0004.Java学习—集合List包含源码分析
概述
由于数组固定长度的局限,在不确定固定尺寸的情况下,可以使用集合来解决,集合可以自动调整大小。
其中基本的类型包括List、Set、Queue、Map,这些类型统称为容器类,用于存储和获取数据。
-
Collection:一个独立元素的序列。List必须以插入的顺序保存元素,Set不能包含重复元素
-
Queue按照排队规则来确定对象产生的顺序
-
Map:一组成对的"键值对"对象,允许使用键来查找值
上图为集合的简图
注:
1.绿色表示接口,橙色表示抽象类,蓝色表示具体类
2.虚线表示实现关系,实线表示继承关系
Iterator集合
public interface Iterator<E> {
// 如果还有可以迭代的元素, 返回true
boolean hasNext();
// 返回当前集合中迭代的下一个元素
E next();
// 方法将迭代器最近返回的元素删除, 如正常删除当前元素, 返回true, 否则返回false
default void remove() {
throw new UnsupportedOperationException("remove");
}
}
// 例
public static void main(String[] args) {
Collection<String> strArr = new ArrayList<>();
Collection<String> strSet = new HashSet<>();
strArr.addAll(Arrays.asList("Hadoop", "Spark", "Flink", "Zookeeper"));
Iterator<String> strArrIterator = strArr.iterator();
Iterator<String> strSetIterator = strArr.iterator();
// 1. 使用hasNext()和next()方法
while (strArrIterator.hasNext()) {
System.out.print(strArrIterator.next() + "\t");
strArrIterator.remove();
}
// 2. 使用for-in语法遍历
for (String str : strArr) {
System.out.print(str + "\t");
strArr.remove(str);
}
// 不同集合调用统一方法disPlay()
// 迭代器能够遍历序列的操作与该序列的底层结构分离
disPlay(strArrIterator);
disPlay(strSetIterator);
System.out.println("");
System.out.println(strArr.size());
System.out.println(strArrIterator.hasNext());
}
public static void disPlay(Iterator<String> iterator) {
while (iterator.hasNext()) {
System.out.print(iterator.next() + "\t");
iterator.remove();
}
}
// Output:
// Hadoop Spark Flink Zookeeper
// 0
// false
1.通常情况下当我们去使用集合的时候,必须知道集合的数据类型,但是使用迭代器不需要知道数据类型和数据的数量,
2.上例中
(1) 使用了 hasNext() 和 next() 两个方法
(2) 使用了for-in语法
3.Iterator在调用remove()方法之前,必须先调用next()方法
4.迭代器是一个对象,它在一个序列中移动并选择该序列中的每一个对象。
5.迭代器能够遍历序列的操作与该序列的底层结构分离,迭代器统一了对集合的访问方式
列表List
- List继承了Collection接口, 并添加了一些方法,可以允许在List中间插入和删除元素
- List的两个特点是
1、有序(存储和取出元素的顺序相同)
2、存储元素可以重复 - List分为两种:
1、 ArrayList: 优势是随机访问元素快, 但在List中间插入和删除元素速度较慢
2、LinkedList: 在List中间进行的插入和删除操作快, 但是随机访问较慢
ArrayList
ArrayList的三种创建方法
transient Object[] elementData;
private static final Object[] EMPTY_ELEMENTDATA = {};
// 创建ArrayList的三种方法
// 第一种传入一个初始容量
public ArrayList(int initialCapacity) {
// 判断初始容量的值
// 如果init > 0, 则创建init大小的Object数组
// 如果init == 0, 则创建一个空Object数组
// 其他情况下报错, 初始容量为负值异常
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
// 第二种传入一个Collection对象
// 可以传入数据类型为Object类型或者其子类
public ArrayList(Collection<? extends E> c) {
// 将Collection集合转换成Object数组
elementData = c.toArray();
// 分别判断size大小是否 > 0
// 如果等于0, 则创建一个空Object数组
// 如果不等于0, 则判断c.toArray()是否生成了Object数组, 如果是, 则生成一个新的数组
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
// 第三种没有参数, new一个无参构造器, 创建一个空的Object数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//下面方法是Arrays.copyOf()
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
// 下面是三元运算
// 判断newType是否是Object数组
// 如果是, 则创建一个newLength大小的Object数组
// 如果不是, 则返回当前newType类型并创建一个Object类型
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
//从指定的源数组——original的指定位置, 开始复制到目标数组的指定位置
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
public static Object newInstance(Class<?> componentType, int length)
throws NegativeArraySizeException {
return newArray(componentType, length);
}
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
ArrayList常见的方法解析
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 1. 参数是Object类型
public boolean add(E e) {
// 判断当前容量与传入的最小容量(默认为10)的大小
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将数据插入到Object数组中
elementData[size++] = e;
return true;
}
// 判断最小容量
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 计算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 判断elementData是否为空Object数组, 如果是, 则返回默认容量(大小为10) 与 传参的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 如果不为空, 则直接返回最小容量的值size + 1
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
// 增加容量, 至少能保证为默认最小容量参数指定的元素数量
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 2. 插入固定索引位置
public void add(int index, E element) {
// 判断index是否在0到size之间, 如不是报错
rangeCheckForAdd(index);
// 判断当前容量与传入的最小容量(默认为10)的大小
ensureCapacityInternal(size + 1); // Increments modCount!!
// 插入到对应的位置上
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
将当前索引内容换成待插入的数据
elementData[index] = element;
size++;
}
// 3. 插入Collection对象(与add大同小异)
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
// 如果c不为空, 则返回true, 否则返回false插入失败
return numNew != 0;
}
// 4. 删除数据
public E remove(int index) {
// 判断index是否小于等于集合的size
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
// numMoved是需要移动的个数, size - (index + 1)
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 手动置空
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
// 5. 获取数据
public E get(int index) {
// 判断index是否小于集合大小, 如果大于则报错
rangeCheck(index);
// 直接获取集合索引所在数据
return elementData(index);
}
// 6. 判断集合是否为空, 即为判断size是否为0
public boolean isEmpty() {
return size == 0;
}
// 7. 获取集合大小
public int size() {
return size;
}
// 8. 获取数据索引
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
// 9. 清除集合, 直接将集合中所有内容置空
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
LinkedList
LinkedList的两种创建方法
//LinkedList 有两种创建方式
// 第一种:创建一个空List
public LinkedList() {
}
// 第二种:传递Collection集合
public LinkedList(Collection<? extends E> c) {
//同样会先创建一个空List
this();
//调用addAll()方法
addAll(c);
}
// 首先看一下add(E e)方法
public boolean add(E e) {
linkLast(e);
return true;
}
// Node节点中包括当前数据, 前一个节点的引用, 后一个节点的引用, 如果是最后一个元素, next是null; 如果是第一个元素,prev是null
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
LinkedList 常见的方法解析
// add方法调用的是linkLast方法
void linkLast(E e) {
// 新创建的LinkedList中last=null
final Node<E> l = last;
// 创建Node节点参数是: 最后一个Node节点(就是新插入的节点的前一个节点)、当前节点数据、next节点为null
final Node<E> newNode = new Node<>(l, e, null);
// 此时最后一个节点变为当前插入的节点
last = newNode;
// 判断: 如果是空集合, 则第一个节点为当前节点; 如果不是空节点: 最后一个节点的下一个节点就是待插入的节点
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
// add的另一个方法, 在集合中间插入数据
public void add(int index, E element) {
//1.判断当前索引是不是在0~当前集合的size中间
checkPositionIndex(index);
//判断index与集合size的关系, 如果相等是在集合最后插入数据, 等同于add(E e); 如果不相等, 则判断插入位置, 详见下一个方法
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
// 判断插入位置
// 参数是: 需要插入的数据, index索引节点
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//首先获取index索引的前一个节点
final Node<E> pred = succ.prev;
//创建包含插入数据的新节点, 新节点的前一个元素是index索引的前一个节点, 后一个几点是index索引节点
final Node<E> newNode = new Node<>(pred, e, succ);
//将index索引节点的前一个节点变为新插入的节点
succ.prev = newNode;
//判断index节点的前一个节点是否为null, 就是判断index节点是不是第一个元素,first
//如果是null则第一个元素变为新节点, 如果不是null则index索引的前一个节点的next节点变成当前插入节点
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
//addAll(Collection<? extends E> c)本质也是调用addAll(int index, Collection<? extends E> c)只不过index索引变成集合的大小size
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
//1.检查index所以是否>=0 && <=size
checkPositionIndex(index);
//2.将传入的集合变成一个Object数组
Object[] a = c.toArray();
//3.数组的长度
int numNew = a.length;
if (numNew == 0)
return false;
//4.创建两个空节点
Node<E> pred, succ;
//5.给两个空节点赋值, 即为index索引所在的节点succ, 和index节点的前一个节点prev
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
// 6. 遍历数组将数据插入到集合中
for (Object o : a) {
// 将Object数据类型强制转换为当前类型E
@SuppressWarnings("unchecked") E e = (E) o;
// 创建当前数据的新节点
Node<E> newNode = new Node<>(pred, e, null);
//判断pred(index索引所在节点的前一个节点)是否为null,
//如果为null则新插入节点即为first第一个节点,
//如果不是则将index索引所在节点的前一个节点的后一个节点变为新插入节点
//然后将index索引的前一个节点变成新插入的节点
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
// 如果succ为null,即index==size, 则最后一个元素节点==newNode新插入节点
// 如果succ不为null, 即index < size, 则新插入节点的后一个节点是index节点, index节点的前一个节点变为新插入节点
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
// size ==size + a.length
size += numNew;
modCount++;
return true;
}
// addFirst和addLast同理是插入带集合第一个元素前、最后一个元素后
public void addFirst(E e) {
linkFirst(e);
}
public void addLast(E e) {
linkLast(e);
}
// LinkedList中的get方法
public E get(int index) {
//判断index是否 >= 0 && <= size
checkElementIndex(index);
//调用弄得方法, 方法如下
return node(index).item;
}
Node<E> node(int index) {
// assert isElementIndex(index);
// 首先判断index与size/2的大小
// 如果index<size/2, 则从0~index区间查找
// 如果index>size/2, 则从index~size-1区间查找
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
//set方法
public E set(int index, E element) {
//1.判断index是否>=0&&<=size
checkElementIndex(index);
//2.取出index所在的节点
Node<E> x = node(index);
//3.将index节点的数据变为新数据并返回老数据
E oldVal = x.item;
x.item = element;
return oldVal;
}
如有任何错误,请不吝赐教。