介绍Java容器原理、特性及简单使用方式,主要包含Collection接口及其主要实现类。



Java集合系列文章包括:

本文主要介绍Java容器原理、特性及简单使用方式,主要包含Collection接口及其主要实现类。

1. 概览

Java 集合, 也叫作容器,主要是由两大接口派生而来:一个是 Collecton接口,主要用于存放单一元素;另一个是 Map 接口,主要用于存放键值对。对于Collection 接口,下面又有三个主要的子接口:List、Set 和 Queue

2. Collection接口

Java集合(一) - Collection部分_指定位置

3. 常用实现类

  • ArrayList
  • List接口的主要实现类,利用数组存储数据,它能够添加null元素,使用数组下标查找数据O(1),适用于频繁的查找工作
  • 插入元素时,当size == elementData.length的时候,就需要扩容。初始化的时候尽量指定容量,减少扩容次数
  • 线程不安全
  • LinkedList
  • Vector
  • Stack

3. ArrayList

1. 数据结构

数组

/**
* 存储元素的数组,当size等于数组的长度,再次进行插入就需要扩容
*/
transient Object[] elementData;
/**
* 存储的元素的个数,一般情况下,size != elementData.length
*/
private int size;


2. 插入元素(add)

1. 扩容

插入元素时,当​​size == elementData.length​​的时候,就需要扩容。扩容实际上是通过​​System.arraycopy​​方法将旧数组内容复制到新数组,新数组长度的大小逻辑如下:

  • 初始情况下,数组长度扩容为10(默认的)
  • 其他情况下,新数组的长度是原数组的1.5倍(oldCapacity + (oldCapacity >> 1)),数组长度最大为Integer.MAX_VALUE

2. 两种插入方式

private void add(E e, Object[] elementData, int s) {
if (s == elementData.length) {
elementData = grow(); // 扩容
}
elementData[s] = e;
size = s + 1;
}

/**
* 元素插入到数组末尾
*/
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}

/**
* 在指定位置插入元素
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
modCount++;
final int s;
Object[] elementData;
if ((s = size) == (elementData = this.elementData).length) {
elementData = grow();
}
System.arraycopy(elementData, index, elementData, index + 1, s - index);
elementData[index] = element;
size = s + 1;
}

private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}


  • 元素插入到数组末尾。首先判断是否需要扩容,然后给elementData[size]赋值,时间复杂度为O(1)
  • 元素插入到指定位置。首先判断指定位置是否越界了([0, size]),然后判断是否需要扩容,然后将[index, size - 1]的元素移动到[index + 1, size],这里是通过System.arraycopy直接复制指定区间的元素,速度比循环更快,最后给elementData[index]赋值
  • ArrayList还提供了批量插入元素的方法(addAll)

因此,在中间位置插入元素效率比在末尾插入要低,所以我们在使用的时候,尽量不要在中间位置插入元素。

3. 访问元素(get)

public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}

E elementData(int index) {
return (E) elementData[index];
}


  • 通过数组下标访问元素,时间复杂度为O(1)
  • ArrayList还提供了判断集合是否包含元素(contains)和查询元素在集合中的下标(indexOf)的方法

4. 删除元素(remove)

/**
* 删除指定位置元素
*/
public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
fastRemove(es, index);
return oldValue;
}

/**
* 删除指定元素
*/
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
if (o == null) {
for (; i < size; i++) {
if (es[i] == null) {
break found;
}
}
} else {
for (; i < size; i++) {
if (o.equals(es[i])) {
break found;
}
}
}
return false;
}
fastRemove(es, i);
return true;
}

private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i) {
System.arraycopy(es, i + 1, es, i, newSize - i);
}
es[size = newSize] = null;
}


  • 删除指定位置元素:将指定元素后面的元素整个向前挪一个位置,再将最后一个元素置为null
  • 删除指定元素:定位指定元素下标,之后的操作与删除指定位置元素一致
  • ArrayList还提供了删除符合条件元素的方法(removeIf)

4. LinkedList

5. Vector

6. Stack

7. 参考