遍历ArrayList时移除一个元素foreach删除会导致快速失败问题,可以使用迭代器的remove()方法
Iterator itr=list.iterator();
while(itr.hashNext()){
if(itr.next().equals("jay")){
itr.remove();
}
}
list、set、map的区别list是有序,下标,可重复,可空,数组
set是无序,map的键,不可重复,只能一个可空,基于map实现(基于hash存储和红黑树)
map无序,键值对,但底层也是数组,不可重复,可空,基于hash存储和红黑树
ArrayList和vector区别
- Arraylist默认扩容50%,线程不安全
- Vector线程安全,默认扩容一倍 ArrayList和LinkedList区别
- ArrayList基于动态数组,LinkedList基于链表
ArrayList下标查询,LinkedList指针遍历,所以查询和修改速度优于LinkedList HashMap
put方法流程?
判断map是否为null,是就执行resize()扩容
- 根据hashcode&(length-1)计算key的索引,如果索引处为null,直接插入节点,否则遍历当前链表或红黑树。如果有数据的key和hash和插入的相同,就替换,没有就尾插。
添加元素后,看是否需要扩容,如果链表长度>8,再看数组容量,如果>64就转红黑树,如果<64就扩容
HashMap扩容机制
当元素个数超过数组大小*负载因子(默认值0.75)时扩容,用尾插法转移到新数组,每次扩容一倍,所以hashmap最小大小是16。
1.7之前是头插法,在多线程的情况下,原先按顺序排列的链表有可能出现首尾相连的问题,所以在1.8之后采用尾插法。为什么加载因子默认是0.75
对时间和空间成本上的折中,对空间和时间效率的最佳平衡,对于泊松分布,负载因子取0.75时,λ=0.5,碰撞最小,链表数量达到8个时的概率变得非常小,长度大于8几乎不可能,从而也决定了红黑树和链表切换的节点是8
hash冲突如何解决
hashmap引入链式寻址法,把存在冲突的key组成一个单向链表,采用尾插法把key保存到链表的尾部
另外解决hash冲突的方法有很多
- 再hash法,如果某个函数产生了冲突,再用另外一个hash函数进行计算
- 开放定址法,以key计算出的hashcode再次hash。
- 建立公共溢出区,把存在冲突的key统一放在一个公共溢出区里。
在解决hash冲突时,为什么选择先用链表,再转红黑树
因为红黑树需要左旋、右旋、变色这些操作来保持平衡,而单链表不需要。因为红黑树新增效率慢,当元素个数小于8的时候,采用链表性能更高,元素大于8个、数组容量小于64时,链表查询速度慢,会采用红黑树加快查询速度。什么时候红黑树退回链表
数组扩容时会执行resize()方法,将会调用split()重组红黑树,如果节点数少于6个就退回到链表。HashMap的长度为什么是2的幂次方
数据存入map时,是用数据的key的hash值%hashmap的长度得到的值作为数据在数组的下标,按位与的计算速度更快,于是优化成hashcode&(length-1)和之前%的结果一样,再后来利用(2^n)-1 的二进制数每一位都是1的特性,&((2^n)-1)每次都会得到不同的值,可以有效减少hash碰撞。所以就设定长度为2的n次方了。一般用什么作为key
一般使用Integer、String这种不可变的类当HashMap的key,String类比较常用。
因为string类型的值在创建时的hashcode就被缓存了,不需要重新计算
获取对象时要用到equals()和hashCode()方法,而string、integer这些都已经重写了这两个方法,不需要额外重写。
为什么线程不安全
jdk1.7使用头插法,在多线程环境下,扩容可能导致环形链表的出现,形成死循环。
-
HashMap和HashTable区别
HashTable是jdk1遗留下来的类,在jdk1.5以后提供了ConcurrentHashMap替代它;HashMap是后来增加的
- HashTable线程安全,HashMap线程不安全
- HashTable的key不允许为null,HashMap可以有一个null,放在下标为0的头节点的链表中。
- HashTable很多方法是同步方法,在单线程环境下比HashMap慢
- HashTable直接使用对象的HashCode,HashMap重新计算hash值