实线边框的是实现类,比如ArrayList,LinkedList,HashMap等

折线边框的是抽象类,比如AbstractCollection,AbstractList,AbstractMap等,

点线边框的是接口,比如Collection,Iterator,List等。

上述所有的集合类,都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要包含hashNext(),next(),remove()三种方法。它的一个子接口LinkedIterator在它的基础上又添加了三种方法,分别是add(),previous(),hasPrevious()。也就是说如果实现Iterator接口,那么在遍历集合中元素的时候,只能往后遍历,被遍历后的元素不会再遍历到,通常无序集合实现的都是这个接口,比如HashSet,HashMap;而那些元素有序的集合,实现的一般都是LinkedIterator接口,实现这个接口的集合可以双向遍历,既可以通过next()访问下一个元素,又可以通过previous()访问前一个元素,比如ArrayList。

1、ArrayList、LinkedList、Vector

List都是有序的:后面add的成员自动添加到末尾;ArrayList是直接加到数组的下一个index处;LinkedList添加到链表的下一个节点中。

List允许元素重复:根据value进行remove的时候,只删除首次找到的对象(允许add多个null元素,操作逻辑相同);

List动态调整大小的秘密:

ArrayList在每次增加元素(可能是1个,也可能是一组)时,都要调用ensureCapacity()来确保足够的容量。当容量不足以容纳当前的元素个数时,就设置新的容量为旧的容量的1.5倍加1,如果设置后的新容量还不够,则直接新容量设置为传入的参数(也就是所需的容量),而后用Arrays.copyof()方法将元素拷贝到新的数组。从中可以看出,当容量不够时,每次增加元素,都要将原来的元素拷贝到一个新的数组中,非常耗时。拷贝的时候,保证了老元素的下标不变化,否则调整了一次大小,数据的位置都变化了,那还了得?

ArrayList的默认容量是10,但是不能直接用,因为内部数组维护了一个size,只有调用了add,或者初始化的时候直接传了一个array过来的情况下,size才会变化;其他操作例如get会先判断size是否超出边界size,超出就会报错。

Linked不需要扩容动作。

ArrayList调整大小的时候,大量使用System.arraycopy()方法。该方法被标记了native,调用了系统的C/C++代码,在JDK中是看不到的,该函数实际上最终调用了C语言的memmove()函数,因此它可以保证同一个数组内元素的正确复制和移动,比一般的复制方法的实现效率要高很多,很适合用来批量处理数组。Java强烈推荐在复制大量数组元素时用该方法,以取得更高的效率。

ArrayList也采用了快速失败的机制:有一个modCount记录修改的次数,一旦发现有多线程试图改变list结构,则立即报错。

(1)、ArrayList是实现了基于动态数组的数据结构,LinkedList基于双向链表的数据结构(头结点不存储数据)。对于随机访问get和set,ArrayList直接通过下标访问,LinkedList则要从链表头部依次移动指针。这种情况下ArrayList完胜。

(2)、当需要插入数据的时候,如果是在集合的前段(大概集合容量的前1/10)处插入数据时,linkedlist性能明显优于arraylist;当在集合的中部甚至靠后的位置插入大量数据时,arraylist的性能反而远远优于linkedlist。原因推测是arrayList会在末尾预留空间,如果有数据从末尾插入,直接写到数组里面去就好了,不需要其他特殊的操作。

(3)、LinkedList还实现类栈和队列的操作方法,因此也可以作为栈、队列和双端队列来使用。

(4)、Vector现在用的比较少了,其实现类似ArrayList,不同点在于Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。两个都是采用的线性连续空间存储元素,但是当空间不足的时候,两个类的增加方式是不同的,Vector增加原来空间的一倍,ArrayList增加原来空间的50%

http://pengcqu.iteye.com/blog/502676#bc2374415

2、HashSet、TreeSet、LinkedHashSet

HashSet无序不可重复,允许使用null

HashSet是如何实现不重复的?

HashSet底层是基于HashMap实现的。该元素作为key放入HashMap,value设置一个虚拟的Object。
由于HashMap的put()方法添加key-value对时,当新放入HashMap的Entry中key与集合中原有Entry的key相同(hashCode()返回值相等,通过equals比较也返回true), 新添加的Entry的value会将覆盖原来Entry的value,但key不会有任何改变, 因此如果向HashSet中添加一个已经存在的元素时,新添加的集合元素将不会被放入HashMap中,原来的元素也不会有任何改变,这也就满足了Set中元素不重复的特性。

TreeSet比HashSet有所改进,是一个有序的集合(默认使用红黑树完成排序)。这里排序是通过元素比较为依据,不是插入的顺序。

LinkedHashSet在HashSet的基础上又形成了双向链表,可以记住插入的顺序

3、HashMap、HashTable、TreeMap、LinkedHashMap

(1)、HashMap原理

实际上是一个数组和单向链表的结合体,通过key值得到一个hashcode,计算出作为数组的下标,能够快速定位到数组槽位。

如果多个不同的key值计算出了相同的hashcode,就会有多个value定位到数组的同一个位置,这就是所谓的冲突。这种情况下,这多个value会组成链表,访问效率就会降低。因此为了提高效率,key值计算出来的hashcode要尽量的分散。
下面是一个HashMap的数据结构图:

上面提到了根据key值计算hashcode,这个计算过程叫做Hash算法,一般通用的做法是key值对数组的长度取模,当然还有很多其他精妙的算法,不赘述。

当HashMap中的元素越来越多的时候,冲突的几率也就越来越高(因为数组的长度是固定的),所以为了提高查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,所以这是一个通用的操作,比较消耗性能。

(2)、HashMap特性

HashMap键必须唯一,值可以重复,没有顺序;键值可以为null,null键值只有一个,放到table[0]链表里面;

HashMap其实就是一个Entry数组,Entry对象中包含了键和值,其中next也是一个Entry对象,它就是用来处理hash冲突的,形成一个链表,既然是数组,就有扩容的问题。

对hashmap进行equal操作,判断的原则是key、value都分别相等才行;

HashMap的key必须唯一,如果key相等,put的时候,会把旧值覆盖掉;

HashMap的put方法:每次put的时候,会把新的键值对插入到单链表的头结点位置。

   // 将“key-value”添加到HashMap中
public V put(K key, V value) {
// 若“key为null”,则将该键值对添加到table[0]中。
if (key == null)
return putForNullKey(value);
// 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出!
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
} // 若“该key”对应的键值对不存在,则将“key-value”添加到table中
modCount++;
//将key-value添加到table[i]处
addEntry(hash, key, value, i);//插入到单链表的头结点位置
return null;
}

hashmap是通过hash值定位数组的,定位方法为hash&(size-1),这里size为数组大小,为了能够充分利用空间,这里的size须为偶数,实际hashmap中,数组的大小定义为capacity为大于initialCapacity的最小的2的n次幂;

当Hashmap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后根据hash值重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效的提高hashmap的性能;

在resize的过程中,Entity<K,V>在数组中的下标会发生变化,原来在一个链表中的元素,调整后不一定在一个链表中(因为hash值重新计算了);在同一个链表中的元素次序会反过来,因为链表插入是从头部插入的,早插入的会逐渐移动到末尾,这是为了避免尾部遍历(tail traversing)。

HashMap的扩容动作:

// 重新调整HashMap的大小,newCapacity是调整后的容量
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
//如果就容量已经达到了最大值,则不能再扩容,直接返回
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
} // 新建一个HashMap,将“旧HashMap”的全部元素添加到“新HashMap”中,
// 然后,将“新HashMap”赋值给“旧HashMap”。
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
} // 将HashMap中的全部元素都添加到newTable中
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j]; //每一个单向链表
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);//计算新的下标,每一个成员都可能计算出与原来不同的下标i
e.next = newTable[i];//注意这三句,把新的e放到链表的前面
newTable[i] = e;
e = next;
} while (e != null);
}
}
}

HashMap的get方法

 // 获取key对应的value
public V get(Object key) {
if (key == null)
return getForNullKey();
// 获取key的hash值
int hash = hash(key.hashCode());
// 在“该hash值对应的链表”上查找“键值等于key”的元素
for (Entry<K,V> e = table[indexFor(hash, table.length)];  e != null; e = e.next) {
            Object k;
//判断key是否相同,用==或者equals
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
//没找到则返回null
return null;
}

(3)、HashMap和HashTable的区别

HashMap是非synchronized,而Hashtable使用synchronized来保证线程安全,多个线程可以共享一个Hashtable,但在线程竞争激烈的情况下HashTable的效率非常低下;而如果没有正确的同步的话,多个线程是不能共享HashMap的;

另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别;

HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行

(4)、TreeMap在HashMap的基础上有所改进,通过比较键值来实现排序,是一个有序的键值对。

(5)、LinkedHashMap在HashSet的基础上又形成了双向链表,用来记住插入的顺序。

另外,LinkedHashMap可以按照访问顺序,对映射条目进行调整,使得每次被访问的条目都会自动移动到双向链表的尾部,如果要使用这种功能,请使用LinkedHashMap<K,V>(initialCapacity,loadFactor,true)

4、包装器synchronizedMap 和 synchronizedList、synchronizedSet

这三个包装器,将非线程安全的Map和List转换为线程·安全的。

Map m = Collections.synchronizedMap(new HashMap());

不过用这种方法只能实现有条件地线程安全――所有单个的操作(put和get)都是线程安全的,但是多个操作组成的操作序列却可能导致数据争用,而引发多线程故障。

   Map m = Collections.synchronizedMap(new HashMap());
List l = Collections.synchronizedList(new ArrayList());
// put-if-absent idiom -- contains a race condition
// may require external synchronization,下面这两句在多线程操作中并不同步
if (!map.containsKey(key))
map.put(key, value);
// ad-hoc iteration -- contains race conditions
// may require external synchronization这种size的操作的结果可能在遍历过程中发生变化
for (int i=0; i<list.size(); i++) {
doSomething(list.get(i));
}
// normal iteration -- can throw ConcurrentModificationException
// may require external synchronization
for (Iterator i=list.iterator(); i.hasNext(); ) {
doSomething(i.next());
}

5、ConcurrentHashMap

Hashtable 和 synchronizedMap 所采取的获得同步的简单方法有两个主要的不足。首先,这种方法对于可伸缩性是一种障碍,因为一次只能有一个线程可以访问 hash 表,严重影响效率。同时,这样仍不足以提供真正的线程安全性,许多公用的混合操作仍然需要额外的同步。虽然诸如 get() 和 put() 之类的简单操作可以在不需要额外同步的情况下安全地完成,但还是有一些公用的操作序列,例如迭代或者 put-if-absent(空则放入),需要外部的同步,以避免数据争用。

Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。数据结构 ConcurrentHashMap的目标是实现支持高并发、高吞吐量的线程安全的HashMap。一个ConcurrentHashMap由多个segment组成,每一个segment都包含了一个HashEntry数组的hashtable, 每一个segment包含了对自己的hashtable的操作,比如get,put,replace等操作,这些操作发生的时候,对自己的hashtable进行锁定。由于每一个segment写操作只锁定自己的hashtable,所以可能存在多个线程同时写的情况,性能无疑好于只有一个hashtable锁定的情况。

有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。

参考:

http://blog.csdn.net/liulin_good/article/details/6213815

http://www.cnblogs.com/ITtangtang/p/3948555.html

http://www.cnblogs.com/ITtangtang/p/3948406.html

http://www.cnblogs.com/ITtangtang/p/3948538.html

http://qifuguang.me/2015/09/10/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E5%85%AB]%E6%B7%B1%E5%BA%A6%E5%89%96%E6%9E%90ConcurrentHashMap

https://www.ibm.com/developerworks/cn/java/j-jtp07233/

《java核心技术 卷I 基础知识》

Java中的集合类的更多相关文章

  1. java中的集合类总结

    在使用Java的时候,我们都会遇到使用集合(Collection)的时候,但是Java API提供了多种集合的实现,我在使用和面试的时候频 频遇到这样的“抉择” . :)(主要还是面试的时候) 久而久 ...

  2. java中的集合类详情解析以及集合和数组的区别

    数组和链表 数组:所谓数组就是相同数据类型的元素按照一定顺序排列的集合. 它的存储区间是连续的,占用内存严重,所以空间复杂度很大,为o(n),但是数组的二分查找时间复杂度很小为o(1). 特点是大小固 ...

  3. 面试题: Java中各个集合类的扩容机制

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) Java 中提供了很多的集合类,包括,collection的子接口list.set,以及map等.由于它 ...

  4. Java中的集合类、Lambda、鲁棒性简述

    集合类 在java.util包中提供了一些集合类,常用的有List.Set和Map类,其中List类和Set类继承了Collection接口.这些集合类又称为容器,长度是可变的,数组用来存放基本数据类 ...

  5. java中的集合类学习(三)

    JAVA中有许多的集合,常用的有List,Set,Queue,Map. 1.其中List,Set,Queue都是Collection(集合),其每个元素都是单独的一个对象,如List<Strin ...

  6. Java中的集合类(List,Set.Map)

    1.List 1.1 Arraylist 与 LinkedList 区别 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全: 底层数据结构: Arr ...

  7. java中的集合类(Collection)中的Set

    set集合不包含重复元素及与我们无关的排序!我说hibernate实体类中的集合都用Set呢,难道是因为这个?

  8. Java中的集合类,集合类有哪些,如何增添删除元素,如何遍历

    http://www.cnblogs.com/LittleHann/p/3690187.html import java.util.*; public class TestCollection { p ...

  9. JAVA学习记录(一)————JAVA中的集合类

    这个图是总体的框架图,主要是两个接口Collection和Map都继承接口Iterator(Iterable),为了实现可以使用迭代器.Collection和Map类似平级关系. 1.这里我先学习下A ...

随机推荐

  1. org.apache.hadoop.conf-Configured

    org.apache.hadoop.conf中的最后一个类,也是这个包中以后用的最频繁的一个,Configurable算是肉体,Configuration算是灵魂吧 package org.apach ...

  2. SolrEntityProcessor

    SolrEntityProcessor从不同的solr实例和内核中引入数据,这个数据是基于指定的或者是过滤的查询来获取到的.如果你需要复制索引,并且小幅度的修改目标索引文件中的数据,那么可以使用Sol ...

  3. 使用autolayout的NSLayoutConstraint类中的constraintWithItem 、constraintsWithVisualFormat这两个类方法来创建视图并可以实现自动布局

    #import "ViewController.h" @interface ViewController () @end @implementation ViewControlle ...

  4. 阮一峰博客里面css3的display:flex的布局

  5. Java基础知识强化之IO流笔记79:NIO之 SocketChannel

    1. Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道.可以通过以下2种方式创建SocketChannel: 打开一个SocketChannel并连接到互联网上的某台服 ...

  6. iOS 10 的一些资料整理

    文/判若两人丶(简书作者)原文链接:http://www.jianshu.com/p/0cc7aad638d9 1.iOS 10 隐私权限设置 iOS 10 开始对隐私权限更加严格,如果你不设置就会直 ...

  7. 如何将你自己的Python package发布到PyPI上

    零.前言 最近做了一个小工具,查询IP或者域名的归属地.做完之后想发布到PyPI上,这样大家就可以通过pip来安装了. 在发布的过程中遇到了一些问题,也学到了很多东西.记录到这篇文章中.希望对大家有所 ...

  8. 强大的JQuery(三)--操作html与遍历

    前两篇博客讲到了JQuery的基础知识以及其动画效果,本篇将为大家介绍jquery操纵html以及jquery的遍历. 一.jquery操作html 1.获取内容和属性 text() - 设置或返回所 ...

  9. Visual Studio 扩展包(.vsix)制作

    前言:上篇介绍了 Visual Studio扩展工具添加与卸载,本编要介绍的是Visual Studio 扩展包(.vsix)的制作. 方法: ①.下载并安装Visual Studio 2010 SD ...

  10. 不错的jQuery图表插件 .

    很多时候我们需要在网页中显示数据统计报表,从而能很直观地了解数据的走向,更好地帮助决策分析.今天就给大家分享几个个人觉得好用的jQuery图表插件,这几个图表插件使用起来非常方便,而且挺灵活的,相信大 ...