java collections_Java集合基础的详细介绍(二)
七.Collections工具类
Collections 是一个操作 Collection 和 Map 等集合的工具类。Collections 中提供了一系列静态的方法对集合元素进行排序、查询和替换等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。
1.排序操作
![](/assets/blank.gif)
示例:
List list = new ArrayList();list.add(56);list.add(12);list.add(34);list.add(89);list.add(-53);list.add(0);list.add(34);// 反转Collections.reverse(list);System.out.println(list);// 随机排序Collections.shuffle(list);System.out.println(list);// 两个索引位置的元素进行交换Collections.swap(list,1,2);System.out.println(list);// Collections自然排序和定制排序方法底层都还是调用的 Arrays.sort(a, (Comparator) c)方法// 自然排序Collections.sort(list);System.out.println(list);// List接口中sort默认方法部分源码(JDK8中接口新特性)default void sort(Comparator super E> c) { Object[] a = this.toArray();// 将集合转为数组 Arrays.sort(a, (Comparator) c);// 调用Arrays的sort方法 ...}
2.查找、替换操作
![](/assets/blank.gif)
示例:
// 元素出现次数int frequency = Collections.frequency(list, 34);System.out.println(frequency);// 复制//报异常:IndexOutOfBoundsException("Source does not fit in dest")// List dest = new ArrayList();// Collections.copy(dest,list);//正确的:目标集合size大小 大于等于 原集合size大小List dest = Arrays.asList(new Object[list.size()]);System.out.println(dest.size());//list.size();Collections.copy(dest,list);// Collections中void copy(List dest,List src)方法源码public static void copy(List super T> dest, List extends T> src) { int srcSize = src.size(); // 若原集合size大小 大于 目标集合size大小(并不是集合初始化容量大小),则抛异常 if (srcSize > dest.size()) throw new IndexOutOfBoundsException("Source does not fit in dest"); ...}
3.同步控制操作
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
List list = new ArrayList();list.add(56);list.add(12);//返回的list2即为线程安全的ListList list2 = Collections.synchronizedList(list);
八.源码学习
1.ArrayList
ArrayList 底层是数组队列,相当于动态数组.与 Java 中的数组相比,它的容量能动态增长.线性表的顺序存储,插入删除元素的时间复杂度为O(n),求表长以及增加元素,取第 i 元素的时间复杂度为O(1) .
它继承于 AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。
ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
ArrayList 实现了Cloneable 接口,即覆盖了函数 clone(),能被克隆。
ArrayList 实现java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。
和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Collections中的静态方法 List synchronizedList(List list) 将其转为线程安全的。
1.1 JDK7
ArrayList是List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。
1.1.1 核心源码
public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable {/*** 1.属性*/// 底层使用数组实现private transient Object[] elementData;// 声明对象数组变量/*** 2.构造方法*/// 构造一个默认初始容量为 10 的空列表public ArrayList() { this(10); }// 构造一个指定初始容量的空列表public ArrayList(int initialCapacity) {// 10 super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity];// 分配空间10 } // 构造一个包含指定 collection 的元素的列表 // 这些元素按照该 collection 的迭代器返回它们的顺序排列的 public ArrayList(Collection extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } /** * 3.存储 */ // 用指定的元素替代此列表中指定位置上的元素,并返回以前位于该位置上的元素。 public E set(int index, E element) { RangeCheck(index); E oldValue = (E) elementData[index]; elementData[index] = element; return oldValue; } // 将指定的元素添加到此列表的尾部。 public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }// 将指定的元素插入此列表中的指定位置。// 如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加 1)。 public void add(int index, E element) { rangeCheckForAdd(index);// 如果数组长度不足,将进行扩容 ensureCapacityInternal(size + 1); // Increments modCount!! // 将 elementData 中从 Index 位置开始、长度为 size-index 的元素,// 拷贝到从下标为 index+1 位置开始的新的 elementData 数组中。// 即将当前位于该位置的元素以及所有后续元素右移一个位置。 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } // 从指定的位置开始,将指定 collection 中的所有元素插入到此列表中。 public boolean addAll(int index, Collection extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index; if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; } /** * 4.读取 */ // 返回此列表中指定位置上的元素。 public E get(int index) { rangeCheck(index); return elementData(index); } /** * 5.删除 * ArrayList 提供了根据下标或者指定对象两种方式的删除功能 */// 移除此列表中指定位置上的元素。public E remove(int index) { rangeCheck(index);// 快速失败的机制,通过记录 modCount 参数来实现 modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work return oldValue; } // 移除此列表中首次出现的指定元素(如果存在)。这是应为ArrayList中允许存放重复的元素。 public boolean remove(Object o) { // 由于 ArrayList 中允许存放 null,因此下面通过两种情况来分别处理。 if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { // 类似 remove(int index),移除列表中指定位置上的元素。 fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }}
1.1.2 底层扩容
// 添加元素举例public boolean add(E e) { ensureCapacityInternal(size + 1); // 有效长度+1 elementData[size++] = e; return true; }private void ensureCapacityInternal(int minCapacity) {// 确认实际容量 modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0)// 当实际容量大于10时,进行扩容 grow(minCapacity); }private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;private void grow(int minCapacity) {// 11 // overflow-conscious code int oldCapacity = elementData.length;// 10 int newCapacity = oldCapacity + (oldCapacity >> 1);// 10 + 10/2 相当于原先默认的1.5倍 if (newCapacity - minCapacity < 0)// 若扩容后比实际容量还小 newCapacity = minCapacity;// 赋值为实际容量 if (newCapacity - MAX_ARRAY_SIZE > 0)// 若扩容后比(Integer.MAX_VALUE-8)还大 newCapacity = hugeCapacity(minCapacity);// 赋值为Integer.MAX_VALUE // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity);// 赋值为新的数组,数组长度为扩容后的长度 }private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
数组扩容通过一个公开的方法 ensureCapacity(int minCapacity) 来实现。会将老数组中的元素重新拷贝一份到新的 数组中,每次数组容量的增长大约是其原容量的 1.5 倍。在实际中我们可以在构造 ArrayList 实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用 ensureCapacity 方法来手动增加 ArrayList 实例的容量, 以减少递增式再分配的数量。
ArrayList 还给我们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它可以通过trimToSize 方法来实现。
public void trimToSize() { modCount++; int oldCapacity = elementData.length; if (size < oldCapacity) { elementData = Arrays.copyOf(elementData, size); } }
1.2 JDK8
ArrayList 是一个动态数组,实现了 List 接口以及 list 相关的所有方法,它允许所有元素的插入,包括 null。另外,ArrayList 和 Vector 除了线程不同步之外,大致相等。
JDK8 : 默认构造空实例,大小是 0;只有在使用到时,才会通过grow方法创建一个大小为 10 的数组
JDK7 : 默认构造初始容量为 10 的空列表
在扩容上代码优点区别,但原理都是相同的,都是原容量的1.5倍.
1.2.1 核心源码
// @since 1.2public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable { /** * 1.属性 * ArrayList所有的方法都是建立在 elementData 属性之上。 */ // 默认容量的大小 private static final int DEFAULT_CAPACITY = 10; // 空数组常量:用于空实例 private static final Object[] EMPTY_ELEMENTDATA = {}; // 默认的空数组常量:用于默认大小空实例的共享空数组实例。 // 把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 存放元素的数组,从这可以发现 ArrayList 的底层实现就是一个 Object数组 transient Object[] elementData; // 数组中包含的元素个数 private int size; // 数组的最大上限 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/*** 2.构造方法* 默认情况下,elementData 是一个大小为 0 的空数组;* 当我们指定了初始大小的时候 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); } }// 实例化:空实例,大小是 0;只有在使用到时,才会通过grow方法创建一个大小为 10 的数组public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;// 空数组实例 } // 形参为Collection实现类对象:可将set对象转为list对象 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.get(int index)方法 * 根据索引获取ArrayList中的元素 */ // 根据索引获取集合中元素 public E get(int index) { rangeCheck(index); return elementData(index); } // 判断索引是否越界 private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } // 直接通过数组下表获取元素 @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; } /** * 4.add方法 * 在插入元素之前,先检查是否需要扩容,后把元素添加到数组中最后一个元素的后面 */// 在数组末尾添加元素,复杂度为O(1)public boolean add(E e) { ensureCapacityInternal(size + 1); elementData[size++] = e; return true; } // 在索引为index处插入元素,复杂度为O(n) public void add(int index, E element) { rangeCheckForAdd(index);// 判断索引是否越界或是否小于0 ensureCapacityInternal(size + 1); // 调用一个 native 的复制方法,把 index 位置开始的元素都往后挪一位 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }// 确认是否是空数组private void ensureCapacityInternal(int minCapacity) {// 当 elementData 为空数组时,它会使用默认的大小(10)去扩容 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }// 确认是否需要扩容private void ensureExplicitCapacity(int minCapacity) {// 快速失败的机制,通过记录 modCount 参数来实现 modCount++; // 实际容量比默认长度大时,进行扩容 if (minCapacity - elementData.length > 0) grow(minCapacity); } // ArrayList 每次扩容都是扩 1.5 倍 // 然后调用 Arrays 类的 copyOf 方法,把元素重新拷贝到一个新的数组中去 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); // 初始化,赋值为新的数组,长度为10 elementData = Arrays.copyOf(elementData, newCapacity); } // 比较minCapacity和 MAX_ARRAY_SIZE private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }/*** 5.set方法*/// 把下标为index的元素替换为element,复杂度为O(1)public E set(int index, E element) { rangeCheck(index);// 判断索引是否越界 E oldValue = elementData(index); elementData[index] = element; return oldValue; } /** * 6.remove方法 */ // 删除索引为index处的元素,复杂度为O(n) public E remove(int index) { rangeCheck(index);// 判断索引是否越界 modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0)// 调用一个 native 的复制方法,把 index 位置开始的元素都往前挪一位 System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; } /** * 7.size方法 */ // 返回数组中元素的个数,时间复杂度为 O(1),返回的并不是数组的实际大小 public int size() { return size; } /** * 8.indexOf方法和lastIndexOf方法 */ // 返回从前往后遍历查找第一个等于给定元素的值的下标,时间复杂度为O(n) 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; } // 返回从后往前遍历查找第一个等于给定元素的值的下标,时间复杂度为O(n) public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--)// 从后往前遍历 if (o.equals(elementData[i])) return i; } return -1; } /** * 从列表中删除所有元素。 */ public void clear() { modCount++; // 把数组中所有的元素的值设为null for (int i = 0; i < size; i++) elementData[i] = null; size = 0; } /** *以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。 *返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的数 组)。 *因此,调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的API之间的桥梁。 */ public Object[] toArray() { return Arrays.copyOf(elementData, size); } /** * 返回此ArrayList实例的浅拷贝。 (元素本身不被复制。) */ public Object clone() { try { ArrayList> v = (ArrayList>) super.clone(); //Arrays.copyOf功能是实现数组的复制,返回复制后的数组。参数是被复制的数组和复制的长度 v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // 这不应该发生,因为我们是可以克隆的 throw new InternalError(e); } }}
1.2.2 System.arraycopy()和Arrays.copyOf()
System.arraycopty():
/** * 在此列表中的指定位置插入指定的元素。 *先调用 rangeCheckForAdd 对index进行界限检查; *然后调用 ensureCapacityInternal 方法保证capacity足够大; *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。 */ public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! //arraycopy()方法实现数组自己复制自己 //elementData:源数组;index:源数组中的起始位置; //elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++;
Arrays.copyOf():
/** *以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。 *返回的数组将是“安全的”,因为该列表不保留对它的引用。(换句话说,这个方法必须分配一个新的数组) *因此,调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的API之间的桥梁。 */ public Object[] toArray() { //elementData:要复制的数组;size:要复制的长度 return Arrays.copyOf(elementData, size); }
两个方法的源码:
// Arrays类public static T[] copyOf(U[] original, int newLength, Class extends T[]> newType) { @SuppressWarnings("unchecked") T[] copy = ((Object)newType == (Object)Object[].class)// 新建数组 ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0,// 拷贝 Math.min(original.length, newLength)); return copy;}// System类public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
联系: 看两者源代码可以发现copyOf()内部调用了System.arraycopy()
区别:
arraycopy()需要目标数组,将原数组拷贝到你自己定义的数组里,而且可以选择拷贝的起点和长度以及放入新数组中的位置
copyOf()是系统自动在内部新建一个数组,并返回该数组。
1.2.3 内部类
(1)private class Itr implements Iterator (2)private class ListItr extends Itr implements ListIterator (3)private class SubList extends AbstractList implements RandomAccess (4)static final class ArrayListSpliterator implements Spliterator
ArrayList有四个内部类,
其中的Itr是实现了Iterator接口,同时重写了里面的hasNext(), next(), remove() 等方法;
其中的ListItr 继承 Itr,实现了ListIterator接口,同时重写了hasPrevious(), nextIndex(), previousIndex(), previous(), set(E e), add(E e) 等方法;
Iterator和ListIterator的区别: ListIterator在Iterator的基础上增加了添加对象,修改对象,逆向遍历等方法,这些是Iterator不能实现的。
1.3 Vector
Vector很多方法都跟 ArrayList 一样,只是多加了个 synchronized 来保证线程安全.部分源码:
// @since JDK1.0public class Vector extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable{// 每次扩容只扩 capacityIncrement 个空间protected int capacityIncrement;// Vector 的默认大小也是 10,而且它在初始化的时候就已经创建了数组了public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; this.capacityIncrement = capacityIncrement; } public Vector(int initialCapacity) { this(initialCapacity, 0); } public Vector() { this(10); } // newCapacity 默认情况下是两倍的 oldCapacity; // 当指定了 capacityIncrement 的值之后,newCapacity 变成了oldCapacity+capacityIncrement private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }}
1.4 总结
JDK8 ArrayList 与 Vector 比较 :
ArrayList 创建时的大小为 0;当加入第一个元素时,进行第一次扩容时,默认容量大小为 10。
ArrayList 每次扩容都以当前数组大小的 1.5 倍去扩容。
Vector 创建时的默认大小为 10。
Vector 每次扩容都以当前数组大小的 2 倍去扩容。当指定了 capacityIncrement 之后,每次扩容仅在原先基础上增加 capacityIncrement 个单位空间。
ArrayList 和 Vector 的 add、get、size 方法的复杂度都为 O(1),remove 方法的复杂度为 O(n)。
ArrayList 是非线程安全的,Vector 是线程安全的。
2.LinkedList
LinkedList 是通过一个双向链表来实现的,它允许插入所有元素,包括 null,同时,它是线程不同步的。LinkedList底层的链表结构使它支持高效的插入和删除操作.如果想使LinkedList变成线程安全的,可以调用Collections中的静态方法 List synchronizedList(List list) 将其转为线程安全.
2.1 内部结构
![](/assets/blank.gif)
双向链表每个结点除了数据域之外,还有一个前指针和后指针,分别指向前驱结点和后继结点(如果有前驱/后继的话)。另外,双向链表还有一个 first 指针,指向头节点,和 last 指针,指向尾节点。
LinkedList类中的一个内部私有类Node:
// 双向链表节点: 每个节点的结构private static class Node {// 内部类:只有当前类需要使用,外面类不需要使用时 E item;// 泛型,obj对象数据 Node next;// 后继节点;下一个元素节点地址:指向下一个元素的指针 Node prev;// 前驱节点;上一个元素节点地址:指向上一个元素的指针// 元素节点分为三部分:前驱节点,本节点的值,后继结点 Node(Node prev, E element, Node next) { this.item = element; this.next = next; this.prev = prev; } }
2.2 属性和构造器
// @since 1.2public class LinkedList extends AbstractSequentialList implements List, Deque, Cloneable, java.io.Serializable{ //链表的节点个数//指向头节点的指针 transient int size = 0; //指向头节点的指针 transient Node first; //指向尾节点的指针 transient Node last; // 空构造方法 public LinkedList() { } // 用已有的集合创建链表的构造方法 public LinkedList(Collection extends E> c) { this(); addAll(c); }}
2.3 添加节点
在表头或表尾进行插入元素只需要 O(1) 的时间,而在指定位置插入元素则需要先遍历一下链表,所以复杂度为 O(n)。
2.3.1 表头添加元素
![](/assets/blank.gif)
addFirst(E e), push(E e), offerFirst(E e)
public void addFirst(E e) {// 在链表头部添加元素节点 linkFirst(e); }public void push(E e) {// 在链表头部添加元素节点 addFirst(e); }public boolean offerFirst(E e) { addFirst(e); return true; }private void linkFirst(E e) { final Node f = first; //当前节点的前驱指向 null,后继指针原来的头节点 final Node newNode = new Node<>(null, e, f); first = newNode;//头指针指向新的头节点 if (f == null)//如果链表为空,last节点也指向该节点 last = newNode; else//否则,将头节点的前驱指针指向新节点,也就是指向前一个元素 f.prev = newNode; size++; modCount++; }
2.3.2 表尾添加元素
![](/assets/blank.gif)
add(E e), addLast(E e), offer(E e), offerLast(E e)
public boolean add(E e) {// 在链表结尾添加元素节点 linkLast(e); return true; }public void addLast(E e) {// 在链表结尾添加元素节点 linkLast(e); }public boolean offer(E e) { return add(e); }public boolean offerLast(E e) { addLast(e); return true; }void linkLast(E e) {// 加为最后节点 final Node l = last;// 声明临时节点用来存储last节点 final Node newNode = new Node<>(l, e, null);// 新节点指向原先last节点 last = newNode;// 新节点作为最后一个节点 if (l == null) first = newNode; else l.next = newNode;// 原先last节点指向新节点(last节点) size++; modCount++; }
2.3.3 指定位置添加元素
![](/assets/blank.gif)
add(int index, E element)
public void add(int index, E element) {//在指定位置添加元素节点 checkPositionIndex(index);//检查索引是否处于[0-size]之间 if (index == size)//添加在链表尾部 linkLast(element); else//添加在链表中间 linkBefore(element, node(index));//一个插入节点的值,一个指定的node }void linkBefore(E e, Node succ) { // assert succ != null; final Node pred = succ.prev;//指定节点的前驱 //当前节点的前驱为指点节点的前驱,后继为指定的节点 final Node newNode = new Node<>(pred, e, succ); //更新指定节点的前驱为当前节点 succ.prev = newNode; //更新前驱节点的后继 if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; }
2.4 删除节点
2.4.1 删除头节点
remove() ,removeFirst(), pop()
public E pop() { return removeFirst(); }public E remove() { return removeFirst(); }public E removeFirst() { final Node f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); }//删除表头节点,返回表头元素的值private E unlinkFirst(Node f) { // assert f == first && f != null; final E element = f.item; final Node next = f.next; f.item = null; f.next = null; // help GC first = next;//头指针指向后一个节点 if (next == null) last = null; else next.prev = null;//新头节点的前驱为 null size--; modCount++; return element; }
2.4.2 删除尾节点
removeLast(), pollLast()
区别: removeLast()在链表为空时将抛出NoSuchElementException,而pollLast()方法返回null。
public E removeLast() { final Node l = last; if (l == null) throw new NoSuchElementException(); return unlinkLast(l); }public E pollLast() { final Node l = last; return (l == null) ? null : unlinkLast(l); }//删除表尾节点,返回表尾元素的值private E unlinkLast(Node l) { // assert l == last && l != null; final E element = l.item; final Node prev = l.prev; l.item = null; l.prev = null; // help GC last = prev;//尾指针指向前一个节点 if (prev == null) first = null; else prev.next = null;//新尾节点的后继为 null size--; modCount++; return element; }
2.4.3 删除指定节点
![](/assets/blank.gif)
remove(Object o), remove(int index)
public boolean remove(Object o) { // 删除指定元素:只会删除一个匹配的对象 if (o == null) {//如果删除对象为null for (Node x = first; x != null; x = x.next) {//从头开始遍历 if (x.item == null) {//找到元素 unlink(x);//从链表中移除找到的元素 return true; } } } else { for (Node x = first; x != null; x = x.next) {//从头开始遍历 if (o.equals(x.item)) { //找到元素 unlink(x);//从链表中移除找到的元素 return true; } } } return false; }public E remove(int index) {//删除指定位置的元素 //检查index范围 checkElementIndex(index); //将节点删除 return unlink(node(index)); }//删除指定节点,返回指定元素的值E unlink(Node x) { // assert x != null; final E element = x.item; final Node next = x.next;// 当前节点的后继 final Node prev = x.prev;// 当前节点的前驱//删除前驱指针 if (prev == null) { first = next;//如果删除的节点是头节点,令头节点指向该节点的后继节点 } else { prev.next = next;//更新前驱节点的后继为当前节点的后继 x.prev = null; }//删除后继指针 if (next == null) { last = prev;//如果删除的节点是尾节点,令尾节点指向该节点的前驱节点 } else { next.prev = prev;//更新后继节点的前驱为当前节点的前驱 x.next = null; } x.item = null; size--; modCount++; return element; }
2.5 获取节点
2.5.1 获取头节点(index=0)
getFirst(), element(), peek(), peekFirst()
区别: 在于对链表为空时的处理,是抛出异常还是返回null,其中getFirst() 和element() 方法将会在链表为空时,抛出NoSuchElementException异常
public E getFirst() { final Node f = first; if (f == null) throw new NoSuchElementException(); return f.item; }public E element() { return getFirst(); }public E peek() { final Node f = first; return (f == null) ? null : f.item; }public E peekFirst() { final Node f = first; return (f == null) ? null : f.item; }
2.5.2 获取尾节点(index=-1)
getLast(), peekLast()
区别: getLast() 方法在链表为空时,会抛出NoSuchElementException,而peekLast() 则不会.
public E getLast() { final Node l = last; if (l == null) throw new NoSuchElementException(); return l.item; } public E peekLast() { final Node l = last; return (l == null) ? null : l.item; }
2.5.3 获取指定节点
get(int index)
public E get(int index) { //检查index范围是否在size之内 checkElementIndex(index); //调用Node(index)去找到index对应的node然后返回它的值 return node(index).item; }//获取指定下标的元素Node node(int index) {//查找index对应的node // assert isElementIndex(index); //根据下标是否超过链表长度的一半,来选择从头部开始遍历还是从尾部开始遍历 if (index < (size >> 1)) { Node x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
2.6 其它方法
set(int index, E element), size(), contains(Object o), indexOf(Object o), lastIndexOf(Object o)
public E set(int index, E element) {//将此列表中指定位置的元素替换为指定的元素 checkElementIndex(index); Node x = node(index); E oldVal = x.item; x.item = element; return oldVal; }public int size() {//返回此列表的元素个数 return size; }public boolean contains(Object o) {//检查对象o是否存在于链表中 return indexOf(o) != -1; }public int indexOf(Object o) {//从头遍历找该对象的索引 int index = 0; if (o == null) { for (Node x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { for (Node x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1; }public int lastIndexOf(Object o) {//从尾遍历找该对象的索引 int index = size; if (o == null) { for (Node x = last; x != null; x = x.prev) { index--; if (x.item == null) return index; } } else { for (Node x = last; x != null; x = x.prev) { index--; if (o.equals(x.item)) return index; } } return -1; }
java collections_Java集合基础的详细介绍(二)相关推荐
- 【Java面试】基础篇详细介绍(已包含所有基础面试题)
1.final关键字的作用 2.avstract class 和interface有什么区别 3.Java集合类:list.set.queue.map.stack的特点与用法 4.说出ArrayLi ...
- java final对象_java面向对象基础_final详细介绍
一.final关键字 1.final修饰的成员变量,其实就是常量 public class Example01 { public final String userName="小李" ...
- RabbitMQ学习总结(一)——基础概念详细介绍
2019独角兽企业重金招聘Python工程师标准>>> 一.基础概念详细介绍 1.引言 你是否遇到过两个(多个)系统间需要通过定时任务来同步某些数据?你是否在为异构系统的不同进程间相 ...
- RabbitMQ学习总结(1)——基础概念详细介绍
一.基础概念详细介绍 1.引言 你是否遇到过两个(多个)系统间需要通过定时任务来同步某些数据?你是否在为异构系统的不同进程间相互调用.通讯的问题而苦恼.挣扎?如果是,那么恭喜你,消息服务让你可以很轻松 ...
- Linux shell脚本基础学习详细介绍(完整版)一
Linux shell脚本基础学习这里我们先来第一讲,介绍shell的语法基础,开头.注释.变量和 环境变量,向大家做一个基础的介绍,虽然不涉及具体东西,但是打好基础是以后学习轻松地前提. 1. Li ...
- Linux shell脚本基础学习详细介绍(完整版)
Linux shell脚本基础学习这里我们先来第一讲,介绍shell的语法基础,开头.注释.变量和 环境变量,向大家做一个基础的介绍,虽然不涉及具体东西,但是打好基础是以后学习轻松地前提. 1. Li ...
- 吊打java面试官之 Hashtable详细介绍(源码解析)和使用示例
概要 前一章,我们学习了HashMap.这一章,我们对Hashtable进行学习. 我们先对Hashtable有个整体认识,然后再学习它的源码,最后再通过实例来学会使用Hashtable. 第1部分 ...
- java中的static属性详细介绍
标题 java中static属性详细介绍 类的生命周期 代码块 静态代码块: 静态方法 静态属性和非静态属性的区别: 使用static的注意事项 继承关系中静态和非静态的关系 普通代码块和构造代码块区 ...
- Linux shell脚本基础学习详细介绍(完整版)2
详细介绍Linux shell脚本基础学习(五) Linux shell脚本基础前面我们在介绍Linux shell脚本的控制流程时,还有一部分内容没讲就是有关here document的内容这里继续 ...
最新文章
- php先分组后排序,PHP数组分组排序实例代码
- 老外听到哪些中式英文是崩溃的?(转自外贸Jackson)
- ORA-01109:数据库无法启动问题
- Phonegap + JqueryMobile常见问题
- 使用URLRewriter实现URL重写
- Java 之 String 类型
- mysql视图执行原理_MySql中的视图 触发器 存储过程,以及事物
- python opencv之 Hough圆变换
- ubuntu linux 使用常见问题
- 背景图宽度自适应及背景图合并的CSS思想
- ASP.NET 网站管理工具“安全”选项卡为什么打不开?
- css----苹果移动端以及小程序滚动模块卡顿的处理
- 数据结构与算法-浙江大学陈越数据结构第一章学习
- php漏洞eval,Web安全之代码执行漏洞
- SQL Server Always Encrypted加密使用
- 天人永隔,惟相思难断。
- CharacterController.Move called on inactive controller
- js 保留两位数且不四舍五入
- Java action name=*_* class=com..action.{1}Action method={2} 中*_*意思
- 魏则西事件与百度医疗竞价排名引发的伦理与道德问题
热门文章
- PCL滤波介绍(3)
- 【怒怼老乔】居然苹果手机IOS系统还不支持css3的transparent属性值,我去~~~~
- squid中的X-Cache和X-Cache-Lookup的意义
- 关于部署osd过程中:Device is in use by a device-mapper mapping问题解决
- [题解]RGB Substring (hard version)-前缀和(codeforces 1196D2)
- 5793. 【NOIP2008模拟】小S练跑步
- [原创]SparkR针对mysql的数据读写操作实现
- win10安装docker并结合Idea2018.1部署springboot项目
- 第三章| 3.1文件处理
- 【BZOJ4282】慎二的随机数列 乱搞