Collection接口?
List、Set、Queue、Stack都继承自Collection接口。
Set接口,该接口有两个实现类:HashSet和TreeSet(有序)。
List接口,该接口的实现类:LinkedList、ArrayList、Vector
List和Set的区别?
- List是有序的,即存储和取出的顺序一致;元素可重复;可通过索引值操作元素
- Set是无序的,元素是唯一的
ArrayList、Vector、LinkedList有什么区别?
ArrayList、Vector、LinkedList类均在java.util包里,均为可伸缩数组,即可以动态改变长度的数组。
ArrayList、Vector都是数组实现的,支持用下标来访问元素,索引数据的速度比较快,但是对数据的插入操作执行得比较慢。都有一个初始化的容量的大小,元素超过这个大小时就需要动态扩充它们的存储空间。
ArrayList、Vector最大的区别就是同步的使用,Vector的绝大多数方法都是同步的,所以Vector是线程安全的,ArrayList是线程不安全的。就效率而言,ArrayList比较高。
LinkedList采用双向列表来实现的,从列表头开始遍历,访问效率比较低,但是插入效率较高。是非线程安全的容器。
选择:对数据主要是索引操作,选用ArrayList.插入删除操作较多,选择LinkedList.多线程中使用容器时,使用Vector较为安全。
总结:ArrayList、Vector底层是数组,查询快,增删慢。
ArrayList线程不安全,效率高;Vector线程安全,效率低
LinkedList底层是链表,查询慢,增删快。
LinkedList线程不安全,效率高
HashSet和TreeSet的区别?
HashSet的底层是哈希表,将元素保存到map的key中。如果希望保证元素的唯一性,需要重写对象的hashCode和equals方法
TreeSet的底层是二叉树,元素是按照一定顺序进行排序的。元素排序方式有两种:第一种是让对象所属的类去继承comparable接口,并且重写equals、compareTo、hashCode方法。第二种是自定义比较器,实现comparator接口。在两种排序方式都存在时,第二种方式优先
集合之Map
保存具有映射关系的一对数据,即Key-Value
Key不可重复,通过Set来组织的
Vlaue可重复,通过Collection存储的
Map接口,该接口的实现类:HashMap、TreeMap、LinkedHashMap、WeakHashMap、IdentityHashMap.
HashMap的实现方式?
HashMap在Java8前是由数组和链表组成的,数组长度默认是16,数组每个元素存储的是链表的头结点。通过哈希函数(类似hash(key.hashCode())%len取模的方式)来计算要添加的元素存放的数组的位置,将要存放的元素插入到该数组元素对应的链表中。
该方式存在的问题是,可能每一次取模后都是同一个值,那么某个链表长度就会非常的长,复杂度最好是O (1),最坏是O (n)
所以Java8以及之后,采用的是数组+链表+红黑树,这样性能从O (n)提高到O (logn)。当TREEIFY_THRESHOLD变量超过8时,即链表长度超过8,将该链表转换为红黑树,当UNTREEIFY_THRESHOLD变量小于6时,即链表长度小于6,将红黑树转换为链表。
HashMap是延迟创建的,即插入元素时,才进行构造
HashMap中put方法的逻辑:
- 如果HashMap未被初始化过,则初始化
- 对Key求Hash值,计算对应的数组下标
- 如果该数组元素还没有值,即不存在链表头指针,则直接将该节点放入数组中
- 如果该数组元素中存在值,以链表的方式链接到链表后面
- 如果链表长度超过阙值,就将链表转成红黑树
- 如果链表长度低于6,将红黑树转回链表
- 如果节点key值已经存在,就替换原节点的value值
- 如果数组满了(容量*加载因子0.75),就需要resize(扩容成原来的2倍后重排)
除了将链表转换成红黑树外,HashMap还通过以下的方式减少碰撞,即让计算出的hash值尽可能不一样:
- 扰动函数:促使元素位置分布均匀,减少碰撞几率
- 使用final对象,并采用合适的equals和hashCode方法,比如使用String对象作为key.final是为了防止key的改变
HashMap计算hash值的方式:key的hashcode值与该值移位16位的值做亦或
HashMap扩容存在的问题?
扩容方式是当数组的大小超过容量*加载因子0.75时,数组进行扩容两倍,扩容的方式是新建一个大小为原来两倍的数组,将数组元素复制过去。
问题1:多线程环境下,如果两个线程同时发现数组需要扩容,那么就会存在条件竞争,容易造成死锁
问题2:扩容是一个比较耗时的过程
HashMap本身不是线程安全的,想要将其变成线程安全的:
Map hashMap = new HashMap();
Map safeHashMap = Collections.synchronizedMap(hashMap);
Hashtable简介
Hashtable与HashMap都是基于Hash实现的,跟HashMap不同的是,Hashtable在每个公有方法前都加了synchronized,所以Hashtable是线程安全的。
ConccurentHashMap的实现原理
无论是Hashtable还是由synchronizedMap方法包装的hashMap,都是线程安全的。多线程并发时,需要竞争同一把锁,效率极低。为了提升在多线程下的执行性能,ConccurentHashMap应运而生。
早期的ConccurentHashMap,通过分段锁Segment来实现,默认配置16把锁。相比于Hashtable来说,效率高了16倍。其做法是:将hashmap的数组逻辑上拆分成几个子数组,每个子数组配置一把锁,线程在获取到某把分段锁的时候,才能操作这个子数组,其他线程想要操作这个子数组,则被阻塞。但若是其他线程想要操作别的未被占用的分段锁下的子数组,那么就可以进行操作。
Java8之后,ConcurrentHashMap采用CAS+synchronized使锁更细化。即为hashMap的每一个数组元素都加上一把锁,只要key的hash值不冲突,那么锁就不会产生冲突。同时像HashMap一样,将链表和红黑树进行相互转换,以提升效率。
ConcurrentHashMap插入节点(put)的逻辑
- 判断数组是否初始化,没有则进行初始化操作
- 通过hash定位数组的索引坐标,判断该数组坐标上是否有Node节点,如果没有则使用CAS进行添加(链表的头节点),添加失败则进入下一次循环,继续尝试添加
- 检查内部是否正在扩容,如果正在扩容就帮助它一块扩容
- 如果定位到的数组位置对应的元素不为空,那么就是用synchronized锁住该数组元素(即对应位置链表或者红黑树的头节点),并进行元素添加操作
- 判断链表长度是否达到临界值8,当长度超过这个值,将链表转换为红黑树
总结:
- 首先使用无锁操作CAS插入头节点,如果别的线程正在插入节点,可能会失败,则重试
- 若头节点存在,则尝试获取头节点的同步锁,再进行操作
HashMap、Hashtable、ConccurentHashMap的区别?
HashMap是线程不安全的,底层是通过数组+链表+红黑树来实现的。
Hashtable是线程安全的,底层是通过数组+链表来实现的,实现线程安全的方式是在修改数组时,锁住整个对象,效率较低
ConccurentHashMap是线程安全的,通过将锁细粒度到数组的每一个元素来提升并发性能。底层是通过数组+链表+红黑树来实现的。
HashMap的key/value均可为null,而其他的两个类不支持
HashMap、Hashtable、TreeMap、WeakHashMap有哪些区别?
HashMap、Hashtable都是基于Hash实现的,存入和取出是随机的,Hashtable是线程安全的,HashMap是线程不安全的。就效率而言,HashMap高于Hashtable。HashMap允许key为null,Hashtable不允许。
TreeMap能够把它保存的记录按照键排序。
LinkedHashMap是HashMap的一个子类,key值是顺序存储的。
WeakHashMap与HashMap类似,不同在于WeakHashMap中key采用的是“弱引用”,只要WeakHashMap中的key不再被外部引用,它就可以被垃圾回收器回收。HashMap中key采用的是“弱引用”,只要WeakHashMap中的key不再被外部引用,它就可以被垃圾回收器回收。
HashMap的底层如何判断两个key是否相同?
主要用到了Object类的两个方法:hashCode()-返回对象存储的内存地址
equals()判断两个对象是否相等
在向HashMap中添加键值对<key,value>时,经过以下几个过程:
调用key的hasCode()方法生成一个hash值h1,如果h1不存在于hashMap中,那么直接将<key,value>添加到HashMap中。
如果h1存在,那么找出HashMap中所有hash值为h1的key,然后分别使用key的equals方法进行判断。如果相等,则替代该key的value值,如果都不存在,则新增一个键值对。
基于此,我们在使用自定义对象作为HashMap的key时,一定要重写对象的hashCode()方法和equals()方法,否则就会导致一个HashMap中存了两个key值相等的对象(因为对象的地址不同)。
什么是迭代器?
迭代器是一个对象,它的工作是遍历并选择序列中的对象
使用:
使用容器的iterator()方法返回一个Iterator
使用Iterator的hasNext()方法判断容器是否还有元素,如果有,可以使用next()方法获取下一个元素。
可以通过remove()方法删除迭代器返回的元素。
for(Iterator<String> iter=ll.iterator(); iter.hasNext();)
{
String str = (String)iter.next();
System.out.println(str);
}
使用iterator时经常会遇到ConcurrentModificationException异常,原因在于使用Iterator遍历容器的同时又对容器做增加或删除操作所导致的。当调用容器的iterator方法时,会将容器个数赋值给变量exceptedModCount,当调用next方法时,会比较列表实际元素个数和该变量的值,不相等则会抛出这个异常。
解决方式:在遍历过程中将要删除的元素保存到一个集合中,等遍历结束后再调用removeAll()方法来删除,或者使用iter.move()方法。
Collection和Collections的区别?
Collection是一个集合接口,提供了对集合对象进行基本操作的通用接口方法。比如,List和Set就是实现这个接口。
Collections类似于一个工具类,不能实例化,提供了一系列静态方法以实现对各种集合的搜索、排序、线程安全化等操作。比如,Collections.sort(list).