List和Set的区别
List
- 允许元素重复
- 允许元素为null
- 有序的,读取的顺序即为元素插入的顺序
- 常用的实现类有ArrayList、LinkedList
Set
- 不允许元素重复
- 只有一个元素可以为null
- 无序的(实际也是有顺序的,但是不是按照插入的顺序排列)
- 常用的实现类有HashSet、LinkedHashSet、TreeSet
HashSet是如何保证不重复的
- 如果hash码不同,则不是同一个元素,存
- 如果hash码相同,如果equals值不同,则存;否则不存
HashMap是线程安全的吗?为什么
- 不是线程安全的
- 可能在两个方面引起线程不安全
- put时:若线程A、B同时将相同的key存入HashMap中,当A的线程查询到插入的位置时,线程B获得cpu并将数据插入HashMap,此时线程A重新获得cpu将数据从B更新为A,此时数据的一致性出现问题,丢失了B的数据
- resize时:若线程A、B同时执行resize(),当线程A未执行完数据迁移时线程B获得cpu并顺利执行完resize操作,此时线程A重新获得cpu,因为jdk1.7中时逆序插入,所以此时的next会指向原map中上一个数据,该段数据会形成环形链表。此时再调用HashMap的get()方法时,会陷入死循环
HashMap的扩容过程
HashMap的扩容过程在resize()方法中实现,jdk1.7和jdk1.8的实现过程存在一部分的不同,故分别讲述
1. jdk1.7
.
除了计算新Map的容量之外,核心的数据迁移在transfer方法中。
遍历整个table,逐个迁移其中的链表数据
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
不难看出,根据单个entry的key的hash值和新的容量,求出新的entry在新的map中的位置,并且将当前的元素迁移到新的链表的头部,完成数据迁移,因此在jdk7中元素在对应的链表中是逆序插入的
2. jdk1.8
jdk1.8的做法与1.7略微有些出入。
遍历table,遍历单个entry的链表,如果
e.hash & oldCap == 0
将当前元素插入到与原Map相同的table位置处,否则插入到j+oldCap位置处,见代码,为方便理解,做简化
if(e.hash & oldCap==1){
newTab[j + oldCap] = e;
}
不难看出,此时元素在对应的链表中是顺序插入的
因此,其他博文中说的HashMap在resize()时同一个链表中的数据是逆序插入的是jdk1.8之前的说法
final、finally、finalize的区别
final
- 修饰符号,可用于修饰变量、方法、类
- 被final修饰的变量不可以在初始化后被修改,但可以在不同阶段被初始化
```
private final int a = 3;
private final int a;
{
a = 3;
}
private static final int a;
static{
a = 3;
}
private final int a;
Constructor(){
a = 3;
}- 被final修饰的方法不可被重写,因此抽象方法不能被声明成final - 被final修饰的类不可被继承
finally
- 用于try代码块
```
try{
}catch{
}finally{
}
```
```
try{
}finally{
}
```
- 在finally代码块前若执行System.exit(code),则不会执行finally中的代码块
![这里写图片描述](https://img-blog.csdn.net/20180916162234697?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lvdWhhdmVhZ29vZGRheQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
可以看出,程序先执行finally中的计算,再return结果值
finalize
- 方法,在垃圾收集器将对象从内存中清理出去前会执行的方法
- 一个对象只会执行一次,当该对象在垃圾回收器的预回收内存中时,在finalize方法中重新为该对象建立引用,可以将该对象重新变为强引用关系,但该操作不可进行第二次
强引用、软引用、弱引用、虚引用
- 强引用:最普遍的引用,当对象存在强引用时,垃圾回收器一定不会回收该对象
- 软引用:非必须引用,内存即将溢出时被垃圾回收器回收
- 弱引用:第二次垃圾回收时回收
- 虚引用:垃圾回收器回收时回收,无法通过引用获取到对象值
Java反射
反射机制:
在运行状态中,对于任意一个类,都可以知道他所有的方法和属性;对于任意一个对象,都可以调用他的所有方法和属性,这种动态获得信息和调用对象方法的功能叫做反射机制
什么是反射:
- 间接操作对象,获取或者设置对象属性
- 只要知道类的名称,就能够获得类的所有信息,从而调用类的所有方法和属性
反射的步骤
- 获得类的上下文加载器:
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
- 通过类全名获得类
Class clazz = classLoader.loadClass(path);
- 通过类获得构造函数
Constructor cons = clazz.getConstructor(null);
- 通过构造函数创建对象
Object obj = cons.newInstance();
- 通过类获得方法
Method method = clazz.getMethod("methodName", paramsClass);
- 调用方法
method.invoke(obj, params);
Arrays.sort()和Collections.sort()的实现原理
Arrays.sort()的实现原理
Arrays的排序采用DualPivotQuicksort(双基准快速排序)算法
点进去可以看到,当数组的长度小于QUICKSORT_THRESHOLD(286)的时候,直接采用快速排序
当长度小于286的时候,会判断数组长度是否大于INSERTION_SORT_THRESHOLD(47),如果小于这个值,则采用插入排序,否则使用快速排序
当长度大于286的时候,会判断数组的连续性好不好;如果数组的连续性好,会采用归并排序,否则使用快速排序
总结:
- 调用Arrays.sort()排序时,
- 如果arr.length() < 47,会采用插入排序
- 如果arr.length()>47 && arr.length()<286,则采用快速排序
- 如果arr.length()>286 && 数组的连续性好,采用归并排序
- 如果arr.length()>286 && 数组的连续性不好,采用快速排序
Collections.sort()的实现原理
Collections.sort()底层调用了Arrays.sort(T[] )
会发现,如果用户设置了使用归并排序的话,则使用归并排序;否则使用TimSort算法
LinkedHashMap的应用
概念
- 是Map接口的哈希表和链表的实现,具有可预知的迭代性(顺序与put时一致)
- 区别于HashMap,LinkedHashMap维护着一个运行于所有条目的双向链表,链表定义了迭代的顺序,该顺序可以插入、读取
实现
- Jdk1.7和Jdk1.8的实现有所区别,这里先讲述jdk1.7的实现方式
- Entry:除了保存当前对象的引用外,还保存了上一个引用before和下一个引用after,从而构成了双向链表
- 初始化:调用了父类HashMap的构造函数,重写了init()方法
@Override
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
- 存储:重写了addEntry()和createEntry(),提供了双向链表的特性
- 读取:重写了get()方法,在getEntry()后,判断当前的accessOrder为true的话,将最近访问的entry放到链表头,并从原来的位置删除
- 排序模式:对于访问顺序,如果accessOrder为true,则使用插入排序;否则根据插入时候的迭代顺序
cloneable接口实现原理
什么是clone
对于给定一个对象o,获得另一个对象o’,o和o’类型相同,内容相同,则称o’是o的克隆或拷贝
什么时候使用克隆
当想要修改对象属性,又不想影响原来值的时候
Java对克隆的支持
- Object类有clone方法,
protected Object clone() throws CloneNotSupportedException
- 提供Cloneable接口,表明该类可以被拷贝
浅克隆、深克隆
- 浅克隆:Object克隆时对对象一无所知,仅进行域对域的复制
- 深克隆:对对象的所有引用进行浅克隆
异常分类以及处理机制
异常分类
- Throwable:所有异常的祖先,指定代码中可以用异常处理机制通过Java应用程序传输的任何问题的共性
- Error: 程序无法处理的错误,表示运行应用程序中较为严重的问题,表示代码运行时JVM出现的问题,如虚拟机运行错误、内存溢出,Error发生时虚拟机将停止工作
- Exception:
- 运行时异常:都是Runable及其子类异常,包括空指针异常、数组下标越界异常
- 非运行时异常(编译异常):非Runable以外的异常,类型上都是Exception子类
异常处理机制
- 抛出异常:当一个方法出现错误并引发异常时,方法创建异常对象并交付运行时系统,异常对象包含异常类型和发生异常时的程序信息,运行时程序负责检测到异常并执行程序
- 捕获异常:当方法抛出异常时,运行时程序会为之寻找合适的异常处理器
throws抛出异常的规则
- 如果是不可查异常,可以不使用throws抛出,可以通过编译,程序运行时遇到抛出
- 必须声明方法可抛出的任何可查异常
- 仅当抛出了异常,该方法的调用者才必须处理或者抛出异常
- 调用方法必须遵循任何可查异常的处理和声明规则
wait和sleep的区别
wait
- 属于Object
- 使对象放弃持有锁,并进入等待的线程池
- 只有对该对象调用notify()才可以唤醒该对象
sleep
- 属于Thread
- 使程序等待一段时间,让出cpu给其他的程序,但他的监控程序一直在运行,当指定时间到达会自动进入运行状态
- 期间不会释放锁
数组在内存中如何分配
数组在内存中的初始化
静态初始化
初始化时显式指定每个数组元素的初始值,由系统决定长度
动态初始化
初始化时显式指定数组的长度,由系统给每个元素分配初始值
数组的内存分配
如int[] arr = new int[3];
在堆内存中开辟int[3],在栈内存中创建arr,在arr地址上存储int[3]的堆内存地址