- 先来个结构图
此图来源于:blog.csdn.net/u010887744/…
接下来将一级一级的分析:
2.Iterator
(1)定义:
public interface Iterator<E>
一个集合的迭代器。 Iterator需要的地方Enumeration在Java集合框架
(2)方法:
| 返回类型 | 方法 |
|---|---|
| default void | forEachRemaining(Consumer<? super E> action)对每个剩余元素执行给定的操作,直到所有元素都被处理或动作引发异常。 |
| boolean | hasNext()如果迭代具有更多元素,则返回 true 。 |
| E | next()返回迭代中的下一个元素。 |
| default void | remove()从底层集合中删除此迭代器返回的最后一个元素(可选操作)。 |
(3)演示:
结果:
结果:
注意:iterator没有继承 Iterable 没有forEach的方法。
接下来就是ListIterator
3.ListIterator
(1)定义
public interface ListIterator<E> extends Iterator<E>
从定义上来看 ListIterator是Iterator的子类,用于允许程序员沿任一方向遍历列表的列表的迭代器,在迭代期间修改列表,并获取列表中迭代器的当前位置。 A ListIterator没有电流元素; 其光标位置始终位于通过调用previous()返回的元素和通过调用next()返回的元素next() 。 长度为n的列表的迭代器具有n+1可能的光标位置,如下所示的^ ( ^ )所示: Element(0) Element(1) Element(2) ... Element(n-1) cursor positions: ^ ^ ^ ^ ^ 请注意, remove()和set(Object)方法未按光标位置进行定义; 它们被定义为对调用next()或previous()返回的最后一个元素进行操作。
- 允许我们向前、向后两个方向遍历 List;
- 在遍历时修改 List 的元素;
- 遍历时获取迭代器当前游标所在位置。
(2)方法:
| 返回类型 | 方法 |
|---|---|
| void | add(E e)将指定的元素插入列表(可选操作)。 |
| boolean | hasNext()返回 true如果遍历正向列表,列表迭代器有多个元素。 |
| boolean | hasPrevious()返回 true如果遍历反向列表,列表迭代器有多个元素。 |
| E | next()返回列表中的下一个元素,并且前进光标位置。 |
| int | nextIndex()返回随后调用 next()返回的元素的索引。 |
| E | previous()返回列表中的上一个元素,并向后移动光标位置。 |
| int | previousIndex()返回由后续调用 previous()返回的元素的索引。 |
| void | remove()从列表中删除由 next()或 previous()返回的最后一个元素(可选操作)。 |
| void | set(E e)用 指定的元素替换由 next()或 previous()返回的最后一个元素(可选操作)。 |
(3)讲解:
迭代器 没有当前所在元素一说,它只有一个游标( cursor )的概念,这个游标总是在元素之间,比如这样:
初始时它在第 0 个元素之前,调用 next() 游标后移一位:
这里写图片描述
调用 previous() 游标就会回到之前位置。当向后遍历完元素,游标就会在元素 N 的后面:
也就是说长度为 N 的集合会有 N+1 个游标的位置。
具体的方法在其实现类中讲解
转载:blog.csdn.net/goulei2010/…
接下来让我们看看最重要的接口 Collection
4. Collection
(1)定义:
public interface Collection<E> extends Iterable<E>
(2)方法
| 返回类型 | 方法 |
|---|---|
| boolean | add(E e)确保此集合包含指定的元素(可选操作)。 |
| boolean | addAll(Collection<? extends E> c)将指定集合中的所有元素添加到此集合(可选操作)。 |
| void | clear()从此集合中删除所有元素(可选操作)。 |
| boolean | contains(Object o)如果此集合包含指定的元素,则返回 true 。 |
| boolean | containsAll(Collection<?> c)如果此集合包含指定 集合中的所有元素,则返回true。 |
| boolean | equals(Object o)将指定的对象与此集合进行比较以获得相等性。 |
| int | hashCode()返回此集合的哈希码值。 |
| boolean | isEmpty()如果此集合不包含元素,则返回 true 。 |
| Iterator | iterator()返回此集合中的元素的迭代器。 |
| default Stream | parallelStream()返回可能并行的 Stream与此集合作为其来源。 |
| boolean | remove(Object o)从该集合中删除指定元素的单个实例(如果存在)(可选操作)。 |
| boolean | removeAll(Collection<?> c)删除指定集合中包含的所有此集合的元素(可选操作)。 |
| default boolean | removeIf(Predicate<? super E> filter)删除满足给定谓词的此集合的所有元素。 |
| boolean | retainAll(Collection<?> c)仅保留此集合中包含在指定集合中的元素(可选操作)。 |
| int | size()返回此集合中的元素数。 |
| default Spliterator | spliterator()创建一个Spliterator在这个集合中的元素。 |
| default Stream | stream()返回以此集合作为源的顺序 Stream 。 |
| Object[] | toArray()返回一个包含此集合中所有元素的数组。 |
| T[] | toArray(T[] a)返回包含此集合中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型。 |
这些都不重要,接口和抽象类都是对方法进行了规范,重点在10个实现类的实现。 其分别为 List 下面3个 ArrayList Vector LinkedList Set 下面3个 HashSet TreeSet LinkedSet Map 下面4个 HashMap TreeMap LinkedHashMap WeakHashMap
5.List精讲
别眨眼,这是通往List的快车,你讲享受List的乐趣。
(1)ArrayList
1、ArrayList 定义
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable
①、实现 RandomAccess 接口
这是一个标记接口,一般此标记接口用于 List 实现,以表明它们支持快速(通常是恒定时间)的随机访问。该接口的主要目的是允许通用算法改变其行为,以便在应用于随机或顺序访问列表时提供良好的性能。 比如在工具类 Collections(这个工具类后面会详细讲解)中,应用二分查找方法时判断是否实现了 RandomAccess 接口:
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
②、实现 Cloneable 接口
这个类是 java.lang.Cloneable,浅拷贝可以通过调用 Object.clone() 方法来实现,但是调用该方法的对象必须要实现 Cloneable 接口,否则会抛出 CloneNoSupportException异常。
Cloneable 和 RandomAccess 接口一样也是一个标记接口,接口内无任何方法体和常量的声明,也就是说如果想克隆对象,必须要实现 Cloneable 接口,表明该类是可以被克隆的。
③、实现 Serializable 接口
这个没什么好说的,也是标记接口,表示能被序列化。
④、实现 List 接口
这个接口是 List 类集合的上层接口,定义了实现该接口的类都必须要实现的一组方法,如下所示,下面我们会对这一系列方法的实现做详细介绍。
2、字段属性
//集合的默认大小
private static final int DEFAULT_CAPACITY = 10;
//空的数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//这也是一个空的数组实例,和EMPTY_ELEMENTDATA空数组相比是用于了解添加元素时数组膨胀多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储 ArrayList集合的元素,集合的长度即这个数组的长度
//1、当 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时将会清空 ArrayList
//2、当添加第一个元素时,elementData 长度会扩展为 DEFAULT_CAPACITY=10
transient Object[] elementData;
//表示集合的长度
private int size;
注:transient 是不被序列化的标记,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会被序列化。
具体详解请参考:baijiahao.baidu.com/s?id=163655…
3.构造方法
1.ArrayList() 构造一个初始容量为十的空列表。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
此无参构造函数将创建一个 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 声明的数组,注意此时初始容量是0,而不是大家以为的 10。
注意:根据默认构造函数创建的集合,ArrayList list = new ArrayList();此时集合长度是0.
2.ArrayList(Collection<? extends E> c) 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
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;
}
}
将已有的集合复制到集合中
3.ArrayList(int initialCapacity) 构造具有指定初始容量的空列表。
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);
}
}
初始化集合大小创建 ArrayList 集合。当大于0时,给定多少那就创建多大的数组;当等于0时,创建一个空数组;当小于0时,抛出异常。
给定的类型是int类型 说明了list的容量是有限度的
4.方法
1.添加元素
通过前面的字段属性和构造函数,我们知道 ArrayList 集合是由数组构成的,那么向 ArrayList 中添加元素,也就是向数组赋值。我们知道一个数组的声明是能确定大小的,而使用 ArrayList 时,好像是能添加任意多个元素,这就涉及到数组的扩容。
扩容的核心方法就是调用前面我们讲过的Arrays.copyOf 方法,创建一个更大的数组,然后将原数组元素拷贝过去即可。下面我们看看具体实现:
public boolean add(E e) {
ensureCapacityInternal(size + 1); //添加元素之前,首先要确定集合的大小
elementData[size++] = e;
return true;
}
在通过调用 add 方法添加元素之前,我们要首先调用 ensureCapacityInternal 方法来确定集合的大小,如果集合满了,则要进行扩容操作。
private void ensureCapacityInternal(int minCapacity) {
//这里的minCapacity 是集合当前大小+1
//elementData 是实际用来存储元素的数组,注意数组的大小和集合的大小不是相等的,前面的size是指集合大小
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//如果数组为空,则从size+1的值和默认值10中取最大的
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;//不为空,则返回size+1
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
在 ensureExplicitCapacity 方法中,首先对修改次数modCount加一,这里的modCount给ArrayList的迭代器使用的,在并发操作被修改时,提供快速失败行为(保证modCount在迭代期间不变,否则抛出ConcurrentModificationException异常,可以查看源码865行),接着判断minCapacity是否大于当前ArrayList内部数组长度,大于的话调用grow方法对内部数组elementData扩容,grow方法代码如下:
private void grow(int minCapacity) {
int oldCapacity = elementData.length;//得到原始数组的长度
int newCapacity = oldCapacity + (oldCapacity >> 1);//新数组的长度等于原数组长度的1.5倍
if (newCapacity - minCapacity < 0)//当新数组长度仍然比minCapacity小,则为保证最小长度,新数组等于minCapacity
newCapacity = minCapacity;
//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 = 2147483639
if (newCapacity - MAX_ARRAY_SIZE > 0)//当得到的新数组长度比 MAX_ARRAY_SIZE 大时,调用 hugeCapacity 处理大数组
newCapacity = hugeCapacity(minCapacity);
//调用 Arrays.copyOf 将原数组拷贝到一个大小为newCapacity的新数组(注意是拷贝引用)
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) //
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? //minCapacity > MAX_ARRAY_SIZE,则新数组大小为Integer.MAX_VALUE
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
对于 ArrayList 集合添加元素,我们总结一下:
①、当通过 ArrayList() 构造一个空集合,初始长度是为0的,第 1 次添加元素,会创建一个长度为10的数组,并将该元素赋值到数组的第一个位置。
②、第 2 次添加元素,集合不为空,而且由于集合的长度size+1是小于数组的长度10,所以直接添加元素到数组的第二个位置,不用扩容。
③、第 11 次添加元素,此时 size+1 = 11,而数组长度是10,这时候创建一个长度为10+10*0.5 = 15 的数组(扩容1.5倍),然后将原数组元素引用拷贝到新数组。并将第 11 次添加的元素赋值到新数组下标为10的位置。
④、第 Integer.MAX_VALUE - 8 = 2147483639,然后 2147483639%1.5=1431655759(这个数是要进行扩容) 次添加元素,为了防止溢出,此时会直接创建一个 1431655759+1 大小的数组,这样一直,每次添加一个元素,都只扩大一个范围。
⑤、第 Integer.MAX_VALUE - 7 次添加元素时,创建一个大小为 Integer.MAX_VALUE 的数组,在进行元素添加。
⑥、第 Integer.MAX_VALUE + 1 次添加元素时,抛出 OutOfMemoryError 异常。
注意:能向集合中添加 null 的,因为数组可以有 null 值存在。
2、删除元素
①、根据索引删除元素
public E remove(int index) {
rangeCheck(index);//判断给定索引的范围,超过集合大小则抛出异常
modCount++;
E oldValue = elementData(index);//得到索引处的删除元素
int numMoved = size - index - 1;
if (numMoved > 0)//size-index-1 > 0 表示 0<= index < (size-1),即索引不是最后一个元素
//通过 System.arraycopy()将数组elementData 的下标index+1之后长度为 numMoved的元素拷贝到从index开始的位置
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; //将数组最后一个元素置为 null,便于垃圾回收
return oldValue;
}
remove(int index) 方法表示删除索引index处的元素,首先通过 rangeCheck(index) 方法判断给定索引的范围,超过集合大小则抛出异常;接着通过 System.arraycopy 方法对数组进行自身拷贝。
②、直接删除指定元素
public boolean remove(Object o) {
if (o == null) {//如果删除的元素为null
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {//不为null,通过equals方法判断对象是否相等
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; //
}
remove(Object o)方法是删除第一次出现的该元素。然后通过System.arraycopy进行数组自身拷贝。
3.修改元素
通过调用 set(int index, E element) 方法在指定索引 index 处的元素替换为 element。并返回原数组的元素。
public E set(int index, E element) {
rangeCheck(index);//判断索引合法性
E oldValue = elementData(index);//获得原数组指定索引的元素
elementData[index] = element;//将指定所引处的元素替换为 element
return oldValue;//返回原数组索引元素
}
通过调用 rangeCheck(index) 来检查索引合法性。
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
当索引为负数时,会抛出 java.lang.ArrayIndexOutOfBoundsException 异常。当索引大于集合长度时,会抛出 IndexOutOfBoundsException 异常。
4、查找元素
①、根据索引查找元素
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
同理,首先还是判断给定索引的合理性,然后直接返回处于该下标位置的数组元素。
②、根据元素查找索引
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;
}
注意:indexOf(Object o) 方法是返回第一次出现该元素的下标,如果没有则返回 -1。
还有 lastIndexOf(Object o) 方法是返回最后一次出现该元素的下标。
5.四个内部类总结
1.静态内部类ArrayListSpliterator
它的方法:
ArrayListSpliterator是静态内部类,且不允许继承(final修饰),在ArrayList中的spliterator方法中返回的正是ArrayListSpliterator对象,每个ArrayListSpliterator表示ArrayList中实现的并发迭代器! 不懂这个下一个以后再说
2.内部类Itr
Itr作为ArrayList的普通内部类,Itr实现了Iterator接口,通过ArrayList的iterator()方法得到的是Itr对象,每个Itr对象表示顺序(线性)迭代器对象!
定义:
是对迭代器接口的具体实现:
hasNext():判断还存不存在元素 next():获取一个元素的值
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
//判断游标位置如果大于最大值报错
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
//符合条件获取下一元素的值
return (E) elementData[lastRet = i];
}
remove()方法:删除,没有啥好讲的
expectedModCount,这个属性在Itr类初始化时被赋予ArrayList对象的modCount属性的值。调用方法的时候改变值
具体请看:www.cnblogs.com/allmignt/p/…
3.内部类SuList 这里出现了 SubList 类,这也是 ArrayList 中的一个内部类。 注意:返回的是原集合的视图,也就是说,如果对 subList 出来的集合进行修改或新增 操作,那么原始集合也会发生同样的操作。
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
List<String> subList = list.subList(0, 1);
for(String str : subList){
System.out.print(str + " ");//a
}
subList.add("d");
System.out.println(subList.size());//2
System.out.println(list.size());//4,原始集合长度也增加了
想要独立出来一个集合,解决办法如下:
List subList = new ArrayList<>(list.subList(0, 1));
6.总结
首先是特点: 底层是数组,那么存储过程是有序的,其次可存入重复的值,并且可以存入null值, 接下来分析时间空间复杂度: 在空间上大部分的时候处于扩容1.5倍,相对来说造成了一定内存损耗, 时间上,索引搜索时间复杂度O(1)或者O(N) 根据索引查找元素时间复杂度是O(1) 根据元素查找最坏的情况下是O(n),执⾏ add(E e) ⽅法的时候, ArrayList 会默认在将 指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插⼊和删除元素的话( add(int index, E element) )时间复杂度就为 O(n-i)。因为在进⾏上述操 作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执⾏向后位/向前移⼀位的操作。 分析可得出 查询的平均时间复杂度为O(1) 增删改时间复杂度为O(n) 并且线程不安全