JAVA基础-面试必备

1. Final 有什么用?

被final修饰的类不可以被继承

被final修饰的方法不可以被重写

被final修饰的变量不可以被改变,

被final修饰不可变的是变量的引用,而不是引用指向的内容, 引用指向的内容是可以改变的

2. 什么是重载(Overload)和重写(Override) ?

重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与 方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分

重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于 父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中 就能是重写。

3. 重载的方法能否根据返回类型进行区分?

方法重载不可以根据返回类型区分

4. == 和 equals 的区别是什么

== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数 据类型 == 比较的是值,引用数据类型 == 比较的是内存地址)

equals() : 它的作用也是判断两个对象是否相等。

5. 什么是反射机制?

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任 意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法 的功能称为java语言的反射机制。

6. 反射机制优缺点

优点: 运行期类型的判断,动态加载类,提高代码灵活度。

缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要 慢很多

7. 在你进行项目开发的过程中有没有用到过反射

在我们的项目中经常会使用反射 + 自定义注解的方式去实现一些功能 , 例如 :

  1. 在前后端交互的时候, 后端Long类型返回前端后会产生精度丢失 , 我们的处理方式就是在服务端, 通过配置修改Jackson的序列化规则, 将一些Long类型字段转化为字符串返回给前端, 这个时候我们自定义了一个@IdEncrpt注解 , 通过反射获取类的属性, 判断属性上是否添加了@IdEncrpt注解, 如果添加了 , 就会通过反射获取属性值, 转化为字符串
  2. 在整合EMQ的时候 , 为了能够方便的接收订阅消息, 我们自定义了一个@Topic注解 , 作用在类上 , 之后我们通过反射获取类的字节码, 并且获取类上的@Topic注解, 读取到里面定义的主题 , 通过策略模式将不同主题的消息分发到不同的处理器中
  3. 除了上述之外, 在我们项目开发中经常使用的一些框架, 例如 : Mybatis , Spring , SpringMVC 等, 以及一些常用的工具库 common-utils , hutool工具库等都大量使用到了反射机制

8. String和StringBuffer、StringBuilder的区别是什么?

可变性 : String类中使用字符数组保存字符串,所以string对象是不可变 的。

StringBuilder与StringBuffer这两种对象都是可变的。

线程安全性 : String中的对象是不可变的,也就可以理解为常量,线程安全。StringBuffer对方法加了同步锁或者对调用的方法加了同 步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

性能 : 每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对 象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引 用。

StirngBuilder 相比使用StringBuffer而言效率更高

9. java常见的集合类有哪些

Map接口和Collection接口是所有集合框架的父接口:

  1. Collection接口的子接口包括:Set接口和List接口
  2. Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及 Properties等
  3. Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
  4. List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GmTTQ6Wl-1677423153615)(assets/image-20220518162421503.png)]

10. 常用的线程安全的类有哪些 ?

  1. Vector:就比Arraylist多了个 synchronized (线程安全),因为效率较低,现在已经不太建议使 用。

  2. hashTable:就比hashMap多了个synchronized (线程安全),不建议使用。

  3. ConcurrentHashMap:是Java5中支持高并发、高吞吐量的线程安全HashMap实现

11. ArrayList 和 LinkedList 的区别是什么?

  1. 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实 现。

  2. 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数 据存储方式,所以需要移动指针从前往后依次查找。

  3. 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。

  4. 内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储 了两个引用,一个指向前一个元素,一个指向后一个元素。

  5. 线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

12. 说一下HashMap的实现原理?

HashMap的数据结构: HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

HashMap 基于 Hash 算法实现的

  1. 当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数 组中的下标
  2. 存储时,如果出现hash值相同的key,此时有两种情况。
    1. 如果key相同,则覆盖原始值;
    2. 如果key不同(出现冲突),则将当前的key-value放入链表中
  3. 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。

HashMap JDK1.8之前

JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BZytVazT-1677423153616)(assets/image-20210824162508405-1629793509954.png)]

HashMap JDK1.8之后

相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8) 时,将链表转化为红黑树,以减少搜索时间。扩容 resize( ) 时,红黑树拆分成的 树的结点数小于等于临界值6个,则退化成链表。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TJeEc8Vb-1677423153616)(assets/image-20210824162536987-1629793538980.png)]

13. HashMap的put方法的具体流程?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iXLm7aZ3-1677423153617)(assets/image-20210824163053878.png)]

  1. 判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
  2. 根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向 ⑥,如果table[i]不为空,转向③;
  3. 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的 是hashCode以及equals;
  4. 判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值 对,否则转向5;
  5. 遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操 作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
  6. 插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩 容。

14. 讲一讲HashMap的扩容机制

  1. 在jdk1.8中,resize方法是在hashmap中的键值对大于阀值(0.75)时或者初始化时,就调用resize方法进 行扩容;
  2. 每次扩展的时候,都是扩展2倍;
  3. 扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。

在putVal()中,我们看到在这个函数里面使用到了2次resize()方法,resize()方法表示的在进行第一 次初始化时会对其进行扩容,或者当该数组的实际大小大于其临界值值(第一次为12) , 这个时候在扩 容的同时也会伴随的桶上面的元素进行重新分发,这也是JDK1.8版本的一个优化的地方,在1.7 中,扩容之后需要重新去计算其Hash值,根据Hash值对其进行分发,但在1.8版本中,则是根据 在同一个桶的位置中进行判断(e.hash & oldCap)是否为0,重新进行hash分配后,该元素的位置 要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上

15. ConcurrentHashMap 底层具体实现知道吗?

ConcurrentHashMap 是一种线程安全的高效Map集合

底层数据结构:

  • JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,

  • JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。

JDK1.7

首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段 数据时,其他段的数据也能被其他线程访问。

在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LDI5HE8G-1677423153618)(assets/image-20210824164717055.png)]

一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一 种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构 的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修 改时,必须首先获得对应的 Segment的锁。

Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元 素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁。

JDK1.8

在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保 证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲 突,就不会产生并发 , 效率得到提升

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QURRy3FH-1677423153619)(assets/image-20210824164853535.png)]

16. 创建线程的四种方式

  1. 继承 Thread 类;

  2. 实现 Runnable 接口;

  3. 实现 Callable 接口;

  4. 使用匿名内部类方式

17. runnable 和 callable 有什么区别

  • Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,是个泛型,和Future、 FutureTask配合可以用来获取异步执行的结果
  • Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出 异常,可以获取异常信息 注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到, 此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

18. 加锁的方式有哪些 ?

使用synchronized关键字

使用Lock锁

synchronized和Lock有什么区别 ?

首先synchronized是Java内置关键字,在JVM层面,Lock是个Java类;

synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。

synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁; 而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。

通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

19. 如果你提交任务时,线程池队列已满,这时会发生什么

有俩种可能:

  1. 如果使用的是无界队列 LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到 阻塞队列中等待执行,因为 LinkedBlockingQueue 可以近乎认为是一个无穷大的队列,可以无限存放 任务
  2. 如果使用的是有界队列比如 ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue 中ArrayBlockingQueue 满了,会根据maximumPoolSize 的值增加线程数量,如果增加了线程数量 还是处理不过来,ArrayBlockingQueue 继续满,那么则会使用拒绝策略RejectedExecutionHandler 处理满了的任务,默认是 AbortPolicy

19. 在你们的项目中有没有使用到线程池

我们的项目中很多地方使用了线程池 , 使用的场景经常有如下几种情况

  1. 业务层处理分多个业务线 , 多条业务线的优先级有高有低 , 使用异步线程池执行优先级较低的业务

    比如: 搜索历史记录的异步保存 , 用户行为数据异步入库

  2. 任务很多很重 , 比如说 : 现在有1000w数据需要进行统计运算 (10个线程 每个线程计算100w数据 , 计算完毕之后把10个线程结算结果合并即可)

    每天晚上计算运营统计数据 , 售货机补货数据计算

20. 你了解的线程池的种类有哪些 ?

  1. newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回 收空闲线程,若无可回收,则新建线程。

  2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列 中等待。

  3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

  4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任 务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

21. 线程池的核心参数有哪些 ?

corePoolSize 核心线程数量

maximumPoolSize 最大线程数量

keepAliveTime 线程保持时间,N个时间单位

unit 时间单位(比如秒,分)

workQueue 阻塞队列

threadFactory 线程工厂

handler 线程池拒绝策略

22. 你们项目中使用线程池, 核心线程数如何配置 ?

  1. IO密集型任务 : 核心线程数的数量 约等于 CPU核心数 * 2-3倍
  2. 计算密集型任务 : 核心线程数 约等于 CPU核心数+1

23.线程池的执行原理知道嘛

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ie4OC2bp-1677423153619)(assets/image-20220518165902129.png)]

提交一个任务到线程池中,线程池的处理流程如下:

  1. 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个 流程。
  2. 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队 列里。如果工作队列满了,则进入下个流程。
  3. 判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任 务。如果已经满了,则交给饱和策略来处理这个任务。

24. 新建 T1、T2、T3 三个线程,如何保证它们按顺序执行?

用 join 方法

25. 讲一讲JVM的组成

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LGVQJ3nN-1677423153621)(assets/image-20220518170240888.png)]

JVM包含两个子系统和两个组件:

  • 两个子系统为Class loader(类装载)、Execution engine(执行引 擎);
  • 两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。
    • Class loader(类装载):根据给定的全限定名类名(如:java.lang.Object)来装载class文件到 Runtime data area中的method area。
    • Execution engine(执行引擎):执行classes中的指令。
    • Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。
    • Runtime data area(运行时数据区域):这就是我们常说的JVM的内存。

26. JAVA代码在JVM是怎么执行的

首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到 内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一 套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎 (Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要 调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

27. 说一下 JVM 运行时数据区

Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这 些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区 域则是依赖线程的启动和结束而建立和销毁。Java 虚拟机所管理的内存被划分为如下几个区域

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kwvPgTlG-1677423153623)(assets/image-20210824170002762-1629795603621.png)]

程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解 析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳 转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成; 为什么要线程计数器?因为线程是不具备记忆功能

**Java 虚拟机栈(Java Virtual Machine Stacks):**每个方法在执行的同时都会在Java 虚拟机栈中创 建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息; 栈帧就是Java虚拟机栈中的下一个单位

**本地方法栈(Native Method Stack):**与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的; Native 关键字修饰的方法是看不到的,Native 方法的源码大部分都是 C和C++ 的代码

Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;

方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的 代码等数据。

28. 堆栈的区别是什么?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vLHsJyLP-1677423153623)(assets/image-20220518170436053.png)]

29. 什么是类加载器,类加载器有哪些?

主要有一下四种类加载器:

  1. 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。
  2. 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提 供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
  3. 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
  4. 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现

30. 什么是双亲委派模型?为什么要使用双亲委派模型

双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这 个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到 顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时, 子加载器才会尝试去加载类。

双亲委派可以防止核心类被篡改,提升系统安全性!
避免重复的类加载,加快速率!

总结就是: 当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加 载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。

31. 对于java的Stream流有使用过嘛 , 讲一讲stream流中的常用方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qad5wG2J-1677423153624)(assets/image-20220518171249899.png)]

32. 静态变量与实例变量区别?

静态变量: 是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;直接使用类名称调用
实例变量: 必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。

33.String类的常用方法有哪些?

常用方法:    
    equals:字符串是否相同
	valueOf:其他类型转字符串
	indexOf:目标字符或字符串在源字符串中位置下标
	replace:字符串替换
	split:以某正则表达式分割字符串
	substring:截取字符串
	trim:去字符串首尾空格
	toLowerCase:字符串转小写
	toUpperCase:字符串转大写
	
其他用的比较少的有:
	equalsIgnoreCase:忽略大小写后字符串是否相同
	compareTo:根据字符串中每个字符的Unicode编码进行比较
	compareToIgnoreCase:根据字符串中每个字符的Unicode编码进行忽略大小写比较
	lastIndexOf:目标字符或字符串在源字符串中最后一次出现的位置下标
	charAt:获取指定下标位置的字符
	codePointAt:指定下标的字符的Unicode编码
	concat:追加字符串到当前字符串
	isEmpty:字符串长度是否为0
	contains:是否包含目标字符串
	startsWith:是否以目标字符串开头
	endsWith:是否以目标字符串结束
	format:格式化字符串
	getBytes:获取字符串的字节数组
	getChars:获取字符串的指定长度字符数组
	toCharArray:获取字符串的字符数组
	join:以某字符串,连接某字符串数组
	length:字符串字符数
	matches:字符串是否匹配正则表达式
	replaceAll:带正则字符串替换
	replaceFirst:替换第一个出现的目标字符串

34.接口和抽象类的区别是什么?

内容上的区别
	不同: 
	抽象类: 
		1.抽象类中可以定义构造器 
		2.可以有抽象方法和具体方法 
		3.接口中的成员全都是 public4.抽象类中可以定义成员变量 
		5.有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法 
		6.抽象类中可以包含静态方法 
		7.一个类只能继承一个抽象类 

	接口:
		1.接口中不能定义构造器 
		2.方法全部都是抽象方法 
		3.抽象类中的成员可以是 private、默认、protectedpublic 
		4.接口中定义的成员变量实际上都是常量 5.一个类可以实现多个接口 

	相同:
		1.不能够实例化 
		2.可以将抽象类和接口类型作为引用类型 
		3.一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,
			否则该类仍然需要 被声明为抽象类

	设计层面上的区别 

		1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。
			抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。 

		2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。
			而接口是一种行为规范,它是一种辐射式设计。

35.String str = “i” 和String str = new String(“1”)一样吗?

不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。

36.Java 中的值传递和引用传递

java中没有引用传递,全部是值传递。
java中方法调用参数传递类型无非两种,一种是基本数据类型、另一种是引用数据类型。受到这两种数据类型的影响,让人误以为,参数传递方式是和数据类型有关的,其实不然。无论是基本数据类型还是引用数据类型,传递的都是变量中保存的值的副本,只是引用变量中保存的是内存地址值。

基本类型作为形式参数传递时,形参的改变不会影响实际参数。

引用类型作为形式参数传递时,形参的改变会影响实际参数。

37.sleep和wait的区别?

	共同点
	wait()wait(long)sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态
	不同点
	方法归属不同
	sleep(long)Thread 的静态方法
	而 wait()wait(long) 都是 Object 的成员方法,每个对象都有
	醒来时机不同
	执行 sleep(long)wait(long) 的线程都会在等待相应毫秒后醒来
	wait(long)wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去
	它们都可以被打断唤醒
	锁特性不同(重点)
	wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制
	wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)
	而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)

38.请写出你最常见的几个 RuntimeException?与非运行时异常的区别?

常见的几个 RuntimeException:
    ConcurrentModificationException:并发修改异常
    ClassCaseException: 类型转换异常
    NullPointException: 空指针异常
    ArrayIndexOutOfBoundsException: 数组索引越界异常
    
与非运行时异常的区别?
		RuntimeException:运行时异常,这种异常我们不需要处理,完全由虚拟机接管,如果有异常产生,将由 JVM 进行处理。比如我们常见的NullPointerException,我们在写程序时不会进行catch或throw。
	Exception :受检查的异常,这种异常是强制我们catch或throw的异常。你遇到这种异常必须进行catch或throw,如果不处理,编译器会报错。比如:IOException。

39.try catch有return,发生异常,走return还是finally

1、不管有没有异常,finally中的代码都会执行
2、当trycatch中有return时,finally中的代码依然会继续执行
3finally是在return后面的表达式运算之后执行的,此时并没有返回运算之后的值,而是把值保存起来,不管finally对该值做任何的改变,返回的值都不会改变,依然返回保存起来的值。也就是说方法的返回值是在finally运算之前就确定了的。
4、如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。
5finally代码中最好不要包含return,程序会提前退出,也就是说返回的值不是trycatch中的值

40.浅拷贝和深拷贝区别

浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。举例来说更加清楚:对象A1中包含对B1的引用,B1中包含对C1的引用。浅拷贝A1得到A2A2 中依然包含对B1的引用,B1中依然包含对C1的引用。深拷贝则是对浅拷贝的递归,深拷贝A1得到A2A2中包含对B2B1的copy)的引用,B2 中包含对C2C1的copy)的引用。
若不对clone()方法进行改写,则调用此方法得到的对象即为浅拷贝

41.Java内存泄漏

1、什么是内存泄漏
内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。随着垃圾回收器活动的增加以及内存占用的不断增加,程序性能会逐渐表现出来下降,极端情况下,会引发OutOfMemoryError导致程序崩溃。

2、内存泄漏的原因
JVM 虚拟机是使用引用计数法和可达性分析来判断对象是否可回收,本质是判断一个对象是否还被引用,如果没有引用则回收。在开发的过程中,由于代码的实现不同就会出现很多种内存泄漏问题,让gc 系统误以为此对象还在引用中,无法回收,造成内存泄漏

3、内存泄漏的解决办法
	1.尽量减少使用静态变量,或者使用完及时 赋值为 null2.明确内存对象的有效作用域,尽量缩小对象的作用域,能用局部变量处理的不用成员变量,因为局部变量弹栈会自动回收;

	3.减少长生命周期的对象持有短生命周期的引用;

	4.使用StringBuilderStringBuffer进行字符串连接,StingStringBuilder以及StringBuffer等都可		以代表字符串,其中String字符串代表的是不可变的字符串,后两者表示可变的字符串。如果使用多个String对		象进行字符串连接运算,在运行时可能产生大量临时字符串,这些字符串会保存在内存中从而导致程序性能下降。

	5.对于不需要使用的对象手动设置null值,不管GC何时会开始清理,我们都应及时的将无用的对象标记为可被清理		的对象;

	6.各种连接(数据库连接,网络连接,IO连接)操作,务必显示调用close关闭。	

42.集合类中主要有几种接口

ListSet 是存储单列数据的集合,Map 是存储键值对这样的双列数据的集合;
List 中存储的数据是有顺序的,并且值允许重复;
Map 中存储的数据是无序的,它的键是不允许重复的,但是值是允许重复的;
Set 中存储的数据是无顺序的,并且不允许重复,但元素在集合中的位置是由元素 的 hashcode 决定,
即位置是固定的(Set 集合是根据 hashcode 来进行数据存储的,所以 位置是固定的,
但是这个位置不是用户可以控制的,所以对于用户来说 set 中的元素还是无序的)。

43.HashSet如何检查重复?

当你把对象加入到HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode值,HashSet会假设对象没有重复出现,但是如果发现有相同hashcode值的对象,这时会调用equals()方法来来检查hashcode相等的对象是否真的相同,如果两者相同,HashSet就不会加入操作成功。

hashcode()与equals()的相关规定:
	1、如果两个对象相等,则hashcode一定相等
	2、两个对象相等,对两个对象equals()结果返回true
	3、两个对象有相同的hashcode值,他们也不一定是相等的
	4、综上,equals方法被覆盖过,则hashcode方法也必须被覆盖
	5、hashcode()的默认行为是对堆上的对象产生独特值,如果没有重写hashcode(),
		则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
==与equals的区别
	1==是判断两个变量或实例是不是指向同一个内存空间,
		equals判断两个变量或者实例所指向的内存空间的值是不是相同
	2==是对内存地址进行比较,equals()是对字符串的内容进行比较
	3==指向引用是否相同,equals()指的是值是否相同

44.HashMap和HashSet区别?

1.HashMap实现了Map接口,而HashSet实现了Set接口。

2.HashMap用于存储键值对,而HashSet用于存储对象。

3.HashMap不允许有重复的键,可以允许有重复的值。HashSet不允许有重复元素。

4.HashMap允许有一个键为空,多个值为空,HashSet允许有一个空值。

5.HashMap中使用put()将元素加入map中,而HashSet使用add()将元素放入set中。

6.HashMap比较快,因为其使用唯一的键来获取对象。

45.Hashtable与HashMap有什么不同之处?

1. 线程是否安全:
	HashMap 是非线程安全的,Hashtable 是线程安全的,因为 Hashtable 内部的方法基本都经过 				synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);

2. 效率: 
	因为线程安全的问题,HashMap 要比 Hashtable 效率高一点。
	另外,Hashtable 基本被淘汰,不要在代码中使用它;
	
3.Null key 和 Null value 的支持
	HashMap 可以存储 null 的 key和 value,但 null 作为键只能有一个,null 作为值可以有多个;			Hashtable 不允许有 null 键和 null 值,否则会抛出NullPointerException4. 初始容量大小和每次扩充容量大小的不同 :
	(1)创建时如果不指定容 量初始值,Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。
	(2)创建时如果给定了容量初始值,那么 Hashtable会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的		幂次方大小(HashMap 中的 tableSizeFor()方法保证,下面给出了源代码)。也就是说 HashMap 总是使		 用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。
5. 底层数据结构
	JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成		红黑树前会 判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而 不是转换为红黑树)时,
	将链表转化为红黑树,以减少搜索时间。 Hashtable 没有这样的机制。	

46.红黑树有什么特征?

红黑树的特性:1)每个节点或者是黑色,或者是红色。

(2)根节点是黑色。

(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NILNULL)的叶子节点!]4)如果一个节点是红色的,则它的子节点必须是黑色的。

(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

注意:

(01) 特性(3)中的叶子节点,是只为空(NILnull)的节点。

(02) 特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。

47.字节流和字符流区别?

字节流在操作的时候本身是不会用到缓冲区(内存)的,是与文件本身直接操作的,而字符流在操作的时候是使用到缓冲区的字节流在操作文件时,即使不关闭资源(close方法),文件也能输出,但是如果字符流不使用close方法的话,则不会输出任何内容,说明字符流用的是缓冲区,并且可以使用flush方法强制进行刷新缓冲区,这时才能在不close的情况下输出内容
字节流是万能的流,可以操作任意类型的文件,而字符流只能操作纯文本文件

48.BIO和NIO区别?

1、bio同步阻塞io:在此种方式下,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了了IO操作以后,用户进程才能运行行。JAVA传统的IO模型属于此种方式!

2、nio同步非阻塞式I/O;java NIO采用了了双向通道进行行数据传输,在通道上我们可以注册我们感兴趣的事件:连接事件、读写事件;NIO主要有三大核心部分:Channel(通道)Buffer(缓冲区), Selector。传统IO基于字节流和字符流进行行操作,而NIO基于ChannelBuffer(缓冲区)进行行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。

BIOBlocking I/O):同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。这里里使用那个经典的烧开水例例子,这里里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留留在一个水壶那,直到这个水壶烧开,才去处理理下一个水壶。但是实际上线程在等待水壶烧开的时间段什什么都没有做。

NIONew I/O):同时支持阻塞与非阻塞模式,但这里里我们以其同步非阻塞I/O模式来说明,那么什什么叫做同步非阻塞?如果还拿烧开水来说,NIO的做法是叫一个线程不不断的轮询每个水壶的状态,看看是否有水壶的状态发生了了改变,从而进行行下一步的操作。

AIOAsynchronous I/O):异步非阻塞I/O模型。异步非阻塞与同步非阻塞的区别在哪里里?异步非阻塞无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理理。对应到烧开水中就是,为每个水壶上面装了了一个开关,水烧开之后,水壶会自动通知我水烧开了了。

49.线程的状态转换?

线程的生命周期:线程要经历新建、就绪、运行(活动)、阻塞和死亡五种不同的状态。 这五种状态都可以通过 Thread 类中的方法进行控制。
	1.新建状态:使用 new 操作 符创建一个线程后,该线程仅仅是一个空对象,这时的线程处于创建状态。

	2.就 绪状态:使用 start()方法启动一个线程后,系统为该线程分配了除 CPU 外的所需资 源,使该线程处于就绪状态。

	3.运行状态:系统真正执行线程的 run()方法。

	4.阻塞和唤醒线程阻塞状态:使用 sleep()wait()方法进行操作。

	5.死亡状态:线程 执行了 interrupt()stop()方法,那么它也会以异常退出的方式进入死亡状态。 线程安全问题:使用synchronized 声明同步或使用锁 lock,Lock 使用起来比较灵活,但需要手动释放和开启,采用 synchronized 不需要用户去手动释放锁。	

50.start和run的区别?

1.start方法:
		(1)使该线程开始执行,Java 虚拟机调用该线程的run方法。
		(2)效果是多个线程并发地运行。
		(3)多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
		(4)用start方法来启动线程,真正实现了多线程运行,
			这时无需等待run方法体中的代码执行完毕而直接继续执行后续的代码。
		(5)start方法启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,
			一旦得到cpu时间片,就开始执行run()方法,这里的run方法称为线程体,
			它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止。
	
2.run方法:
		(1)Thread类既实现了Runnable接口,又在内部定义Runnable类型成员变量target,
			run方法内部判断target不是null,调用target的run方法,target是null,就什么也不做
		(2)直接创建Thread对象调用start方法开启线程,成员变量target的值是null,
			多线程方式执行run方法时,此时相当于run方法内部什么也不执行
		(3)所以需要定义Thread的子类重写run方法,指定线程执行的功能,
			然后创建Thread子类对象调用start方法开启线程,就会以多线程的方式运行重写后的run方法
		(4)实现Runnable接口的方式创建并开启线程,最终以多线程的方式调用Runnable接口实现类重写的run方法
		(5)run()方法只是类的一个普通方法而已,如果直接调用run方法,没有多线程的运行效果,
			run方法没有执行完毕,不会继续执行其它代码。

51.Synchronized作用,Synchronized 和 volatile 的区别

	Synchronized作用: 保证多线程操作共享数据时,数据的安全
	1volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。
		但是volatile关 键字只能用于变量而synchronized关键字可以修饰方法以及代码块。
		synchronized关键字在JavaSE1.6之后进行了优化,
		包括减少获得锁和释放锁带来的性能消耗而引入偏向锁和轻量级锁等,优化之后执行效率有了显著提升,
		实际开发中使用 synchronized 关键字的场景还是更多一些;
	2、多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞;
	3volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证;
	4volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访 问资源的同步性。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@山雨欲来风满楼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值