今天看了一下集合框架,其实指的就是Collection接口和Map接口,其中Collection接口是Iterable接口的子接口。而Map接口上面已经没有父类接口了。
其中,Iterable接口包含三个方法
一、Collection接口
从Collection接口,就是我们的框架内容了,大致将一下,Collection接口下面有很多的子接口,其他的没用过,但是有三个我们经常碰见的,就是Set , List,Queue
List:是一种底层是数组实现的, 元素可重复的有序集合,下面有很多实现,比如ArrayList,LinkedList , Vector等
Set:底层是hash表实现的,不可重复的无序集合,下面的实现有HashSet,TreeSet等
Queue:用于装载先进先出原则的数据 ,下面的实现有Dequeue,LinkedList等。
下面来介绍集中常用的实现
1、ArrayList,最常用的集合,底层是数组,源码中的定义如下,和父类List接口一直,是可以存放重复的元素 ,且存放的元素是有序的。
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
ArrayList与普通的数组有什么区别呢?
最大的区别就是数组的长度是固定的,不可变的,一旦超出数组的长度范围是会报错的,而ArrayList底层虽然也是数组,但是长度却是可变的,每次增加元素是,也就是add方法,都将会检查一遍上面定义的这个数组的长度是否足够,不够的话,将新建一个新的数组,并将老的数组复制到新的数组当中。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)//如果下标大于数组长度,则需要新建一个数组
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
第二点就是,集合做到动态扩容牺牲的是自身的效率,因此,数组的效率是比集合要高的。
第三,集合定义了remove()方法删除自身元素,而数组没有。
第四点,集合存入对象时,抛弃类型信息,所有对象屏蔽为Object,编译时不检查类型,但是运行时会报错。而数组一旦定义好了,如果存放不同类型的数据,编译就会不通过。这个区别总结起来就是,数组只能存放单一类型,而集合可以存放任意类型,默认就是Object,所有对象。
当然了,集合和数组也可以转换,只限于集合存放单一类型
集合–>数组,List.toArray()
数组–>集合,Array.asList()
2、LinkedList
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
看定义我们就知道,LinkedList是实现了List和Queue接口的。底层是链表实现的。LinkedList通过代价较低的在List中间进行插入和删除操作,提供了优化的顺序访问。也即是说,相比于ArrayList,LinkedList的插入删除速度是比ArrayList慢的,而查询的速度却更加的快。
源码中,unlinkLast()方法和unlinkFirst(),顾名思义,也就是pollLat()he pollFirst()的调用,里面是这么写的:
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
/**
* Unlinks non-null last node l.
*/
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
里面的这句help GC很有意思,意思是让某个对象=null,就能被GC掉,否则这个对象永远不会被GC,为什么呢?翻翻JVM书可以找到答案。
3、Vector
List的另一种实现,使用方法和ArrayList一样,但是,人家是线程安全的,所有操作元素的方法都加了synchronized关键字,这也是两者的最大的区别。
两者还有一个不同点,就是两者的扩容方式:
ArrayList:初始容量*3/2+1
Vector:默认扩充两倍。
4、HashSet
说HashSet之前先看看下面的HashMap,因为HashSet是对HashMap的一层包装,内部就是一个HashMap,没什么区别。先看HashMap。
5、HashMap
Map接口的实现类。以key/value键值对存放数据,key值不允许为空,value可以为空,且由于容器可能会对元素进行重新hash,HashMap无法保证元素是有序的。
重新Hash之后,有可能会产生冲突,HashSet处理冲突时候,采用的是冲突链表方式。
HashMap的性能问题,一般来说,put和get是可以在常数时间内完成,但是,如果要对HashMap进行迭代,就需要去遍历整个HashMap和冲突链表。因此,如果是迭代次数比较多的场景,HashMap的初始值不应该设置太大。
有两个参数可以影响HashMap的性能,一个是初始容量(inital capacity)和负载系数(load factor),负载系数指的是自动扩充容量的临界值。当entry的数量大于(初始容量*和负载系数)时,HashMap将进行扩容,且重新计算Hash。所以,对于插入操作较多的场景,将初始值设置稍大,可以减少重新Hash的次数。
另外,将对象放到HashMap中时,需要注意两个方法,hashCode()和equals(),当插入对象时,是通过hashCode计算Hash值判断对象应该插入到什么位置,当Hash值重复时,就要通过equals()方法去判断是不是同一个对象。所以说,如果要将自定义的对象放入到HashMap中,需要重写这两个方法。
6、HashTable
也是Map的实现类,与HashMap一样,唯一不同的就是,HashTable是同步的。