- Java集合常见类型?
collection是单列集合,map是双列集合。collection中有两个子接口,list和set,平时用的比较多的像arraylist、linkedlist。hashset、treeset。
map接口中比较常见的是hashmap、treemap、还有一个线程安全的concurrenthashmap
2. arraylist底层是如何实现的?
其底层是使用动态数组来实现的。其初始容量为0,第一次添加数据的时候才会初始化容量为10。array list在进行扩容的时候是原来容量的1.5倍,每次扩容都需要拷贝数组。
其向array list中添加元素的逻辑为:
第一:确保数组已使用长度(size)+1之后足够存下下一个数据
第二:计算数组的容量,如果当前数组已使用长度+1后大与当前数组的长度,则使用grow方法进行扩容(1.5倍)
第三:确保新增数据有地方存储后,则将新元素添加到size的位置
第四:添加成功返回布尔值
-
数组和list之间是如何转换的?
数组转list可以使用Arrays这个jdk自动的一个工具类,使用Arrays.aslist()方法可以直接转换成list。
list转数组:可以调用list中toArray()方法,需要给一个参数,指定数组的类型,需要指定数组的长度 -
用Arrays.asList转List后,如果修改了数组内容,list受影响吗?List用toArray转数组后,如果修改了List内容,数组受影响吗?
第一:Arrays.asList转成list之后,如果修改了数组的内容,list会受影响,因为它底层使用的Arrays类中的一个内部类来构造的集合,在这个集合的构造器中,把我们传入的集合进行了包装,但最终都是指向的同一个内存地址。
第二:List.toArray()转成数组之后,如果修改了list的内容,不会影响数组,因为底层是进行了数组的拷贝,跟原来的元素没有关系了,所以即使list修改,数组也不会受影响。 -
ArrayList 和 LinkedList 的区别是什么?
ArrayList底层使用的是动态数组实现的,LinkedList底层使用的是双向链表。
对于数据的操作效率,ArrayList按照下标查询复杂度是O(1),LinkedList不支持下标查询。未知索引时二者查找效率都是O(n)。
ArrayList底层是数组,内存连续,节省内存。LinkedList是双向链表需要存储两个指针是数据,更占用内存。
二者都不是线程安全的。 -
如何解决ArrayList和Linked List二者都不是线程安全的问题?
我们可以优先在方法内使用,定义为局部变量。
如果要作为成员变量来使用的话,可以使用线程安全的集合来替代。
arraylist可以使用synchronizedlist方法将其转换成线程安全的容器再使用
linked list可以转换成concurrent linkedqueue来使用。 -
hashmap的实现原理?
1底层采用hash表和红黑树(数组+链表|红黑树)
2添加数据时,计算key的值确定元素在数组中的下标,key相同则替换,不同则存入链表或者红黑树中。
3获取数据通过key的hash计算数组下标获取元素 -
hashmap在jdk1.7和jdk1.8有什么区别?
1.8之前采用的是拉链法,即数组+链表
1.8之后采用数组+链表+红黑树,链表长度大于8且数组长度大于64则会从链表转换成红黑树 -
hashmap的put方法具体流程
1判断键值对数组table是否为空或null,否则执行resize()进行扩容(初始化)
2根据键值key计算hash值得到数组索引
3判断table[i]==null,条件成立,直接新建节点添加
4如果table[i]==null不成立,
4.1判断table[i]的首个元素是否和key一样,如果相同则直接覆盖
4.2判断table[i]是否为treenode,即table[i]是否为红黑树,如果是则直接在树中插入键值对。
4.3遍历table[i],链表的尾部插入数据,然后判断链表长度是否大于8,大于的话把链表转成红黑树,在红黑树中执行插入操作,遍历过程中若发现key已经存在则直接覆盖value
5插入成功后,判断实际存在的键值对数量size是否超过了最大容量(数组长度*0.75),如果超过则进行扩容。 -
hashmap的扩容机制?
1在添加元素或者初始化的时候调用resize方法进行扩容,第一次初始化数组长度为16,以后每次扩容都是达到了扩容阈值(数组长度*0.75)
2每次扩容的时候,都是扩容为之前容量的2倍
3扩容之后会创建一个新的数组,把老数组的数据挪动到新的数组中
4没有hash冲突的节点,则直接使用e.hash&(newCap-1)计算新数组的索引位置
5如果是红黑树的话,走红黑树的添加
6如果是链表,则可能需要拆分链表,判断e.hash&oldCap是否为0,该元素要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上。 -
hashmap的寻址算法?
这个哈希方法首先计算出key的hashCode值,然后通过这个hash值右移16位后的二进制进行按位异或运算得到最后的hash值。
在putValue的方法中,计算数组下标的时候使用hash值与数组长度取模得到下标位置 -
为什么hashmap的数组长度一定是2的次幂
计算索引时效率更高,2的n次幂可以使用位与运算替代取模
扩容的时候重新计算索引效率更高 -
hashmap是线程安全的吗?
不是,如果想要线程安全,可以采用concurrenthashmap -
hashset和hashmap的区别?
hashset底层是使用hashmap实现的,利用hashmap的key进行存储元素值,而value默认位object对象,所以hashset不允许重复。两个元素的hashcode相等并且equals()方法返回true。 -
hashtable和hashmap区别?
hashtable是数组+链表
hashmap在jdk1.8之后是数组+链表+红黑树
hashtable存储数据不能位null,而hashmap可以。
hash算法不同,hashtable是用本地修饰的hashcode值
扩容方式不同,hashtable是当前容量翻倍+1,hashmap是当前容量翻倍
hashtable是线程安全的,hashmap不是线程安全的,效率更高一些。
实际开发环境一般不建议使用hashtable