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();
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步