深入理解容器(一)
容器一
基本概念
Java容器类类库的用途是保存对象,可以将其分为2个概念。
Collection
一个独立元素的序列,这些元素都服从一条或多条规则。其中List必须按照插入的顺序保存元素、Set不能有重复的元素、Queue按照排队规则来确定对象的产生顺序(通常也是和插入顺序相同)
Map
一组成对的值键对对象,允许用键来查找值。ArrayList允许我们用数字来查找值,它是将数字和对象联系在一起。而Map允许我们使用一个对象来查找某个对象,它也被称为关联数组。或者叫做字典
List
ArrayList
ArrayList基于数组实现,拥有数组的特点:查询元素快(具有索引);但是在删除和添加数组时效率较低。
ArrayList的实现
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
//第一种实现
//默认数组长度为0实现
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//第二种实现
//数组长度由参数传入实现
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//第三种实现
//传入容器参数,转换为数组
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
从源码可以看出,ArrayList的实现是依靠数组的.查询时时间复杂度为1
下面我们看下在ArrayList.add(int index,E element)实现中,为何效率会低。
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
核心代码
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
跟随下去
//参数
//第一个参数:源数组
//第二个参数:源数组索引
//第三个参数:目标数组
//第四个参数:目标数组开始的位置
//第五个参数:需要复制元素的长度
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
这个意思就是从index索性处每个元素向后移动一位,最后把索引为index空出来,并将element赋值给它。这样一来我们并不知道要插入哪个位置,所以会进行匹配那么它的时间赋值度就为n。
LinkedList
基于链表实现的List,执行查询时效率低,更适合添加和删除元素。特性功能比ArrayList强大,支持Queue和Stack
LinkedList中add的实现
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
LinkBefore
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
先获取插入索引元素的前驱节点,然后把这个元素作为后继节点,然后在创建新的节点,而新的节点前驱节点和获取前驱节点相同,而后继节点则等于要移动的这个元素。所以这里是不需要循环的,从而在插入和删除的时候效率比较高。
Set
Set也是一个集合,但是他的特点是不可以有重复的对象,所以Set最常用的就是测试归属性,很容易的询问出某个对象是否存在Set中。并且Set是具有和Collection完全一样的接口,没有额外的功能,只是表现的行为不同。
HashSet
HashSet查询速度比较快,但是存储的元素是随机的并没有排序
TreeSet
TreeSet是将元素存储红-黑树结构中,所以存储的结果是有顺序
Queue
Queue是队列,队列是典型的先进先出的容器,就是从容器的一端放入元素,从另一端取出,并且元素放入容器的顺序和取出的顺序是相同的。LinkedList提供了对Queue的实现,LinkedList向上转型为Queue。其中Queue有offer、peek、element、pool、remove等方法
offer是将元素插入队尾,返回false表示添加失败。peek和element都将在不移除的情况下返回对头,但是peek在对头为null的时候返回null,而element会抛出NoSuchElementException异常。poll和remove方法将移除并返回对头,但是poll当元素不在队列为null,而remove会抛出NoSuchElementException异常