ArrayList和Vector有什么区别?
Vector 他是线程安全的动态数组 Vector 扩容是扩大一倍,ArrayList是或扩充50%
ArrayList 和 LinkedList 有什么区别?
ArrayList底层是数组,他总体来说增删慢查询快,因为每次增删都需要调整数据在数组中的位置,除了在头尾,查询可以根据下标直接拿到
LinkedList底层是双向链表,他总体来说查询慢增删快,因为增删只需要调整数据的前后指针即可,除了头尾,但是他查询的话需要遍历整个链表
ArrayList 的扩容机制了解吗?
他会先检查是否需要扩容,如果当前容量+1大于数组长度就会触发扩容,在扩容的时候会新建出一个原来数组长度的1.5倍然后把旧数据拷贝过去,再进行添加新的元素。
有哪几种实现 ArrayList 线程安全的方法?
使用CopyOnWriteArrayList
使用Vector
使用Collections.synchronizedList 包装 ArrayList
使用 ArrayList 时,应用程序通过同步机制去控制 ArrayList 的读写。
HashMap的数据结构
在1.8以前是数组+链表的结构,1.8以后是会在链表数据超过一定阈值的时候转换为数组+红黑树的结构来提高查询效率。
为什么使用红黑树而不是二叉树或平衡树
因为二叉树或平衡树他们横向扩展差,数据过多的话会让树的高度变高导致查找、插入和删除操作的效率下降,而红黑树就会解决这个问题
HashMap的put流程
首先会对key做哈希运算取到哈希值,会根据这个哈希值找到他在数组中的位置,这个时候会出现两种情况,如果当前的位置是空的就会直接将值插入,如果这个位置不是空的就会比对他的key值是否相等,如果相等就会对已有的值进行替换,如果不想等就会说明发生了哈希冲突会在这个位置添加一个新的节点,如果链表长度超过8的时候且当前HashMap的容量达到了一个较大的阈值(负载因子超过0.75),会触发扩容操作,将HashMap的容量增加一倍,并重新计算每个元素在新数组中的索引位置。
HashMap的get流程
会先根据key做哈希运算取到哈希值,根据这个哈希值找到数组的对应位置,如果这个位置没值就会返回null,如果有值那么有可能是多个或者一个,一个的话就会直接取到值并进行返回,如果是多个值会遍历链表并判断是否是这个key并取到值,如果遍历会没有值就会返回null。
HashMap扩容机制
如果链表长度超过8的时候且当前HashMap的容量达到了一个较大的阈值(负载因子超过0.75),会触发扩容操作,将HashMap的容量增加一倍,并重新计算每个元素在新数组中的索引位置,扩容后,负载因子会相应地降低,这样可以提供更好的性能和更低的冲突概率。
初始化HashMap传递初始容量17数组长度实际为多少
HashMap在设置初始容量并不是直接设置数组长度,而是通过将传递的初始容量减去1后进行位运算,最终的数组长度就是经过位运算得到的值,所以初始容量是17数组长度实际为32。
如何解决Hash冲突
链表+红黑树,通过将链表转换为红黑树,可以减少链表查找的时间复杂度。这样,即使在有大量哈希冲突的情况下,HashMap仍然能够保持较高的性能。
链表转红黑树的阈值为什么是8
根据以实际测试与优化得出阈值为8时是一个平衡性能和内存消耗的考量,如果全部转为红黑树他每个节点都需要存颜色、父节点、左子节点、右子节点的指针会导致内存消耗增加。
扩容因子为什么是0.75
经过实践和优化,这个值既允许在数组中保持一定的空闲槽位,以减少冲突的概率,又可以有效利用内存空间,所以扩容因子选择为0.75是为了在HashMap的负载因子和内存利用率之间找到一个平衡点,以提供较好的性能和空间利用率。
为什么 HashMap 的容量是 2 的倍数呢?
我看过HashMap的底层源码他注释上写的容量是2的时候在时间与存储上都是最佳的。
HashMap1.7和1.8的区别
数据结构从数组+链表 改成了数组+链表+红黑树
链表的插入方式从头插法改成尾插法,因为头插法在多线程环境扩容的时候可能会产生死循环
1.7是先判断是否需要扩容再插入数据,1.8以后是先插入数据再判断是否需要扩容
1.7散列函数做了4次位移和4次异或,1.8只做了1次
HashMap和HashTable的区别?
HashTable是线程安全的,HashMap是线程不安全的
HashTable的key和balue都不可以出现null,而HashMap可以
HashTable默认容量是11并且扩容不要求一定是2的整数次幂触发扩容时会变成原来的2倍加1,HashMap默认容量是16扩容必须是2的整数次幂触发扩容时会变成原来的2倍