目录
3.String、StringBuffer、StringBuilder的区别
11.Jdk1.7到Jdk1.8 HashMap 发⽣了什么变化(底层)?
15.CopyOnWriteArrayList的底层原理是怎样的
18.在Java的异常处理机制中,什么时候应该抛出异常,什么时候捕 获异常?
1.JDK、JRE、JVM之间的区别
- JDK(Jav aSE Development Kit): 提供了Java编译、运行的开发工具以及资源,包括了Java编译期,Java运行时环境和Java的类库
- JRE(Java Runtime Enviroment): 就是所谓的java运行时环境,用于运行class文件。其中包括了JVM和JVM所需的类库,普通人员只需要安装JRE就可以跑一个java项目,开发人员必须安装Jdk进行编写java程序。
- JVM(Java Virtual Mechinal):java虚拟机,也是JRE的一部分。其作用就是Java想要跨平台运行,就要用到JVM来运行class字节码文件。
- 举例: 如果你想写java程序,就可以用txt来编写,最后把txt文件通过javac 编译一下,编译成字节码文件,JVM可以执行字节码文件,这样就可以运行一个java程序。
2.hashcode()和equals()的区别
hashcode(): 意思就是就是当对象调用hashcode()方法时,会计算出一个hash值,其实就行相当于那个对象的指纹信息,但是Java中不能保证每个对象的指纹信息(hash值)唯一,就是不能完全保证不相同,也是会有相同的可能性,比如:
1.如果这两个对象的hashcode不相同,则这两个对象肯定不会是同一个。
2.如果这两个对象的hashcode相同,并不能说是同一对象,也有可能是两个对象
3.如果两个对象相同,则hashcode一定相同
当java中的集合类比较两个对象是否相等,就跟上面一样,先调用对象的hashcode()方法,先进行比较,如果不相同,则两个对象一定不相同,如果说相同的话,就要在调用equals()方法再次进行比较,用来判断这两个对象到底是否相等。equals()方法是比较重的比较方法,hashcode()方法是较轻的,就需要计算出哈希值,就是所谓的数字进行比较,所以会先调用hashcode()方法。
3.String、StringBuffer、StringBuilder的区别
String 是一个不可变字符串,源码是一个final修饰的char数组,private final char value[];如果要修改这个字符串的话,就是又创建了一个String对象,StringBuffer和StringBuilder是可变字符串。可以通过他们的append()改变。
StringBuffer是线程安全的,StringBulider是线程不安全的,单线程效率下使用StringBuilder效率更高
StringBuffer 的append()方法源码:通过加锁来实现线程安全的
StringBulider的append()源码:
4.泛型中extends和super的区别
1.< ? extends T > 表示包括T在内的任何T的子类
2.< ? super T >表示包括T在内的任何T的父类
5.==和equals()的区别
==:如果是基本数据用==比较的就是值,如果是引用数据类型用==比较的就是地址。
equals(): 比较的是两个之间的内容是否相等
6.重写和重载的区别
- 重载(Overload): 在一个类中,同名的方法(参数类型不同,参数个数不同,参数顺序不同)则视为重载。
- 重写(Override): 重写的意思就是重写一遍,发生在子类继承父类的时候,方法重写方法名,参数类型,返回值必须相同,且访问权限必须大于父类,抛出的异常也必须小于父类。
7.List和Set的区别
- List: 有序,可重复,可以add多个null,可以通过迭代器来取出各个元素,也可以通过get(index)来取出指定下标元素。
- Set: 无序,不重复,最多只能有与一个null对象,只能通过迭代器(iterator)取出所有元素。
8.ArrayList和LinkedList区别
- ArrayList: 基于动态数组实现,是一个有序集合,比较适合查询操作效率更高,通过add()方法添加集合中,如果指定下标add(index)添加的效率比较慢,因为涉及到元素的移动,后面的元素会向后移动,就涉及到数组的扩容机制,所以效率比较慢。
- LinkedList: 基于链表实现,更适合元素的添加、删除,不适合查询,查询要遍历所有元素,实现了Deque,可以用来当做队列。添加元素时添加到集合的首尾速率较快。
- 两个集合都实现了List接口。
9.谈谈ConcurrentHashMap的原理
谈到ConcurrentHashMap就要讲一讲jdk 1.7 和 jdk 1.8 的区别
jdk1.7版本:ReentrantLock + Segment + Entry
1.通过分段式锁Segment来实现,其实就是一个Segment数组,一个Segment对象里面包含了一个HashEntry.
2.调用put(key,value) 方法时,根据源码分析,先是调用hashcode()方法计算出哈希值,再把算出来的(hash值和Segment数组.length-1)进行与运算,计算出相对应Segment下标(index),在多线程并发下,线程回去竞争lock()锁,调用Segment.lock();意思就是如果线程1加锁,就执行线程1,线程2就要等待。线程1执行后还要进行(hash值和entry.length-1)的与运算,计算出Segment[index]对象中Entry的数组下标,找到之后记性插入,如果没有元素直接插入,如果有的话就插入在后面,就行成了一个链表。
jdk1.8版本: Synchronized + Node + CAS + 红黑树
数组有多长,锁的量就有多大,因为每一个锁是锁Node 的首节点,多线程环境下操作时,操作同一个Node时,会排队等待,多个元素插入变成一个链表,大数据涌入时每次都要调用.next()方法性能比较慢,所以就要引用红黑树,当链表的长度大于8时,就变成一颗红黑树来提升效率。
10.谈谈 concurrentHashMap的扩容机制
jdk1.7版本:
1.concurrentHashMap是由Segment实现
2.Segment内部相当于一个HasMap
3.每个Segment会自动扩容,也就跟HashMap扩容类似
4.先生成新的数组,然后转移元素到新的数组上去
5.是否需要扩容是Segment内部判断的,每个Segment单独判断
jdk1.8版本:
1.不在有Segment实现
2.当某个线程进行put时,如果发现ConcurrentHashMap正在进行扩容,那么就加入一起进行扩容。
3.当某个线程进行put时,发现没有正在扩容,那么就将key-value添加到ConcurrentHashMap中,然后判断阈值,是否需要扩容
4. ConcurrentHashMap支持多线程进行扩容
5.扩容之前生成一个新的数组
6.在转移元素时,先将原数组分组,然后交给不同的线程来进行元素的转移,每个线程分组一组或者多组
11.Jdk1.7到Jdk1.8 HashMap 发⽣了什么变化(底层)?
- JDK1.7 用的是数组+链表实现的,JDK1.8用得是数组+链表+红黑树,加入红黑树的原因就是加快查询和修改的效率
- 1.7中链表采用的是头插法,1.8采用的就是尾插法,因为1.8中插入key和value时需要判断练链表元素个数,所有需要遍历整个链表,所有正好采用尾插法。
- 1.7哈希算法比较复杂,存在各种右移与异或运算,1.8中进行了简化,因为复杂的哈希算法就是为了提高散列性,来提供整个HashMap的效率,而1.8中提供了红黑树,所以可以适当的简化哈希算法,节省CPU资源。
12.说⼀下HashMap的Put⽅法
先说HashMap的Put⽅法的⼤体流程:
- 根据Key通过哈希算法与与运算得出数组下标
- 如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中 是Node对象)并放⼊该位置
- 如果数组下标位置元素不为空,则要分情况讨论
- 如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进⾏扩容,如果不⽤扩容就⽣成Entry对 象,并使⽤头插法添加到当前位置的链表中
- 如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红⿊树Node,还是链表Node
- 如果是红⿊树Node,则将key和value封装为⼀个红⿊树节点并添加到红⿊树中去,在这个 过程中会判断红⿊树中是否存在当前key,如果存在则更新value
- 如果此位置上的Node对象是链表节点,则将key和value封装为⼀个链表Node并通过尾插 法插⼊到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会 判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插⼊到链 表中,插⼊到链表后,会看当前链表的节点个数,如果⼤于等于8,那么则会将该链表转成 红⿊树
- 将key和value封装为Node插⼊到链表或红⿊树中后,再判断是否需要进⾏扩容,如果需要 就扩容,如果不需要就结束PUT⽅法
13.深拷⻉和浅拷⻉
- 深拷贝与浅拷贝都是指对象的拷贝,一个对象中存在两种属性,一种是基本数据类型,一种是实例对象的引用
- 浅拷贝: 只会拷贝基本数据类型的值,以及实例对象的引用地址,并不会拷贝引用地址所指向的对象,也就是拷贝出来的对象,内部的类属性指向的是同一个对象
- 深拷贝: 既会拷贝基本数据类型的值也会拷贝引用地址所指向的对象进行复制,深拷贝出来的对象,内部的属性指向的不是同一个对象
14.HashMap的扩容机制原理
1.7版本
1. 先⽣成新数组
2. 遍历⽼数组中的每个位置上的链表上的每个元素
3. 取每个元素的key,并基于新数组⻓度,计算出每个元素在新数组中的下标
4. 将元素添加到新数组中去
5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
1.8版本
1. 先⽣成新数组
2. 遍历⽼数组中的每个位置上的链表或红⿊树
3. 如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去
4. 如果是红⿊树,则先遍历红⿊树,先计算出红⿊树中每个元素对应在新数组中的下标位置
a. 统计每个下标位置的元素个数
b. 如果该位置下的元素个数超过了8,则⽣成⼀个新的红⿊树,并将根节点的添加到新数组的对应位置
c. 如果该位置下的元素个数没有超过8,那么则⽣成⼀个链表,并将链表的头节点添加到新组的对应位置
5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
15.CopyOnWriteArrayList的底层原理是怎样的
1. ⾸先CopyOnWriteArrayList内部也是⽤过数组来实现的,在向CopyOnWriteArrayList添加元素
时,会复制⼀个新的数组,写操作在新数组上进⾏,读操作在原数组上进⾏
2. 并且,写操作会加锁,防⽌出现并发写⼊丢失数据的问题
3. 写操作结束之后会把原数组指向新数组
4. CopyOnWriteArrayList允许在写操作时来读取数据,⼤⼤提⾼了读的性能,因此适合读多写少的应 ⽤场景,但是CopyOnWriteArrayList会⽐较占内存,同时可能读到的数据不是实时最新的数据,所 以不适合实时性要求很⾼的场景
16.什么是字节码?采⽤字节码的好处是什么?
编译器(javac)将Java源⽂件(*.java)⽂件编译成为字节码⽂件(*.class),可以做到⼀次编译到处运⾏, windows上编译好的class⽂件,可以直接在linux上运⾏,通过这种⽅式做到跨平台,不过Java的跨平 台有⼀个前提条件,就是不同的操作系统上安装的JDK或JRE是不⼀样的,虽然字节码是通⽤的,但是 需要把字节码解释成各个操作系统的机器码是需要不同的解释器的,所以针对各个操作系统需要有各⾃ 的JDK或JRE。
采⽤字节码的好处,⼀⽅⾯实现了跨平台,另外⼀⽅⾯也提⾼了代码执⾏的性能,编译器在编译源代码 时可以做⼀些编译期的优化,⽐如锁消除、标量替换、⽅法内联等
17.Java中的异常体系是怎样的
- Java中的所有异常都来⾃顶级⽗类Throwable。
- Throwable下有两个⼦类Exception和Error。
- Error表示⾮常严重的错误,⽐如java.lang.StackOverFlowErrorJava.lang.OutOfMemoryError,
- 通常这些错误出现时,仅仅想靠程序⾃⼰是解决不了的,可能是虚拟机、磁盘、操作系统层⾯出现
- 的问题了,所以通常也不建议在代码中去捕获这些Error,因为捕获的意义不⼤,因为程序可能已经 根本运⾏不了了。
- Exception表示异常,表示程序出现Exception时,是可以靠程序⾃⼰来解决的,⽐如
- NullPointerException、IllegalAccessException等,我们可以捕获这些异常来做特殊处理。
- Exception的⼦类通常⼜可以分为RuntimeException和⾮RuntimeException两类
- RunTimeException表示运⾏期异常,表示这个异常是在代码运⾏过程中抛出的,这些异常是⾮检查 异常,程序中可以选择捕获处理,也可以不处理。这些异常⼀般是由程序逻辑错误引起的,程序应该从逻 辑⻆度尽可能避免这类异常的发⽣,⽐如NullPointerException、IndexOutOfBoundsException等。
- ⾮RuntimeException表示⾮运⾏期异常,也就是我们常说的检查异常,是必须进⾏处理的异常,如果 不处理,程序就不能检查异常通过。如IOException、SQLException等以及⽤户⾃定Exception异常。
18.在Java的异常处理机制中,什么时候应该抛出异常,什么时候捕 获异常?
- 异常相当于⼀种提示,如果我们抛出异常,就相当于告诉上层⽅法,我抛了⼀个异常,我处理不了这个 异常,交给你来处理,⽽对于上层⽅法来说,它也需要决定⾃⼰能不能处理这个异常,是否也需要交给 它的上层。
- 所以我们在写⼀个⽅法时,我们需要考虑的就是,本⽅法能否合理的处理该异常,如果处理不了就继续 向上抛出异常,包括本⽅法中在调⽤另外⼀个⽅法时,发现出现了异常,如果这个异常应该由⾃⼰来处理,那就捕获该异常并进⾏处理。
19.Java中有哪些类加载器
- JDK⾃带有三个类加载器:bootstrap ClassLoader、ExtClassLoader、AppClassLoader。
- BootStrapClassLoader是ExtClassLoader的⽗类加载器,默认负责加载%JAVA_HOME%lib下的 jar包和class⽂件。
- ExtClassLoader是AppClassLoader的⽗类加载器,负责加载%JAVA_HOME%/lib/ext⽂件夹下的 jar包和class类。
- AppClassLoader是⾃定义类加载器的⽗类,负责加载classpath下的类⽂件。
20.说说类加载器双亲委派模型
- JVM中存在三个默认的类加载器:1. BootstrapClassLoader 2. ExtClassLoader 3.AppClassLoader
- AppClassLoader的⽗加载器是ExtClassLoader,ExtClassLoader的⽗加载器是 BootstrapClassLoader。 JVM在加载⼀个类时,会调⽤AppClassLoader的loadClass⽅法来加载这个类,不过在这个⽅法中,会 先使⽤ExtClassLoader的loadClass⽅法来加载类,同样ExtClassLoader的loadClass⽅法中会先使⽤ BootstrapClassLoader来加载类,如果BootstrapClassLoader加载到了就直接成功,如果 BootstrapClassLoader没有加载到,那么ExtClassLoader就会⾃⼰尝试加载该类,如果没有加载到, 那么则会由AppClassLoader来加载这个类。
- 所以,双亲委派指得是,JVM在加载类时,会委派给Ext和Bootstrap进⾏加载,如果没加载到才由⾃⼰ 进⾏加载。
- 总结一句话就是,向上委派,向下加载