集合可以看作是一种容器,用来存储对象信息。所有集合类都位于java.util包下.支持多线程的集合类位于java.util.concurrent包下.
常用集合框架:List , Set, Map。
一、List :继承自Collection。可以存在相同的对象(重复),有序的。具体实现类有ArrayList,LinkedList,Vector。
ArrayList: ArrayList 实现于 List、RandomAccess 接口,具有list的特性,有序,可以重复,并且可以插入空数据,也支持随机访问。ArrayList相当于动态数据(动态数组),其中最重要的两个属性分别是: elementData 数组,以及 size 大小,ArrayList 的主要消耗是数组扩容来在指定位置添加数据。增删是数组复制的过程,效率比较慢,但查询比较快。
LinkedList: LinkedList 底层是基于双向链表实现的,也是实现了 List 接口,所以也拥有 List 的一些特点, 可见每次插入都是移动指针,和 ArrayList 的拷贝数组来说效率要高上不少。
LinkedList 插入,删除都是移动指针效率很高。
查找需要进行遍历查询,效率较低。
Vector: Vector 也是实现于 List 接口,底层数据结构和 ArrayList 类似,也是一个动态数组存放数据。不过是在 add() 方法的时候使用 synchronized 进行同步写数据,但是开销较大,所以 Vector 是一个同步容器并不是一个并发容器。
总结:List 有序,可重复
ArrayList:
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程不安全,效率高
LinkedList:
优点: 底层数据结构是链表,查询慢,增删快。
缺点: 线程不安全,效率高
Vector:
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程安全,效率低
二、Set:继承自Collection。不能存在相同的对象(唯一),无序的。具体实现类有HashSet,LinkedHashSet,TreeSet。
HashSet: 实现Set接口。底层数据结构采用哈希表实现,元素无序且唯一,线程不安全,效率高,可以存储null元素,元素的唯一性是靠所存储元素类型是否重写hashCode()和equals()方法来保证的。Hashset不是线程同步的,如果多线程操作HashSet集合,则应通过代码来保证其同步。
如何来保证元素唯一性?具体实现比较唯一过程?
存储元素时首先会使用hash()算法函数生成一个int类型hashCode散列值,然后跟
已经存储的hashCode值比较,如果hashCode不相等,肯定是不同的对象。
如果hashCode值相同,再比较equals方法。
如果equals也相同,则对象相同,不需要存储。
LinkedHashSet: LinkedHashSet是HashSet的一个子类,具有HashSet的特性,也是根据元素的hashCode值来决定元素的存储位置。LinkedHashSet底层数据结构采用链表和哈希表共同实现,链表保证了元素的顺序与存储顺序一致,哈希表保证了元素的唯一性。线程不安全,效率高。
TreeSet:底层数据结构采用红黑树来实现,元素唯一且已经排好序;唯一性同样需要重写hashCode和equals()方法,二叉树结构保证了元素的有序性。根据构造方法不同,分为自然排序(无参构造)和比较器排序(有参构造),自然排序要求元素必须实现Compareable接口,并重写里面的compareTo()方法,元素通过比较返回的int值来判断排序序列,返回0说明两个对象相同,不需要存储;比较器排需要在TreeSet初始化是时候传入一个实现Comparator接口的比较器对象,或者采用匿名内部类的方式new一个Comparator对象,重写里面的compare()方法;默认自然排序。
总结:Set 无序,唯一
HashSet:
优点: 底层数据结构是哈希表。可存储null元素。通过hashCode()和equals()方法保证唯一
缺点: 线程不安全,效率高
LinkedHashSet:
优点: 底层数据结构是链表和哈希表。由链表保证插入的元素有序;由哈希表保证元素唯一
缺点: 线程不安全,效率高
TreeSet:
优点: 底层数据结构是红黑树,自平衡的排序二叉树。通过自然排序和比较器排序保证元素顺序。根据比较的返回值是否是0来决定元素唯一性。
缺点: 线程不安全,效率高
Collection集合的方法:
适用场景分析:
三、Map: Map用于保存具有映射关系的数据,以键值对的形式存放对象。key-value。它们都可以使任何引用类型的数据,key不能重复。可以通过指定的key取出对应的value。具体实现类有HashMap、LinkedHashMap、TreeMap和HashTable。
HashMap: HashMap 是一个我们最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度。
HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null;
HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。
HashMap基于哈希表结构实现的 ,当一个对象被当作键时,必须重写hasCode和equals方法。
HashMap工作原理:
HashMap基于hashing原理,通过put()和get()方法存储和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashCode值,然后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会存储在链表的下一个节点中。
HashMap实现原理:
HashMap 底层是基于数组和链表(或红黑树)实现的,jdk8以后把链表改为红黑树。
数组的特点:查询效率高,插入,删除效率低。
链表的特点:查询效率低,插入删除效率高。
在HashMap底层使用数组加(链表或红黑树)的结构完美的解决了数组和链表的问题,使得查询和插入,删除的效率都很高。
因为链表中元素太多的时候会影响查找效率,所以当链表的元素个数达到8的时候使用链表存储就转变成了使用红黑树存储,原因就是红黑树是平衡二叉树,在查找性能方面比链表要高.
HashMap中的两个重要的参数:HashMap中有两个重要的参数:初始容量大小和加载因子(默认0.75),初始容量大小是创建时给数组分配的容量大小,默认值为16,用数组容量大小乘以加载因子得到一个值,一旦数组中存储的元素个数超过该值就会调用rehash方法将数组容量增加到原来的两倍,专业术语叫做扩容。
LinkedHashMap: LinkedHashMap继承自HashMap,它主要是用链表实现来扩展HashMap类,HashMap中条目是没有顺序的,但是在LinkedHashMap中元素既可以按照它们插入时的顺序排序,也可以按它们最后一次被访问的顺序排序。
TreeMap: TreeMap基于红黑树数据结构的实现,键值可以使用Comparable或Comparator接口来排序。TreeMap继承自AbstractMap,同时实现了接口NavigableMap,而接口NavigableMap则继承自SortedMap。SortedMap是Map的子接口,使用它可以确保条目是排好序的。
TreeMap有两种排序方式:
♦ 自然排序:TreeMap的所有key必须实现Comparable接口,而且所有的key应该是同一个类的对象,否则会抛出ClassCastException。
♦ 定制排序:创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中的所有key进行排序。
在实际使用中,如果更新时不需要保持元素的顺序,就使用HashMap,如果需要保持元素的插入顺序或者访问顺序,就使用LinkedHashMap,如果需要按照键值排序,就使用TreeMap。
HashTable:Hashtable是一个散列表,存储的内容是键值对映射,不同之处在于,Hashtable是继承自Dictionary的,Hashtable中的函数都是同步的,这意味着它也是线程安全的,另外,Hashtable中key和value都不可以为null。
遍历Map的四种方式
四、常见集合面试题
Arraylist 与 LinkedList 区别?
ArrayList 和 LinkedList 都是不同步的,非线程安全;
Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是双向链表数据结构。
ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响。
LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index) 方法)。
对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
HashMap 和 Hashtable 的区别?
HashMap 是非线程安全的,HashTable 是线程安全的;
HashMap 要比 HashTable 效率高一点。
HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 HashTable 中不允许null键值对。
创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。
JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
ConcurrentHashMap和Hashtable的区别?
底层数据结构:JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
实现线程安全的方式:①在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。HashMap 没有考虑同步,HashTable 考虑了同步的问题。但是 HashTable 在每次同步执行时都要锁住整个结构。ConcurrentHashMap 锁的方式是稍微细粒度的。