|Collection
| ├List
| │-├LinkedList
| │-├ArrayList
| │-└Vector
| │ └Stack
| ├Set
| │├HashSet
| │├TreeSet
| │└LinkedSet
|
|Map
├Hashtable
├HashMap
└WeakHashMap
1、Java中的容器
Java容器类类库的用途是“持有对象”,并将其划分为两个不同的概念:
1)Collection:一个独立元素的序列,这些元素都服从一条或者多条规则。 List必须按照插入的顺序保存元素,而set不能有重复的元素。Queue按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)。
2)Map:一组成对的“键值对”对象,允许使用键来查找值。
Collection集合:
Collection是最基本的集合接口,List和Set是Collection集合的两个主要的子接口。
List接口:
List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。 实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。
LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在 LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。注意:LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:List list = Collections.synchronizedList(new LinkedList(…));
ArrayList实现了可变大小的数组。它实现了List接口,允许所有元素,包括null。ArrayList没有同步。size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。和LinkedList一样,ArrayList也是非同步的(unsynchronized)。一般情况下使用这两个就可以了,因为非同步,所以效率比较高。
如果涉及到堆栈,队列等操作,应该考虑用List(如LinkedList和ArrayList),对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的 Iterator是同一接口,但是,因为Vector是同步的,当一个 Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。
Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方 法,还有 peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。
Set接口:
Set是一种不包含重复元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。 Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。 Set容器类主要有HashSet和TreeSet等。
HashSet类 :Java.util.HashSet类实现了Java.util.Set接口。 它不允许出现重复元素;不保证集合中元素的顺序 ;允许包含值为null的元素,但最多只能有一个null元素。
TreeSet是将元素存储在红-黑树结构中,所以存储的结果是有顺序的(所以如果想要存储的集合有顺序那么选择TreeSet)。
Map集合接口:
Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个 value。Map接口提供3种集合的视图:Map的内容可以被当作一组key集合,或者一组value集合,或者一组key-value映射。
Hashtable类:Hashtable实现Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。
HashMap类 :HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。
-JDK1.0引入了第一个关联的集合类HashTable,它是线程安全的。 HashTable的所有方法都是同步的。
-JDK2.0引入了HashMap,它提供了一个不同步的基类和一个同步的包装器synchronizedMap。synchronizedMap被称为 有条件的线程安全类。
-JDK5.0util.concurrent包中引入对Map线程安全的实现ConcurrentHashMap,比起synchronizedMap,它提供了更高的灵活性,同时可以并发地进行读和写操作。
HashTable和HashMap的区别主要有两点:
1、Hashtable 中的方法是同步的,而HashMap中的方法是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
2、Hashtable中,key和value都不允许出现null值。在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
一、线程安全(Thread-safe)的集合对象:
- Vector 线程安全
- Stack线程安全(继承Vector)
- HashTable 线程安全
- StringBuffer 线程安全
- ConcurrentHashMap 线程安全
- CopyOnWriteArrayList 线程安全
二、非线程安全的集合对象:
- ArrayList
- LinkedList
- HashMap
- HashSet
- TreeMap
- TreeSet
- StringBulider
2、Collection和Collections的区别
java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
Collections是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
Collections工具类中经常用到的方法:
(1)public static void sort(List list);--根据元素的自然顺序对指定列表按升序排序。
(2)public static void reverse(List list);--逆序反转,反转指定列表中元素的顺序。
(3)public static void shuffle(List list);--使用默认随机源随机更改指定列表的元素顺序。
(4)public static void copy(List deslist,List sourcelist);--将所有元素从一个列表(sourcelist)复制到另一个列表(deslist)。
(5)public static ArrayList list(Enumeration e);--返回一个数组列表,按照枚举结构中元素的原有顺序包含其中的所有元素。
(6)public static int frequency(Collection c,Object o);--返回指定collection中等于指定对象的元素数。
(7)public static T max(Collection c);--按照元素的自然顺序,返回指定collection中的最大元素。
(8)public static T min(Collection c);--按照元素的自然顺序,返回指定collection中的最小元素。
(9)public static void rotate(List list,int distance);--根据指定的距离循环移动指定列表的元素。
3、List、Set、Map之间的区别
List、Set、Map之间的区别主要体现在元素的重复性、有序性和是否允许为空值三方面。
1) 元素重复性:
① List允许有重复的元素。任何数量的重复元素都可以在不影响现有重复元素的值及其索引的情况下插入到List集合中;
② Set集合不允许元素重复。Set以及所有实现了Set接口的类都不允许重复值的插入,若多次插入同一个元素时,在该集合中只显示一个;
③ Map以键值对的形式对元素进行存储。Map不允许有重复键,但允许有不同键对应的重复的值;
2) 元素的有序性:
① List及其所有实现类保持了每个元素的插入顺序;
② Set中的元素都是无序的,但是某些Set的实现类以某种特殊形式对其中的元素进行排序,如:LinkedHashSet按照元素的插入顺序进行排序;
③ Map跟Set一样对元素进行无序存储,但其某些实现类对元素进行了排序,如:TreeMap根据键对其中的元素进行升序排序;
3) 元素是否为空值:
① List允许任意数量的空值;
② Set最多允许一个空值的出现;[ 当向Set集合中添加多个null值时,在该Set集合中只会显示一个null元素]
③ Map只允许出现一个空键,但允许出现任意数量的空值;
总结:List中的元素:有序、可重复、可为空;
Set中的元素:无序、不重复、只允许有一个空元素;
Map中的元素:无序、键不可重复,值可重复、只允许有一个空键,有多个空值。
什么场景下使用list,set,map呢?
1、如果经常使用索引来对容器中的元素进行访问,那么List是正确的选择。如果已经知道索引的话,那么List的实现类(如ArrayList)可以提供更快速的访问,如果经常添加或删除元素,那么LinkedList是最好的选择。
2、如果想要容器中的元素能够按照它们插入的次序进行有序存储,那么就选择List,因为List是一个有序容器,它按照插入顺序进行存储。
3、如果想要保证插入元素的唯一性,即不允许有重复的元素,那么可以选择一个Set的实现类,比如HashSet、LinkedHashSet或者TreeSet。所有Set的实现类都遵循了统一约束比如唯一性,另外还提供了额外的特性,比如TreeSet还是一个SortedSet,所有存储于TreeSet中的元素可以使用Java里的Comparator或者Comparable进行排序。LinkedHashSet也按照元素的插入顺序对它们进行存储。
4、如果想要以键和值的形式进行数据存储,那么Map是正确的选择。
3.1、ArrayList、LinkedList和Vector的区别
ArrayList:基于数组实现的,数组长度大于实际存储的元素个数。允许使用索引查询元素,所以查询速度较快。但是插入或删除数据时,需要移动插入或删除元素所在索引的所有后续元素,因此,插入或删除数据比较慢。另外,ArrayList不是线程安全的。
Vector:与ArrayList几乎相同,主要区别在于Vector是线程安全的(方法使用synchronized关键字修饰),而ArrayList不是线程安全的。另外,在扩容时,ArrayList增加50%,而Vector会扩容1倍。
LinkedList:使用双向链表实现存储,按索引查询数据需要进行向前或向后遍历,所以查询速度较慢。但是插入数据时只需要记录本项前后项即可,因此插入数据较快。另外,LinkedList不是线程安全的,LinkedList也不需要扩容。
线程安全的List:Vector、SynchronizedList和CopyOnWriteArrayList。
Vector是线程安全的List集合,不过他的性能是最差的,所有的方法都是加了synchronized来同步,从而保证线程安全。如下所示:
public synchronized boolean add(E e) {modCount++;ensureCapacityHelper(elementCount + 1);elementData[elementCount++] = e;return true;
}public synchronized E get(int index) {if (index >= elementCount)throw new ArrayIndexOutOfBoundsException(index);return elementData(index);
}public synchronized E remove(int index) {modCount++;if (index >= elementCount)throw new ArrayIndexOutOfBoundsException(index);E oldValue = elementData(index);int numMoved = elementCount - index - 1;if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);elementData[--elementCount] = null; // Let gc do its workreturn oldValue;
}
SynchronizedList是Collections类的静态内部类,它能把所有 List 接口的实现类转换成线程安全的List,比 Vector 有更好的扩展性和兼容性。它的所有方法都是带同步对象锁的,和 Vector 一样,它不是性能最优的。如下所示:
public E get(int index) {synchronized (mutex) {return list.get(index);}
}public E set(int index, E element) {synchronized (mutex) {return list.set(index, element);}
}public void add(int index, E element) {synchronized (mutex) {list.add(index, element);}
}public E remove(int index) {synchronized (mutex) {return list.remove(index);}
}
CopyOnWriteArrayList是java1.5以后才加入的新类,从命名可以理解为复制再写入的List。
它的添加或删除操作是加锁的(ReentrantLock ,非synchronized同步锁),读操作没有加锁。添加或者删除元素时,先加锁,再进行复制替换操作,最后再释放锁。 它的优势在于,读操作是不加锁的。这样做的好处是,在高并发情况下,读取元素时就不用加锁,写数据时才加锁,大大提升了读取性能。缺点是浪费空间,每次添加、删除元素就需要复制一份新数组。
public class CopyOnWriteArrayList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable {private static final long serialVersionUID = 8673264195747942595L;/** The lock protecting all mutators */final transient ReentrantLock lock = new ReentrantLock();/** The array, accessed only via getArray/setArray. */private transient volatile Object[] array;/*** Gets the array. Non-private so as to also be accessible* from CopyOnWriteArraySet class.*/final Object[] getArray() {return array;}/*** Sets the array.*/final void setArray(Object[] a) {array = a;}//新增
public boolean add(E e) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;Object[] newElements = Arrays.copyOf(elements, len + 1);newElements[len] = e;setArray(newElements);return true;} finally {lock.unlock();}
}
//查询
public E get(int index) {return get(getArray(), index);
}
private E get(Object[] a, int index) {return (E) a[index];
}
//删除
public E remove(int index) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;E oldValue = get(elements, index);int numMoved = len - index - 1;if (numMoved == 0)setArray(Arrays.copyOf(elements, len - 1));else {Object[] newElements = new Object[len - 1];System.arraycopy(elements, 0, newElements, 0, index);System.arraycopy(elements, index + 1, newElements, index,numMoved);setArray(newElements);}return oldValue;} finally {lock.unlock();}
}
remove(int index)的作用就是将volatile数组中第index个元素删除。它的实现方式是,如果被删除的是最后一个元素,则直接通过Arrays.copyOf()进行处理,而不需要新建数组。否则,新建数组,然后将volatile数组中被删除元素之外的其它元素拷贝到新数组中;最后,将新数组赋值给volatile数组。
3.2、HashMap、Hashtable和TreeMap的区别
HashMap和HashTable都实现了Map接口。
HashMap允许有空键和空值,而HashTable不允许有空键或空值。HashTable中的方法是被synchronized关键字修饰的,所以HashTable是线程安全的。而HashMap不是线程安全的,HashMap是HashTable的轻量级实现(非线程安全的实现),所以效率会高于HashTable。HashMap去掉了HashTable的contains方法,改成containsValue和containsKey方法。
在多个线程访问HashTable时,不需要自己为它的方法实现同步,而HashMap就必须为之提供同步。
HashMap是基于散列表(哈希表)实现的,TreeMap是基于红黑树实现的。
HashMap中存储的元素是无序的,TreeMap中存储的元素是有序的(如果需要按某种顺序存储键值对数据,可以使用TreeMap)。
HashMap是通过key的hashCode方法对数据进行查询的,Treemap适用于按自然顺序或自定义顺序遍历键(key)。HashMap和TreeMap都是非线程安全的。
3.3、HashSet、TreeSet和LinkedHashSet的区别
Set接口继承了Collection接口。Set集合不包含重复的元素,这是使用Set的主要原因。有三种常见的Set实现——HashSet, TreeSet和LinkedHashSet。
HashSet是基于哈希表(散列表)实现的(基于HashMap实现的),其中存储的元素是无序的。
TreeSet是基于红黑树实现的,其中存储的元素是有序的。
LinkedHashSet也是基于哈希表实现的,同时维护了一个双链表来记录插入数据的顺序。
总体而言,如果需要一个访问快速的Set,应该使用HashSet;如果需要一个排序的Set,应该使用TreeSet;当需要记录插入数据的顺序时,应该使用LinkedHashSet。