集合

ArrayList底层的实现原理

ArrayList底层是基于动态的数组实现的

ArrayList的初始容量是0只有在第一次添加数据的时候才会初始化容量为10

ArrayList在进行扩容的时候是原来容量的1.5倍,每次扩容都需要拷贝数组

ArrayList在添加数据的时候

需要先确保数组已经使用的长度(size)+1之后足够存在下一个数据

然后计算数组的容量,如果当前数组已使用的长度+1大于当前的数组长度,则调用grow方法来进行扩容

确保新增的数组有地方存储后,将新元素添加到位于size的位置上

返回添加成功的布尔值

如何实现数组和List之间的转换

数组转换成List 使用的是 Arrays.asList方法

List转换为数组使用的是List.toArray方法

用Arrays.asList转换成List后如果修改了数组中的内容,list受到影响吗?List.toArray转换为数组之后修改LIst中的数据,数组会受影响吗?

Arrays.asList修改为List之后,如果修改了数组中的内容list会受到影响

因为他的底层使用的Arrays类中的一个内部类ArrayList来构造的集合,在这个集合的构造器中,我们只是把这个数组进行了包装,他最终指向的还是同一个内存地址

List.toArray转换为数组之后修改LIst中的数据,数组不会受影响

因为List使用toArray转数组之后,在底层是对他进行了数组的拷贝,跟原来的数组就没有关系了。

单向链表和双向链表的区别

单向链表只有一个方向,结点只有一个后继指针next

双向链表支持两个方向,每个节点不仅有一个后继指针next指向后面,还有一个前继指针prev指向前面

链表操作数据的时间复杂度是多少

单向链表的 增删查 头结点的时间复杂度是O(1),其他节点的是O(n)

双向链表的增删查 头尾节点的时间复杂度是O(1) 其他节点是O(n),给定节点的时间复杂度是O(1)

ArrayList和LinkedList的区别

底层数据结构:

ArrayList是基于动态数组实现的;LinkedList是基于双向链表实现的

操作效率:

ArrayList按照下标查询的时间复杂度是O(1),LinkedList不支持下标查询

如果没有索引的话查询这两个的时间复杂度都是O(n)

新增和删除的话 ArrayList尾部是O(1),其他的需要挪动数组 是O(n)

LinkedList头尾是O(1),其他的需要遍历链表是O(n)

内存空间占用:

ArrayList底层是数组,内存连续,是节省内存的;LinkedList是双向链表需要存储数据和两个指针,更占用内存

线程安全 :

这俩个都是线程不安全的

如果要保证线程安全有两种方式

一种是在方法内部使用,因为局部变量是线程安全的

还有一种就是使用线程安全的ArrayList和LinkedList ;在new的时候用Collection.synchronizedList方法

HashMap实现原理

hashMap底层使用的是数组加上链表或者红黑树

添加数据的时候,通过hash算法确定元素在数组的下标 ,如果当前位置存在数据的话,就判断key是否相同,如果相同就覆盖数据,不同的就存入到链表或者红黑树中

HashMap的JDK1.7和JDK1.8的区别

jdk1.7中采用的是数组加链表的形式 ,put的时候采用的是头插法

jdk1.8中采用的是数组加链表加红黑树,在链表长度大于8并且数组长度大于64时链表会转换为红黑树,put的时候采用的是尾插法

HashMap的put的具体流程

先去判断键值对数组table是否为空,如果为空就去执行resize()进行扩容,这个就是初始化,如果不为空就去计算key来确定数组的下标,先去判断数组当前下标的位置是否有数据,如果没有数据就直接添加,如果有数据就去判断key是否存在,如果存在就直接覆盖,如果不存在就去判断这个是不是红黑树,如果是红黑树,就走红黑树的添加逻辑,如果不是红黑树,就遍历链表查看当前的key是否存在,如果存在就覆盖掉,如果不存在就在链尾添加数据,然后再判断链表长度是否大于8,如果大于8就转换为红黑树,最后再判断当前数组的长度是否超过一个阈值,这个阈值就是用当前数组的长度乘以加载因子(0.75),如果大于阈值就进行扩容。

HashMap的扩容机制

再添加元素或者初始化的时候调用resize()方法进行扩容,第一次添加的数组的长度为16,以后每次扩容都是到达了扩容阈值,就是数组长度乘以加载因子(0.75),每次扩容都是之前数组的2倍,扩容之后,会创建新的数组,需要把老的数组的数据挪到新数组中,没有hash冲突的节点使用位于运算{e.hash&(新数组的长度-1)}计算下标放到新数组中,如果是红黑树就走红黑树的添加逻辑,如果是链表,就需要遍历链表,可能需要拆分链表,通过判断{(e.hash&旧的数组长度)是否为0}要不该元素还在原始位置,要不移动到新的位置(新位置=旧位置+旧的数组长度)上。

hashMap的寻址算法

计算对象的hashCode(),再进行调用hash()方法进行二次哈希,hashcode值右移16位在异或运算,让哈希分布的更加均匀,最后(数组长度-1)位于hash得到索引

为何HashMap的数组长度一定是2的次幂

计算索引时效率更高,如果是2的n次幂可以使用位于运算来代替取模,还有就是扩容时重新计算索引的效率更高,(e.hash&旧的数组长度) 0 {(e.hash 位于 旧的数组长度)0}的元素留在原来的位置,否则新位置=旧位置+旧的数组长度

HashMap在1.7的情况下多线程死循环问题

在jdk1.7的hashMap中在进行数组扩容的时候,因为链表是头插法,在进行数据迁移的时候,有可能会导致死循环,

比如说,现在有两个线程

线程一:读取到当前hashmap数据,数据中一个链表,在准备扩容的时候,线程二介入;

线程二:也读取hashmap,直接进行扩容,因为是头插法,链表的顺序会颠倒过来,比如原来的顺序是AB,扩容之后变成了BA,线程二执行结束。

线程一:继续执行的时候就会出现死循环的问题。

线程一先将A移入新的链表,再将B插入链头,由于另一个线程的原因,B的next指向了A,所以出现了B->A->B,形成循环,当然jdk1.8中将扩容算法做了调整,不再将元素加入链头,而是保持与扩容前一样的顺序,就避免了jdk7中死循环的问题。

 posted on 2024-03-28 21:58  瞎弄  阅读(9)  评论(0)    收藏  举报