Iterator接口,这是一个用于遍历集合中元素的接口,主要包含hashNext(),next(),remove()三种方法。它的一个子接口LinkedIterator在它的基础上又添加了三种方法,分别是add(),previous(),hasPrevious()。也就是说若是是先Iterator接口,那么在遍历集合中元素的时候,只能日后遍历,被遍历后的元素不会在遍历到,一般无序集合实现的都是这个接口,好比HashSet,HashMap;而那些元素有序的集合,实现的通常都是LinkedIterator接口,实现这个接口的集合能够双向遍历,既能够经过next()访问下一个元素,又能够经过previous()访问前一个元素,好比ArrayList。java
List是元素有序而且能够重复的集合。
List的主要实现:ArrayList, LinkedList, Vector。算法
ArrayList | LinkedList | Vector | |
---|---|---|---|
底层实现 | 数组 | 双向循环链表 | 数组 |
同步性及效率 | 不一样步,非线程安全,效率高 | 不一样步,非线程安全,效率高 | 同步,线程安全,效率低 |
特色 | 查询快,增删慢 | 查询慢,增删快 | 查询快,增删慢 |
默认容量 | 10 | / | 10 |
扩容机制 | int newCapacity = oldCapacity + (oldCapacity >> 1); //1.5 倍 | / | 2 倍 |
总结:数组
- ArrayList 和 Vector 基于数组实现,对于随机访问get和set,ArrayList优于LinkedList,由于LinkedList要移动指针。
Set集合中的对象不按特定的方式排序(存入和取出的顺序不必定一致),而且没有重复对象。
Set的主要实现类:HashSet, TreeSet。安全
HashSet | TreeSet | LinkedHashSet | |
---|---|---|---|
底层实现 | HashMap | 红黑树 | LinkedHashMap |
重复性 | 不容许重复 | 不容许重复 | 不容许重复 |
有/无序 | 无序 | 有序,支持两种排序方式,天然排序和定制排序,其中天然排序为默认的排序方式。 | 有序,以元素插入的顺序来维护集合的连接表 |
时间复杂度 | add(),remove(),contains()方法的时间复杂度是O(1) | add(),remove(),contains()方法的时间复杂度是O(logn) | LinkedHashSet在迭代访问Set中的所有元素时,性能比HashSet好,可是插入时性能稍微逊色于HashSet,时间复杂度是 O(1)。 |
同步性 | 不一样步,线程不安全 | 不一样步,线程不安全 | 不一样步,线程不安全 |
null值 | 容许null值 | 不支持null值,会抛出 java.lang.NullPointerException 异常。由于TreeSet应用 compareTo() 方法于各个元素来比较他们,当比较null值时会抛出 NullPointerException异常。 | 容许null值 |
比较 | equals() | compareTo() | equals() |
当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其余加入的对象的hashcode值做比较,若是没有相符的hashcode,HashSet会假设对象没有重复出现。可是若是发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。若是二者相同,HashSet就不会让加入操做成功。
hashCode()与equals()的相关规定:数据结构
总结:
HashSet是一个通用功能的Set,而LinkedHashSet 提供元素插入顺序保证,TreeSet是一个SortedSet实现,由Comparator 或者 Comparable指定的元素顺序存储元素。多线程
Map 是一种把键对象和值对象映射的集合,它的每个元素都包含一对键对象和值对象。 Map没有继承于Collection接口从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
Map 的经常使用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap并发
HashMap | HashTable | ||
---|---|---|---|
底层实现 | 数组+链表 | 数组+链表 | |
同步性 | 线程不一样步 | 同步 | |
null值 | 容许 key 和 Vale 是 null,可是只容许一个 key 为 null,且这个元素存放在哈希表 0 角标位置 | 不容许key、value 是 null | |
hash | 使用hash(Object key)扰动函数对 key 的 hashCode 进行扰动后做为 hash 值 | 直接使用 key 的 hashCode() 返回值做为 hash 值 | |
容量 | 容量为 2^4 且容量必定是 2^n | 默认容量是11,不必定是 2^n | |
扩容 | 两倍,且哈希桶的下标使用 &运算代替了取模 | 2倍+1,取哈希桶下标是直接用模运算 |
1. HashMap 的工做原理?
经过hash的方法,经过put和get存储和获取对象。存储对象时,咱们将K/V传给put方法时,它调用hashCode计算hash从而获得bucket位置,进一步存储,HashMap会根据当前bucket的占用状况自动调整容量(超过Load Facotr则resize为原来的2倍)。获取对象时,咱们将K传给get,它调用hashCode计算hash从而获得bucket位置,并进一步调用equals()方法肯定键值对。若是发生碰撞的时候,Hashmap经过链表将产生碰撞冲突的元素组织起来,在Java 8中,若是一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提升效率。
2.get和put的原理吗?equals()和hashCode()的都有什么做用?
经过对key的hashCode()进行hashing,并计算下标( n-1 & hash),从而得到buckets的位置。若是产生碰撞,则利用key.equals()方法去链表或树中去查找对应的节点
3. HashMap 的长度为何是2的幂次方?
为了能让 HashMap 存取高效,尽可能较少碰撞,也就是要尽可能把数据分配均匀,每一个链表/红黑树长度大体相同。这个实现就是把数据存到哪一个链表/红黑树中的算法。框架
HashMap实现了Map接口,不保障元素顺序。
TreeMap实现了SortedMap接口,是一个有序的Map。内部采用红黑树实现,红黑树是一种维护有序数据的高效数据结构函数
ConcurrentHashMap 和 Hashtable 的区别主要体如今实现线程安全的方式上不一样。
底层数据结构: JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构同样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 以前的 HashMap 的底层数据结构相似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
实现线程安全的方式(重要): ① 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不一样数据段的数据,就不会存在锁竞争,提升并发访问率。(默认分配16个Segment,比Hashtable效率提升16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操做。(JDK1.6之后 对 synchronized锁作了不少优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,可是已经简化了属性,只是为了兼容旧版本;② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率很是低下。当一个线程访问同步方法时,其余线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另外一个线程不能使用 put 添加元素,也不能使用 get,竞争会愈来愈激烈效率越低。
高并发
JDK1.8 以前 HashMap 由 数组+链表 组成的(“链表散列” 即数组和链表的结合体),数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(HashMap 采用 “拉链法也就是链地址法” 解决冲突),若是定位到的数组位置不含链表(当前 entry 的 next 指向 null ),那么对于查找,添加等操做很快,仅需一次寻址便可;若是定位到的数组包含链表,对于添加操做,其时间复杂度依然为 O(1),由于最新的 Entry 会插入链表头部,急须要简单改变引用链便可,而对于查找操做来说,此时就须要遍历链表,而后经过 key 对象的 equals 方法逐一比对查找.所谓 “拉链法” 就是将链表和数组相结合。也就是说建立一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中便可。
相比于以前的版本, JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减小搜索时间。
TreeMap、TreeSet以及JDK1.8以后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,由于二叉查找树在某些状况下会退化成一个线性结构。
在jdk1.8以前,首先将数据分为一段一段的存储,而后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其余段的数据也能被其余线程访问。
ConcurrentHashMap 是由 Segment 数组结构和 HahEntry 数组结构组成。
Segment 实现了 ReentrantLock,因此 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。
static class Segment<K,V> extends ReentrantLock implements Serializable { } 复制代码
一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap相似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每一个 HashEntry 是一个链表结构的元素,每一个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先得到对应的 Segment的锁。
在jdk1.8时,ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构相似,数组+链表/红黑二叉树。
synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提高N倍。