ArrayList和LinkList
ArrayList和LinkList都是实现了List的接口类。
ArrayList在末尾添加和删除的时间是一样的,中间部分的增删所需时间大大增加。根据索引查找元素的速度大大加快。
LinkList插入和删除集合中在任何位置元素花费的时间都是一样的,根据索引查找一个元素的时间较长。
原因是ArrayList的实现是基于数组,LinkList的实现是基于链表,对于随机访问的get set方法 ,ArrayList是要优于LinkList的,因为LinkList要移动指针。对于新增和删除操作。add remove,LinkList比较占优势,因为ArrayList要移动数据。、
HashMap
HashMap是数组和链表的结合体,在java1.8当链表节点较少时仍以链表的形式存在,大于8时会转化为红黑树。在储存空间中以键值对的形式存在。
线程安全
HashMap是线程不安全的。通过Collections.synchronizedMap(hashMap)可以让HashMap同步。synchronizedMap方法返回一个SynchronziedMap类的对象,而在synchronzied使用了synchronzied来保证对map的操作时线程安全的。
可以使用HashTable代替HashMap,HashTable是线程安全的,HashTable当一个线程访问同步方法时,会被阻塞住,不但不可以使用put方法,连get方法也不可以,效率很低,基本上不会使用。也可以使用jdk1.5之后的ConcurrentHashMap代替HashTable,他的同步性更好,他可以根据同步级别对map一部分上锁,但是HashTable提供更强的线程安全性。
为什么HashMap是线程不安全的
当多个线程同时使用put方法添加元素的时候,正好有两个put的key发生了碰撞,那么根据HashMap的实现,这两个键都会添加到bucket数组的同一个位置,这样会使一个线程的put数据被覆盖掉。
如果两个线程同时发现元素个数超过了负载因子,这样多个线程就会同时对hash数组进行扩容,都在重新计算元素位置,以及复制数据,但是最终只有一个线程扩容后的数组会赋给table,其他线程都会丢失,各自线程put的数据也会丢失,并且会引起死循环。
碰撞问题
两个键值得hashcode相同,就会发生碰撞。HashMap使用链表来存储对象,hashcode相同bucket就相同。使用不可变的,声明为final的对象,采用合适的equals和hashcode方法将会减少碰撞的发生,提高效率。
两个键的hashcode相同,如何获取值对象。当调用get方法,HashMap会使用键对象的hashcode找到bucket的位置,再调用keys.equals方法找到链表中正确的节点
为什么String,Integer这种封装类适合作为键,String是不可变的也是final的,而且已经重写了hashcode和equals,要计算hash
code就要防止键值改变,如果键在放入时和获取时返回不同的hashcode,那么就不能从HashMap找到想要的对象。而且String也是线程安全的。
扩容问题
HashMap超过了负载因子定义的长度就会进行扩容。默认的负载因子大小为0.75,当容量超过原容量大小 * 负载因子时将会创建原来HashMap大小两倍的bucket数组来重新调整map大小,并将原来的对象放入新的bucket数组中,这个过程叫做rehashing,因为他调用hash方法找到新的bucket位置。
扩容是一个相当耗时的操作,他需要重新计算这些元素在新的数组中位置并进行复制处理,在定义HashMap时最好提前预告元素个数,有助于提高性能。默认容量是16。
HashSet
HashSet实现了Set接口,不允许集合中存在重复值,对象判断是否重复是通过equals 和 hashcode。保存对象之前如果没有重写hashcode和equals方法,在对比的时候回调用默认的实现。
HashSet使用对象计算hashcode值,HashMap使用键对象计算hashcode值。
TreeMap
TreeMap是有顺序的,HashMap的储存是以键值对的形式存在的,允许建和值为null。
HashTable
HashTable的初始大小和HashMap不一样是因为HashTable的扩容方法是乘2加1,保证了容量是奇数。