Java 集合類學習筆記
不管哪一種資料結構本質上來說都是一種容器, 都需要搞清楚兩點
(1)如何儲存
(2)儲存特點
陣列
陣列的邏輯結構是線性的, 屬於順序的儲存結構, 一旦申請到記憶體那麼記憶體就固定了
在陣列這個儲存結構中所有的資料都是緊密分佈的, 中間不能有間隔
查詢方式:
int[] numbers = new int[length];
for(int i = 0; i < numbers.length; i++){ //陣列的下表從0開始
//TODO 對陣列的操作
}
-
優點
- 按照索引查詢, 查詢效率高
-
缺點
- 新增/刪除效率低, 刪除和新增都要設計移動陣列中的元素
集合
-
java集合類庫將介面(interface)與實現(implementation)分離.
-
集合有兩個基本介面: Collection 和 Map
Collection
在java集合中,最基本的就是Collection集合
有如下方法:
boolean add(Object o); //新增元素 addAll(Collection other); //新增另一個集合中的所有元素, 結果為兩個集合的並集 boolean remove(Object o); //從當前集合中刪除第一個找到的與Object物件相同的元素 boolean removeIf(Predicate<? super E> filter); //根據過濾器移除集合中的元素 boolean isEmpty(); //判斷當前集合是否為空集合 boolean contains(Object o); //判斷是否存在該元素 boolean containsAll(Collection c); //判斷集合c中的元素是否在當前集合中都存在 int size(); //獲取當前集合長度 boolean retainAll(Collectioj coll); //當前集合僅保留與coll集合相同的元素, 保留兩個集合的交集 Object[] toArray(); //返回當前集合中所有元素的陣列 <T> T[] toArray(T[] a); //根據集合元素返回陣列 Iterator<E> iterator(); //返回一個迭代器物件 Stream<E> stream(); //返回一個Stream流 Stream<E> parallelStream(); //返回一個平行Stream流 void clear(); //刪除所有元素
在java 5時Collection介面繼承了java.lang.Iterable
介面, 因此Collection集合在java 5之後都可使用foreach
模式遍歷
Implementing this interface allows an object to be the target of the "for-each loop" statement.
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
boolean removeIf(Predicate<? super E> filter);
removeIf(filter -> filter something)
Iterator
因為Collection結構擴充套件了Iterator介面, 因此, 對於標準類庫中的任何集合都可以使用"foreach迴圈"
public interface Iterable<E>{
Iterator<T> iterator(); //for each迴圈可以處理任何實現了Iterable的物件, 因為其返回了Iterator迭代器物件
...
}
Iterator介面有四個方法
public interface Iterator<E>{
E next(); //返回迭代的下一個元素, 若沒有下一個元素的情況下進行迭代則丟擲NoSuchElementException異常
boolean hasNext(); //返回是否還有元素可以迭代
void remove(); //從collection中移除返回的最後一個元素, 若已經呼叫過remove再次呼叫或者沒有呼叫next就呼叫remve就會丟擲IllegalStateException異常
default void forEachRemaining(Consumer<? super E> action); //提供一個lambda表示式,將對迭代器的每一個元素一一呼叫這個lambda表示式
}
遍歷的順序取決於集合型別, 如果是ArrayList, 迭代器將按照索引從0開始, 每迭代一次, 索引值加1. 如果是訪問HashSet中的元素, 則會按照一種基本上隨機的順序獲得元素.
HashSet使用的是HashMap中的迭代器, HashMap中的迭代器按照key值進行消費, 所以具有一定的隨機性.
public void forEachRemaining(Consumer<? super K> action) {
int i, hi, mc;
if (action == null) //若消費動作為空則丟擲空指標異常
throw new NullPointerException();
HashMap<K,V> m = map;
/**
* Node中有四個值
* final int hash; 節點的hash值
* final K key; 節點key
* V value; 節點value
* Node<K,V> next; 下個節點的引用
*/
Node<K,V>[] tab = m.table;
if ((hi = fence) < 0) { // fence: one past last index 過去的最後一個索引
mc = expectedModCount = m.modCount; //表被修改次數, 保證執行緒安全
hi = fence = (tab == null) ? 0 : tab.length;
}
else
mc = expectedModCount;
if (tab != null && tab.length >= hi &&
(i = index) >= 0 && (i < (index = hi) || current != null)) {
Node<K,V> p = current;
current = null;
do {
if (p == null)
p = tab[i++];
else {
action.accept(p.key); //根據指標指向node值進行消費
p = p.next;
}
} while (p != null || i < hi);
if (m.modCount != mc)
throw new ConcurrentModificationException(); //如果遍歷的過程中被修改, 丟擲ConcurrentModificationException異常
}
}
ListItr extends Itr implements ListIterator
ArrayList中關於ListIterator的實現(列表迭代器)
/**
* An optimized version of AbstractList.ListItr
*/
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super(); //顯示呼叫父類構造器
cursor = index; //遊標指向當前索引
}
public boolean hasPrevious() {
return cursor != 0; //遊標不為初始值0
}
public int nextIndex() {
return cursor; //返回遊標指向的元素索引
}
public int previousIndex() {
return cursor - 1; //返回遊標上一個索引
}
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification(); //檢查修改次數
int i = cursor - 1; //賦值為遊標前一個索引(返回的當前物件)
if (i < 0)
throw new NoSuchElementException(); //元素預設, 和Iterator中沒有元素的情況下呼叫next()丟擲異常一致
//non-private to simplify nested class access(用於簡化巢狀類的訪問), 代表元陣列
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException(); //檢查陣列是否被修改過
cursor = i; //遊標向前移
return (E) elementData[lastRet = i]; //lastRet 最後返回的資料下標
}
public void set(E e) {
if (lastRet < 0) //集合做remove或add操作之後lastRet置為-1, 對於移除的元素沒法進行設定, 所以丟擲異常
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(lastRet, e); //設定返回位置的元素為e
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {
checkForComodification(); //檢查集合前後一致性
try {
int i = cursor; //設定當前遊標位置
ArrayList.this.add(i, e); //判斷容量是否夠, 然後呼叫System.arraycopy(datas, index, data, index +1, size - index)方法,然後賦值和容量增加
cursor = i + 1; //遊標+1
lastRet = -1; //新增元素後最後修改元素重置為1
expectedModCount = modCount; //集合修改操作時modCount增加
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
/* * @param src the source array. * @param srcPos starting position in the source array. * @param dest the destination array. * @param destPos starting position in the destination data. * @param length the number of array elements to be copied. * @exception IndexOutOfBoundsException if copying would cause * access of data outside array bounds. * @exception ArrayStoreException if an element in the <code>src</code> * array could not be stored into the <code>dest</code> array * because of a type mismatch. * @exception NullPointerException if either <code>src</code> or * <code>dest</code> is <code>null</code>. */ public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
從源陣列src取元素, 範圍為下標srcPos到srcPos+length-1, 存放到目標陣列dest中, 範圍為destPos到destPos+length-1
它是一個靜態本地方法,由虛擬機器實現此處作用就是陣列的擴容
JAVA核心技術卷中提及了一個推論: 可以將Iterator.next 與 InputStream.read看成等效的: 從資料流中讀取一個位元組, 就會自動的消耗掉這個位元組. 下一次呼叫read將會消耗並返回輸入的下一個位元組. 用同樣的方式, 反覆的呼叫next就可以讀取集合中的所有元素
List
list是一個有序集合. 元素會增加到容器中的特定位置.
-
有兩種訪問List集合元素的方法
- 使用迭代器Iterator訪問
- 使用一個整數索引來訪問(隨機訪問 Random Access), get(int index);
-
List介面的實現類
- Vector類
- ArrayList類
- Stack類
- LinkedList類
default void replaceAll(UnaryOperator<E> operator); //對集合中所有元素執行此操作
default void sort(Comparator<? super E> c); //對集合中元素進行排序, 預設呼叫Arrays.sort
E get(int index); //根據索引位置取得元素
E set(int index, E element); //設定索引位置的元素
void add(int index, E element); //在索引位置新增元素
E remove(int index); //移除索引位置的元素
int indexOf(Object o); //查詢到的第一個該元素的位置
int lastIndexOf(Object o); //查詢到的最後一個該元素的位置
List<E> subList(int fromIndex, int toIndex); //集合切割
default Spliterator<E> spliterator(); //用於遍歷和區分元素物件, 提供了單獨和批量遍歷元素的功能
ListIterator<E> listIterator(); //返回一個列表迭代器, 用來訪問列表中的元素
**replaceAll(UnaryOperator
對集合中的所有元素執行操作並返回
default void replaceAll(UnaryOperator<E> operator) { Objects.requireNonNull(operator); final ListIterator<E> li = this.listIterator(); while (li.hasNext()) { li.set(operator.apply(li.next())); } }
java.util.function.UnaryOperator是一個功能介面,它擴充套件了java.util.function.Function介面。他的傳入型別和返回型別為同一型別
/** * Represents an operation on a single operand that produces a result of the * same type as its operand. This is a specialization of {@code Function} for * the case where the operand and result are of the same type. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is {@link #apply(Object)}. * * @param <T> the type of the operand and result of the operator * * @see Function * @since 1.8 */ @FunctionalInterface public interface UnaryOperator<T> extends Function<T, T> { /** * Returns a unary operator that always returns its input argument. * * @param <T> the type of the input and output of the operator * @return a unary operator that always returns its input argument */ static <T> UnaryOperator<T> identity() { return t -> t; } }
interface Function 介面方法R apply(T t); : 接受泛型物件T並返回泛型物件R
/** * Applies this function to the given argument. * * @param t the function argument * @return the function result */ R apply(T t);
例項:
@Test
public void unaryOperatorTest(){
List<String> list1 = Arrays.asList("abc", "def", "ghi");
UnaryOperator<String> operator1 = (v) -> v.substring(0,1);
list1.replaceAll(operator1);
list1.forEach(System.out::print);
System.out.println();
List<Integer> list2 = Arrays.asList(1, 2, 3);
UnaryOperator<Integer> operator2 = (v) -> v * 2;
list2.replaceAll(operator2);
list2.forEach(System.out::print);
System.out.println();
}
輸出:
adg
246
List sort(Comparator<? super E> c);
List中預設呼叫Arrays.sort(T[] a, Comparator<? super T> c)方法進行分類(排序), 排序完之後使用迭代器賦值給源集合
default void sort(Comparator<? super E> c) { Object[] a = this.toArray(); Arrays.sort(a, (Comparator) c); ListIterator<E> i = this.listIterator(); for (Object e : a) { i.next(); i.set((E) e); } }
Arrays.sort(T[] a, Comparator<? super T> c);
public static <T> void sort(T[] a, Comparator<? super T> c) { if (c == null) { //若沒有傳入比較器則使用預設排序 sort(a); } else { if (LegacyMergeSort.userRequested) //使用者請求傳統歸併排序(jdk5的傳統排序方法, 存在缺陷) legacyMergeSort(a, c); else TimSort.sort(a, 0, a.length, c, null, 0, 0); } }
TimSort.sort(T[] a, int lo, int hi, Comparator<? super T> c, T[] work, int workBase, int workLen);
/** * Sorts the given range, using the given workspace array slice * for temp storage when possible. This method is designed to be * invoked from public methods (in class Arrays) after performing * any necessary array bounds checks and expanding parameters into * the required forms. * * @param a the array to be sorted * @param lo the index of the first element, inclusive, to be sorted * @param hi the index of the last element, exclusive, to be sorted * @param c the comparator to use * @param work a workspace array (slice) * @param workBase origin of usable space in work array * @param workLen usable size of work array * @since 1.8 */ static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c, T[] work, int workBase, int workLen) { // 斷言傳入比較器, 排序陣列不為空, 起始索引大於0, 結束索引大於等於起始索引且不大於陣列長度 assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length; int nRemaining = hi - lo; //結束索引減去起始索引為需要排序的長度 if (nRemaining < 2) return; // Arrays of size 0 and 1 are always sorted // private static final int MIN_MERGE = 32;如果小於32的長度則進行二分排序 // If array is small, do a "mini-TimSort" with no merges if (nRemaining < MIN_MERGE) { int initRunLen = countRunAndMakeAscending(a, lo, hi, c); //返回升序片段長度 binarySort(a, lo, hi, lo + initRunLen, c); return; } /** * March over the array once, left to right, finding natural runs, * extending short natural runs to minRun elements, and merging runs * to maintain stack invariant. */ TimSort<T> ts = new TimSort<>(a, c, work, workBase, workLen); int minRun = minRunLength(nRemaining); //對需要排序的長度進行分片, 返回分片長度 do { // Identify next run int runLen = countRunAndMakeAscending(a, lo, hi, c); //取得該分片長度內的最小升序片段長度 // If run is short, extend to min(minRun, nRemaining) if (runLen < minRun) { //若有序長度等於需要排序的長度則無需排序 int force = nRemaining <= minRun ? nRemaining : minRun; binarySort(a, lo, lo + force, lo + runLen, c); runLen = force; } // Push run onto pending-run stack, and maybe merge ts.pushRun(lo, runLen); //將起始位置和長度入棧 ts.mergeCollapse(); //有序片段合併 // Advance to find next run lo += runLen; //增加排序完成的片段, 起始位置右移 nRemaining -= runLen; //減少需要排序的片段長度 } while (nRemaining != 0); // Merge all remaining runs to complete sort assert lo == hi; ts.mergeForceCollapse(); assert ts.stackSize == 1; }
countRunAndMakeAscending(T[] a, int lo, int hi, Comparator<? super T> c)
查詢有序片段長度並返回
private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi, Comparator<? super T> c) { assert lo < hi; int runHi = lo + 1; if (runHi == hi) return 1; // Find end of run, and reverse range if descending //查詢升序或降序片段的最後一位 if (c.compare(a[runHi++], a[lo]) < 0) { // Descending //依次對前後兩位進行比較 while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0) runHi++; reverseRange(a, lo, runHi); //若為降序則進行反轉, 反轉成升序 } else { // Ascending //依次對前後兩位進行比較 while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0) runHi++; } return runHi - lo; }
binarySort(T[] a, int lo, int hi, int start,Comparator<? super T> c)
使用二分查詢進行排序
private static <T> void binarySort(T[] a, int lo, int hi, int start, Comparator<? super T> c) { assert lo <= start && start <= hi; //start為有序片段末尾索引 if (start == lo) start++; for ( ; start < hi; start++) { T pivot = a[start]; // Set left (and right) to the index where a[start] (pivot) belongs int left = lo; int right = start; assert left <= right; /* * Invariants: * pivot >= all in [lo, left). * pivot < all in [right, start). */ //查詢到需要插入的位置(這一部分是已經排好序的片段) while (left < right) { int mid = (left + right) >>> 1; //無符號右移(等價於left+reight/2) if (c.compare(pivot, a[mid]) < 0) right = mid; else left = mid + 1; } assert left == right; /* * The invariants still hold: pivot >= all in [lo, left) and * pivot < all in [left, start), so pivot belongs at left. Note * that if there are elements equal to pivot, left points to the * first slot after them -- that's why this sort is stable. * Slide elements over to make room for pivot. */ int n = start - left; // The number of elements to move // Switch is just an optimization for arraycopy in default case //進行插入操作 switch (n) { case 2: a[left + 2] = a[left + 1]; case 1: a[left + 1] = a[left]; break; default: System.arraycopy(a, left, a, left + 1, n); } a[left] = pivot; } }
TimSort int minRunLength(int n)
進行分片操作
private static int minRunLength(int n) { assert n >= 0; //斷言需要分片的長度大於0 int r = 0; // Becomes 1 if any 1 bits are shifted off while (n >= MIN_MERGE) { r |= (n & 1); //若n為奇數則用r進行標記 n >>= 1; //長度折半 } return n + r; }
TimSort void pushRun(int runBase, int runLen)
將這個排序片段的起始位置和長度入棧
/** * Pushes the specified run onto the pending-run stack. * * @param runBase index of the first element in the run * @param runLen the number of elements in the run */ private void pushRun(int runBase, int runLen) { this.runBase[stackSize] = runBase; this.runLen[stackSize] = runLen; stackSize++; }
TimSort void mergeAt
對已經排好序的連續片段進行合併
private void mergeAt(int i) { assert stackSize >= 2; assert i >= 0; assert i == stackSize - 2 || i == stackSize - 3; int base1 = runBase[i]; int len1 = runLen[i]; int base2 = runBase[i + 1]; int len2 = runLen[i + 1]; assert len1 > 0 && len2 > 0; assert base1 + len1 == base2; /* * Record the length of the combined runs; if i is the 3rd-last * run now, also slide over the last run (which isn't involved * in this merge). The current run (i+1) goes away in any case. */ runLen[i] = len1 + len2; if (i == stackSize - 3) { runBase[i + 1] = runBase[i + 2]; runLen[i + 1] = runLen[i + 2]; } stackSize--; /* * Find where the first element of run2 goes in run1. Prior elements * in run1 can be ignored (because they're already in place). */ int k = gallopRight(a[base2], a, base1, len1, 0, c); assert k >= 0; base1 += k; len1 -= k; if (len1 == 0) return; /* * Find where the last element of run1 goes in run2. Subsequent elements * in run2 can be ignored (because they're already in place). */ len2 = gallopLeft(a[base1 + len1 - 1], a, base2, len2, len2 - 1, c); assert len2 >= 0; if (len2 == 0) return; // Merge remaining runs, using tmp array with min(len1, len2) elements if (len1 <= len2) mergeLo(base1, len1, base2, len2); else mergeHi(base1, len1, base2, len2); }
LinkedList
-
LinkList是一個有序集合, 將每個物件存放在單獨的連結中, 每個連結存放著下一個連結的引用
-
在java中所有的連結串列都是雙向連結的
-
JDK1.6之後LinkedList實現了Deque介面. 如果要使用堆疊結構的集合, 也可以考慮使用LinkedList而不是Stack
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first; //頭結點
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last; //尾節點
public E getFirst(); //取得頭結點中的元素
public E getLast(); //取得尾結點中的元素
public E removeFirst(); //移除頭結點,頭結點後移
public E removeLast(); //移除尾結點, 尾結點前移
public void addFirst(E e); //新增新的頭結點
public void addLast(E e); //新增新的尾結點
public boolean add(E e); //新增一個元素, 實際上呼叫addLast(E e);
Node<E> node(int index); //根據索引位置取得節點
ListIterator<E> listIterator(int index); //返回new ListItr(int index);
...
}
連結串列中的節點
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;
}
}
建議使用listIterator返回迭代器對LinkedList進行操作, 因為LInkedList的隨機訪問每一次都要遍歷連結串列, 效率低下
for(int i = 0; i < list.size(); i++){
do something with list.get(i); //這一步操作每一次都會遍歷整個連結串列來取得i位置的節點, 屬於虛假的隨機訪問
}
若訪問的位置index大於等於size()/2, 則從尾部開始遍歷
如果有隨機訪問的需要, 通常不會選擇使用LInkedList
ArrayList
- 在ArrayList上進行的操作都是非同步的也就是非執行緒安全的
- ArrayList封裝了一個動態再分配的陣列,初始為一個空陣列, 當新增第一個元素時擴容為10, 而之後呼叫add(或其他)方法使容量不夠時就會進行擴容, ArrayList擴容增加原來的50%(Vector增加1倍)
ArrayList成員變數
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10; //初始容量, 若超過這個容量就會進行擴容
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access //transient 無法被持久化的變數型別
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
...
}
ArrayList的擴容機制
private void grow(int minCapacity) { //minCapacity 要求的最小容量
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); //增加50%容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) //若超出ArrayList最大長度則取Integer.MAX_VALUE
newCapacity = hugeCapacity(minCapacity);
// 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;
}
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
整形的最大值: Integer.MAX_VALUE = 231 - 1 (int 長度為32bit 最高位為標誌位標識正數或負數)
ArrayList構造器
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
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);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() { //空參構造, 沒有執行add時為一個空的Objec陣列
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) { //傳入一個Collection集合的子類
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class); //通過複製陣列的方式將傳入Collection集合轉化為ArrayList
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
需要注意的是傳入長度為0時和無參構造都是賦值一個空Object[]陣列, 只是名字不同EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA
ArrayList中轉換成陣列的方法也是使用了Arrays.copyOf()
public Object[] toArray() { return Arrays.copyOf(elementData, size); } public <T> T[] toArray(T[] a) { if (a.length < size) // Make a new array of a's runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass()); System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; }
ArrayList查詢元素
/**
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the lowest index <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
* or -1 if there is no such 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;
}
/**
* Returns the index of the last occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the highest index <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
* or -1 if there is no such index.
*/
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;
}
需要注意的是判斷集合是否包含某一個元素就是返回是否查詢到了這個元素
/** * Returns <tt>true</tt> if this list contains the specified element. * More formally, returns <tt>true</tt> if and only if this list contains * at least one element <tt>e</tt> such that * <tt>(o==null ? e==null : o.equals(e))</tt>. * * @param o element whose presence in this list is to be tested * @return <tt>true</tt> if this list contains the specified element */ public boolean contains(Object o) { return indexOf(o) >= 0; }
如果想要找到集合中所有和元素相同的元素的下標可以使用for迴圈根據下標遍歷查詢也可以使用ListIterator進行迭代, 下面是一個使用ListIterator的例子
ArrayList<String> list = new ArrayList<>(); list.add("1"); //index 0 list.add("2"); list.add("3"); list.add("1"); //3 list.add("5"); list.add("6"); list.add("1"); //6 list.add("1"); //7 String target = "1"; ListIterator<String> listIterator = list.listIterator(); while (listIterator.hasNext()){ Object str = listIterator.next(); if (str.equals(target)){ //TODO 這裡存放想要對下標做的事previousIndex() System.out.print(listIterator.previousIndex() + " "); } }
輸出:
0 3 6 7
ArrayList獲取元素
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
ArrayList設定新增元素
/**
* Replaces the element at the specified position in this list with
* the specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) { //指定位置插入元素
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index); //將陣列index之後的所有元素後移一位(直接複製index之後的資料到index+1)
elementData[index] = element;
size++;
}
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;
return numNew != 0;
}
/**
* Inserts all of the elements in the specified collection into this
* list, starting at the specified position. Shifts the element
* currently at that position (if any) and any subsequent elements to
* the right (increases their indices). The new elements will appear
* in the list in the order that they are returned by the
* specified collection's iterator.
*
* @param index index at which to insert the first element from the
* specified collection
* @param c collection containing elements to be added to this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(int index, Collection<? extends E> c) { //從index開始之後新增Collection集合, 和add(int index, E element)實現方法很相似
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length; //新增集合的長度
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index; //index之後所有元素都要移動, 這是需要移動的數量
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved); //把elementData中index到index+munMoved範圍的資料移動到(複製)index+新集合長度numNew之後
System.arraycopy(a, 0, elementData, index, numNew); //如果當index=size時直接拼接到list集合後面
size += numNew;
return numNew != 0;
}
ArrayList移除元素
移除元素和新增元素很相似, 都涉及到了陣列的複製, 只不過一個是根據index往前移, 一個是往後移
在範圍刪除上也是和範圍新增大同小異
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
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;
}
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
* (if such an element exists). Returns <tt>true</tt> if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
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; // clear to let GC do its work
}
/**
* Removes all of the elements from this list. The list will
* be empty after this call returns.
*/
public void clear() { //直接清空List元素, 保留陣列長度
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
/**
* Removes from this list all of the elements whose index is between
* {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.
* Shifts any succeeding elements to the left (reduces their index).
* This call shortens the list by {@code (toIndex - fromIndex)} elements.
* (If {@code toIndex==fromIndex}, this operation has no effect.)
*
* @throws IndexOutOfBoundsException if {@code fromIndex} or
* {@code toIndex} is out of range
* ({@code fromIndex < 0 ||
* fromIndex >= size() ||
* toIndex > size() ||
* toIndex < fromIndex})
*/
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
ArrayList中無論是新增還是刪除都涉及到了元素的移動陣列的複製, 相比於她的高效的get()方法, 插入元素的效率過於低下
所以如果有大量的插入且沒有相同元素的情況下可以使用Set集合
ArrayList的拷貝
/**
* Returns a shallow copy of this <tt>ArrayList</tt> instance. (The
* elements themselves are not copied.)
*
* @return a clone of this <tt>ArrayList</tt> instance
*/
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size); //如果陣列元素為物件則直接把物件地址複製過去
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
ArrayList的拷貝只是吧原有陣列中的引用(地址)複製到了新陣列中, 如果對源陣列進行操作也會影響到新陣列中
例子:
@Test public void cloneTest(){ ArrayList<A> list1 = new ArrayList<>(); list1.add(new A(1)); ArrayList<A> listClone = (ArrayList<A>)list1.clone(); list1.get(0).setId(2); listClone.forEach(System.out::println); } class A{ int id; public A(int _id){ this.id = _id; } public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public String toString(){ return "[ id = " + id + "]"; } }
輸出:
[ id = 2]
Set
-
Set介面是Collection的子介面, 它沒有提供額外的方法, 所以同樣Set也支援foreach和Iterator.
-
和List不同的是Set集合不允許包含相同的元素.
HashSet
- HashSet和LinkedHashSet按hash演算法來儲存集合中的元素, 具有很好的存取和查詢效能.
- 這種結構無法控制出現的次序, 如果不在意元素的順序那麼這將會是一種很好的結構.
- 散列表為每一個物件計算一個整數hash code, 雜湊碼(hash code)是由物件例項欄位得出的一個整數, 有不同資料的物件將產生不同的雜湊碼.
- jdk1.8中HashSet含有一個HashMap物件, 對hashSet的操作實際上都是對hashMap的操作
- HashTable用連結串列陣列實現, 要想查詢元素在表中的位置, 要計算他的雜湊碼然後將她的雜湊碼和桶的總數取餘, 結果為這個元素在桶中的索引
- 若這時桶已經被填充滿, 就需要先與桶中所有元素進行比較, 檢視物件是否已經存在,
HashSet的成員變數
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
HashSet的所有方法實際上都是對成員變數中HashMap的操作, 也利用了Map中key值唯一的特性
LinkedHashSet
- LinkedHashSet是HashSet的子類, 他在HashSet的基礎上, 在節點中增加兩個屬性, before和after, 維護了節點前後新增功能
- 相比於HashSet, LinkedHashSet插入效能有所不足, 而迭代訪問所有元素中LinkedHashSet有較好的效能
LinkedHashSet構造器
和HashSet實際上是對HashMap的操作一樣, LinkedHashSet實際上也是對LinkedHashMap的操作
public LinkedHashSet(xxx){}//不管傳入引數是什麼最終都是呼叫父類的
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
//實際上建立的是一個LinkedHashMap物件
LinkedHashSet.Entry維護了兩個Entry<K,V>變數: before, after; 其繼承了Node<K,V>, 相比於Node只有指向下一個元素的Node<K,V> next變數Linked是雙向的
/**
* HashMap.Node subclass for normal LinkedHashMap entries.
*/
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
...
}
TreeSet
- TreeSet和HashSet十分相似, 但她是一個有序集合. 可以任意順序將元素插入到集合中
- TreeSet的排序使用紅黑數實現, 新增元素時總會將其放到正確的排序位置上, 因此迭代器可以有序的訪問每一個元素(沒錯因為TreeMap用的紅黑樹)
TreeSet的構造方法
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public TreeSet() {
this(new TreeMap<E,Object>());
}
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
對於某些資料來說, 對其進行排序要比給出一個雜湊函式更加困難, 雜湊函式只需要將物件適當的打亂存放, 而比較函式必須精準的區分各個物件
如果使用TreeSet
, E就必須要提供Comparator 的實現 , 能夠進行集中兩個元素的精準比較, 區分出每一個唯一的元素
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
...
}
例子:
@Test
public void treeTest(){
Apple appleOne = new Apple("red", 1);
Apple appleTwo = new Apple("blue", 2);
Apple appleThree = new Apple("green", 3);
Set<Apple> set = new TreeSet<>(); //使用Apple實現的比較器
set.add(appleOne);
set.add(appleTwo);
set.add(appleThree);
System.out.println(set);
TreeSet<Apple> apples = new TreeSet<>(Comparator.comparing(Apple::getId)); //建立TreeSet時傳入比較器
apples.addAll(set);
System.out.println(apples);
}
private class Apple implements Comparable<Apple>{
private int id;
private String color;
public Apple(String _color, int _id){
this.color = _color;
this.id = _id;
}
@Override
public int compareTo(Apple o) {
return color.compareTo(o.getColor());
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null) return false;
return (((Apple)obj).getColor().equalsIgnoreCase(this.color));
}
//Getter ...
//Setter ...
@Override
public String toString() {
return " [color: "+ color + ", id = " + id + "] ";
}
}
輸出:
[ [color: blue, id = 2] , [color: green, id = 3] , [color: red, id = 1] ]
[ [color: red, id = 1] , [color: blue, id = 2] , [color: green, id = 3] ]
雖然新增到TreeSet不使用equals方法, 但當元素實現Comparable介面時重寫了comparaTo()方法, 也建議重寫equals()方法, 保證comparaTo()方法和equals()方法返回結果一致
Map
- 對映(Map)用來存放鍵值對(key, value), key和value可以是任何型別的值
- 鍵值對的對映是唯一的總能通過key找到唯一的value, 所以key不允許重複,而value允許重複
int size(); //取得大小
boolean isEmpty(); //判斷為空
boolean containsKey(Object key); //判斷是否包含key
boolean containsValue(Object value); //判斷是否包含value
V put(K key, V value); //新增(key, value)對映
void putAll(Map<? extends K, ? extends V> m); //新增m中的所有對映
V get(Object key); //取得value
V remove(Object key); //移除(key, xxx)對映
Set<K> keySet(); //返回key集
Collection<V> values(); //返回value集合
Set<Map.Entry<K, V>> entrySet(); //返回鍵值對組成的EntrySet
default V getOrDefault(Object key, V defaultValue); //若get(key)==null返回defaultValue, 否則返回get(key)
default void forEach(BiConsumer<? super K, ? super V> action); //遍歷所有元素並執行操作apply(k,v), 不對原本map產生影響
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function); //對所有元素v=apply(k,v)然後將v返回到map中
default V putIfAbsent(K key, V value); //若get(key) == null,則put(key, value), 不會覆蓋以前的key值
default boolean remove(Object key, Object value); //存在(key, value的情況下), remove(key)
default boolean replace(K key, V oldValue, V newValue); //保證存在(key, oldValue)的情況下, put(key, newValue);
default V replace(K key, V value); //若存在get(key), 則put(key, value)
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction); //計算新值並返回, 若不存在則建立
Map的遍歷和遍歷替換
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
// ise thrown from function is not a cme.
v = function.apply(k, v);
try {
entry.setValue(v);
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
}
}
由此可知, Map的遍歷可以使用keySet();返回key集然後遍歷, 或者使用entrySet();進行遍歷
除了遍歷替換以外還提供了基於(key,value)的替換, 這兩個方法差別就是替換條件不同
default boolean replace(K key, V oldValue, V newValue) {
Object curValue = get(key);
if (!Objects.equals(curValue, oldValue) ||
(curValue == null && !containsKey(key))) {
return false;
}
put(key, newValue);
return true;
}
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
如果不存在的情況下新增到map中
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
除了這幾個方法以外, map提供了merge()方法
default V merge(K key, V value, //操作對映的key和新值
BiFunction<? super V, ? super V, ? extends V> remappingFunction) { //remappingFunction : 組合原值和新值的函式
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key); //取得原本的值
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value); //若oldValue不為空則使用remappingFunction組合舊值和新值
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
例子:
@Test
public void merge(){
Map<String, String> map = new HashMap<>();
map.put("first", "f-i-r-s-t");
map.merge("first", "value", (v1, v2)->{
//v1為oldValue這裡是"f-i-r-s-t", v2為新值
return v2 + ":" + v1.replaceAll("-", "");
});
System.out.println(map.get("first"));
}
HashMap
- HashMap是執行緒不安全的, 允許使用null鍵, null值
一些基本操作
//為空判斷
public boolean isEmpty() {
return size == 0;
}
//取值
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
//包含判斷
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
//移除對映
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value;
}
//新增對映
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
Node<K,V>
對map中的操作其實都是對map中Node陣列 table的操作
/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
transient Node<K,V>[] table;
/**
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
*/
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //元素hash值
final K key; //鍵
V value; //值
Node<K,V> next; //下一個元素的引用
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value); //按位異或該位上若相同返回1 不同則返回0
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
HashMap計算hash的方式
/* ---------------- Static utilities -------------- */
/**
* Computes key.hashCode() and spreads (XORs) higher bits of hash
* to lower. Because the table uses power-of-two masking, sets of
* hashes that vary only in bits above the current mask will
* always collide. (Among known examples are sets of Float keys
* holding consecutive whole numbers in small tables.) So we
* apply a transform that spreads the impact of higher bits
* downward. There is a tradeoff between speed, utility, and
* quality of bit-spreading. Because many common sets of hashes
* are already reasonably distributed (so don't benefit from
* spreading), and because we use trees to handle large sets of
* collisions in bins, we just XOR some shifted bits in the
* cheapest possible way to reduce systematic lossage, as well as
* to incorporate impact of the highest bits that would otherwise
* never be used in index calculations because of table bounds.
*/
static final int hash(Object key) {
int h;
//保證hashCode不變的情況下, 把hashCode和hashCode高16位進行異或運算, 這種計算方法是靈活性和實用性的一種權衡
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
HashMadp的新增:
/**
* Implements Map.put and related methods.
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
HashMap擴容:
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // 若原來有值小於允許的最大值, 且大於16(預設桶大小), 則進行擴容擴容為2的冪數倍
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults 第一次裝填資料時, 桶初始化
newCap = DEFAULT_INITIAL_CAPACITY; // 預設桶大小為16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 裝填因子預設為75%
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) { //自動再雜湊
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
在更改Map中的元素時, 如果元素的雜湊碼發生了變化, name元素在資料結構中的位置也會發生變化.
HashMap的迭代器
/* ------------------------------------------------------------ */
// iterators
abstract class HashIterator {
Node<K,V> next; // next entry to return
Node<K,V> current; // current entry
int expectedModCount; // for fast-fail
int index; // current slot
HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {
}while (index < t.length && (next = t[index++]) == null); //這裡進行迴圈,找到節點中不為空的下一個元素
}
}
public final boolean hasNext() {
return next != null;
}
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
HashIterator構造方法和nextNode都有 do {} while (index < t.length && (next = t[index++]) == null);這句話的作用是跳過那些表中指向null的節點, 因為HashMap放置元素是根據計算hash值放到不同的桶中, 所以遍歷整個集合的時候會有桶指向null, 所以我們用Iterator遍歷時就需要跳過那些為null的位置.
LinkedHashMap
- 作為HashMap的子類, 由雙向連結串列實現, 定義了迭代順序.
HashTable
- HashMap和HashTable都是Hash表
- HashTable是執行緒安全的, 而HashMap不是, HashTable, 不允許
HashTable 新增操作分析
private transient Entry<?,?>[] table; //HashTable中的鍵值對存放在各個儲存桶中, 儲存桶為Entry<k,v>陣列
/**
* The table is rehashed when its size exceeds this threshold. (The
* value of this field is (int)(capacity * loadFactor).)
*
* @serial
*/
private int threshold;
/**
* Maps the specified <code>key</code> to the specified
* <code>value</code> in this hashtable. Neither the key nor the
* value can be <code>null</code>. <p>
*
* The value can be retrieved by calling the <code>get</code> method
* with a key that is equal to the original key.
*
* @param key the hashtable key
* @param value the value
* @return the previous value of the specified key in this hashtable,
* or <code>null</code> if it did not have one
* @exception NullPointerException if the key or value is
* <code>null</code>
* @see Object#equals(Object)
* @see #get(Object)
*/
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) { //要求value不為空
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length; //計算儲存桶位置
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index]; //取出儲存桶
for(; entry != null ; entry = entry.next) { //遍歷該儲存桶查詢是否key已經存在
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index); //若不存在則進行新增
return null;
}
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) { //若儲存數量達到臨界值
// Rehash the table if the threshold is exceeded
rehash(); //則進行擴容和table的再雜湊
tab = table; //再雜湊後引用地址改變需要重新賦值
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length; //重新計算儲存桶位置
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index]; //找到該儲存桶
tab[index] = new Entry<>(hash, key, value, e); //新增在儲存桶頭部(e==Entry<K,V> next;)
count++;
}
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1; //
if (newCapacity - MAX_ARRAY_SIZE > 0) { //判斷是否已經到最大了
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); //返回擴容後的大小和邊界大小(最大大小)小的那個
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) { //原有內容進行再雜湊
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
//HashMap中的擴容: x2 newCap = oldCap << 1; //HashTable中的擴容: x2 + 1 (實際可以使用的為容量x載入因子【預設0.75】) int newCapacity = (oldCapacity << 1) + 1; //ArrayList中的擴容: x1.5 int newCapacity = oldCapacity + (oldCapacity >> 1);
Entry<K,V>
/**
* Hashtable bucket collision list entry
*/
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Entry<K,V> next;
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
@SuppressWarnings("unchecked")
protected Object clone() {
return new Entry<>(hash, key, value,
(next==null ? null : (Entry<K,V>) next.clone()));
}
// Map.Entry Ops
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
if (value == null)
throw new NullPointerException();
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
(value==null ? e.getValue()==null : value.equals(e.getValue()));
}
public int hashCode() {
return hash ^ Objects.hashCode(value);
}
public String toString() {
return key.toString()+"="+value.toString();
}
}