插入和删除是否受元素位置的影响: ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② LinkedList 采用链表存储,所以对于add(E e)方法的插入,删除元素时间复杂度不受元素位置的影响,近似 O(1),如果是要在指定位置i插入和删除元素的话((add(int index, E element)) 时间复杂度近似为o(n))因为需要先移动到指定位置再插入。
/** *返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1 */ publicintindexOf(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++) //equals()方法比较 if (o.equals(elementData[i])) return i; } return -1; }
/** * 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。. */ publicintlastIndexOf(Object o){ if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; }
/** * 删除该列表中指定位置的元素。 将任何后续元素移动到左侧(从其索引中减去一个元素)。 */ public 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; }
/** * 从列表中删除指定元素的第一个出现(如果存在)。 如果列表不包含该元素,则它不会更改。 *返回true,如果此列表包含指定的元素 */ publicbooleanremove(Object o){ if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); returntrue; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); returntrue; } } returnfalse; }
/* * Private remove method that skips bounds checking and does not * return the value removed. */ privatevoidfastRemove(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 }
// 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 实例的容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。 * * @param minCapacity 所需的最小容量 */ publicvoidensureCapacity(int minCapacity){ int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // any size if not default element table ? 0 // larger than default for default empty table. It's already // supposed to be at default size. : DEFAULT_CAPACITY;
if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } }
最好在 add 大量元素之前用 ensureCapacity 方法,以减少增量重新分配的次数
我们通过下面的代码实际测试以下这个方法的效果:
1 2 3 4 5 6 7 8 9 10 11 12 13
publicclassEnsureCapacityTest{ publicstaticvoidmain(String[] args){ ArrayList<Object> list = new ArrayList<Object>(); finalint N = 10000000; long startTime = System.currentTimeMillis(); for (int i = 0; i < N; i++) { list.add(i); } long endTime = System.currentTimeMillis(); System.out.println("使用ensureCapacity方法前:"+(endTime - startTime));
} }
运行结果:
1
使用ensureCapacity方法前:2158
1 2 3 4 5 6 7 8 9 10 11 12 13 14
publicclassEnsureCapacityTest{ publicstaticvoidmain(String[] args){ ArrayList<Object> list = new ArrayList<Object>(); finalint N = 10000000; list = new ArrayList<Object>(); long startTime1 = System.currentTimeMillis(); list.ensureCapacity(N); for (int i = 0; i < N; i++) { list.add(i); } long endTime1 = System.currentTimeMillis(); System.out.println("使用ensureCapacity方法后:"+(endTime1 - startTime1)); } }
staticinthash(int h){ // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
// 继承自 Map.Entry<K,V> staticclassNode<K,V> implementsMap.Entry<K,V> { finalint hash;// 哈希值,存放元素到hashmap中时用来与其他元素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; } publicfinal K getKey(){ return key; } publicfinal V getValue(){ return value; } publicfinal String toString(){ return key + "=" + value; } // 重写hashCode()方法 publicfinalinthashCode(){ return Objects.hashCode(key) ^ Objects.hashCode(value); }
publicfinal V setValue(V newValue){ V oldValue = value; value = newValue; return oldValue; } // 重写 equals() 方法 publicfinalbooleanequals(Object o){ if (o == this) returntrue; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) returntrue; } returnfalse; } }
树节点类源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
staticfinalclassTreeNode<K,V> extendsLinkedHashMap.Entry<K,V> { TreeNode<K,V> parent; // 父 TreeNode<K,V> left; // 左 TreeNode<K,V> right; // 右 TreeNode<K,V> prev; // needed to unlink next upon deletion boolean red; // 判断颜色 TreeNode(int hash, K key, V val, Node<K,V> next) { super(hash, key, val, next); } // 返回根节点 final TreeNode<K,V> root(){ for (TreeNode<K,V> r = this, p;;) { if ((p = r.parent) == null) return r; r = p; }
privatevoidrehash(HashEntry<K,V> node){ HashEntry<K,V>[] oldTable = table; int oldCapacity = oldTable.length; //两倍容量 int newCapacity = oldCapacity << 1; threshold = (int)(newCapacity * loadFactor); //基于新容量,创建HashEntry数组 HashEntry<K,V>[] newTable = (HashEntry<K,V>[]) new HashEntry[newCapacity]; int sizeMask = newCapacity - 1; //实现数据迁移 for (int i = 0; i < oldCapacity ; i++) { HashEntry<K,V> e = oldTable[i]; if (e != null) { HashEntry<K,V> next = e.next; int idx = e.hash & sizeMask; if (next == null) // Single node on list //原位置只有一个元素,直接放到新数组即可 newTable[idx] = e; else { // Reuse consecutive sequence at same slot //=========图一===================== HashEntry<K,V> lastRun = e; int lastIdx = idx; // 新的位置只可能是不便或者是老的位置+老的容量。 // 遍历结束后,lastRun 后面的元素位置都是相同的 for (HashEntry<K,V> last = next; last != null; last = last.next) { int k = last.hash & sizeMask; if (k != lastIdx) { lastIdx = k; lastRun = last; } } //=========图一===================== //=========图二===================== newTable[lastIdx] = lastRun; //=========图二===================== // Clone remaining nodes //=========图三===================== for (HashEntry<K,V> p = e; p != lastRun; p = p.next) { V v = p.value; int h = p.hash; int k = h & sizeMask; HashEntry<K,V> n = newTable[k]; //这里旧的HashEntry不会放到新数组 //而是基于原来的数据创建了一个新的HashEntry对象,放入新数组 newTable[k] = new HashEntry<K,V>(h, p.key, v, n); } //=========图三===================== } } } //采用头插法,将新元素加入到数组中 int nodeIndex = node.hash & sizeMask; // add the new node node.setNext(newTable[nodeIndex]); newTable[nodeIndex] = node; table = newTable; }
这里第一个 for 是为了寻找这样一个节点,这个节点后面的所有 next 节点的新位置都是相同的。然后把这个作为一个链表赋值到新位置。第二个 for 循环是为了把剩余的元素通过头插法插入到指定位置链表。这样实现的原因可能是基于概率统计,
publicintsize(){ // Try a few times to get accurate count. On failure due to // continuous async changes in table, resort to locking. final Segment<K,V>[] segments = this.segments; int size; boolean overflow; // true if size overflows 32 bits long sum; // sum of modCounts long last = 0L; // previous sum int retries = -1; // first iteration isn't retry try { for (;;) { //当第5次走到这个地方时,会将整个Segment[]的所有Segment对象锁住,RETRIES_BEFORE_LOCK默认为2 if (retries++ == RETRIES_BEFORE_LOCK) { for (int j = 0; j < segments.length; ++j) ensureSegment(j).lock(); // force creation } sum = 0L; size = 0; overflow = false; for (int j = 0; j < segments.length; ++j) { Segment<K,V> seg = segmentAt(segments, j); if (seg != null) { //累加所有Segment的操作次数 sum += seg.modCount; int c = seg.count; //累加所有segment中的元素个数 size+=c if (c < 0 || (size += c) < 0) overflow = true; } } //当这次累加值和上一次累加值一样,证明没有进行新的增删改操作,返回sum //第一次last为0,如果有元素的话,这个for循环最少循环两次的 if (sum == last) break; //记录累加的值 last = sum; } } finally { //如果之前有锁住,解锁 if (retries > RETRIES_BEFORE_LOCK) { for (int j = 0; j < segments.length; ++j) segmentAt(segments, j).unlock(); } } //溢出,返回int的最大值,否则返回累加的size return overflow ? Integer.MAX_VALUE : size; }
privatefinalvoidtransfer(Node<K,V>[] tab, Node<K,V>[] nextTab){ int n = tab.length, stride; //如果是多cpu,那么每个线程划分任务,最小任务量是16个桶位的迁移 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) stride = MIN_TRANSFER_STRIDE; // subdivide range //如果是扩容线程,此时新数组为null if (nextTab == null) { // initiating try { @SuppressWarnings("unchecked") //两倍扩容创建新数组 Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; nextTab = nt; } catch (Throwable ex) { // try to cope with OOME sizeCtl = Integer.MAX_VALUE; return; } nextTable = nextTab; //记录线程开始迁移的桶位,从后往前迁移 transferIndex = n; } //记录新数组的末尾 int nextn = nextTab.length; //已经迁移的桶位,会用这个节点占位(这个节点的hash值为-1--MOVED) ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); boolean advance = true; boolean finishing = false; // to ensure sweep before committing nextTab for (int i = 0, bound = 0;;) { Node<K,V> f; int fh; while (advance) { int nextIndex, nextBound; //i记录当前正在迁移桶位的索引值 //bound记录下一次任务迁移的开始桶位 //--i >= bound 成立表示当前线程分配的迁移任务还没有完成 if (--i >= bound || finishing) advance = false; //没有元素需要迁移 -- 后续会去将扩容线程数减1,并判断扩容是否完成 elseif ((nextIndex = transferIndex) <= 0) { i = -1; advance = false; } //计算下一次任务迁移的开始桶位,并将这个值赋值给transferIndex elseif (U.compareAndSwapInt (this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) { bound = nextBound; i = nextIndex - 1; advance = false; } } //如果没有更多的需要迁移的桶位,就进入该if if (i < 0 || i >= n || i + n >= nextn) { int sc; //扩容结束后,保存新数组,并重新计算扩容阈值,赋值给sizeCtl if (finishing) { nextTable = null; table = nextTab; sizeCtl = (n << 1) - (n >>> 1); return; } //扩容任务线程数减1 if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { //判断当前所有扩容任务线程是否都执行完成 if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) return; //所有扩容线程都执行完,标识结束 finishing = advance = true; i = n; // recheck before commit } } //当前迁移的桶位没有元素,直接在该位置添加一个fwd节点 elseif ((f = tabAt(tab, i)) == null) advance = casTabAt(tab, i, null, fwd); //当前节点已经被迁移 elseif ((fh = f.hash) == MOVED) advance = true; // already processed else { //当前节点需要迁移,加锁迁移,保证多线程安全 //此处迁移逻辑和jdk7的ConcurrentHashMap相同,不再赘述 synchronized (f) { if (tabAt(tab, i) == f) { Node<K,V> ln, hn; if (fh >= 0) { int runBit = fh & n; Node<K,V> lastRun = f; for (Node<K,V> p = f.next; p != null; p = p.next) { int b = p.hash & n; if (b != runBit) { runBit = b; lastRun = p; } } if (runBit == 0) { ln = lastRun; hn = null; } else { hn = lastRun; ln = null; } for (Node<K,V> p = f; p != lastRun; p = p.next) { int ph = p.hash; K pk = p.key; V pv = p.val; if ((ph & n) == 0) ln = new Node<K,V>(ph, pk, pv, ln); else hn = new Node<K,V>(ph, pk, pv, hn); } setTabAt(nextTab, i, ln); setTabAt(nextTab, i + n, hn); setTabAt(tab, i, fwd); advance = true; } elseif (f instanceof TreeBin) { TreeBin<K,V> t = (TreeBin<K,V>)f; TreeNode<K,V> lo = null, loTail = null; TreeNode<K,V> hi = null, hiTail = null; int lc = 0, hc = 0; for (Node<K,V> e = t.first; e != null; e = e.next) { int h = e.hash; TreeNode<K,V> p = new TreeNode<K,V> (h, e.key, e.val, null, null); if ((h & n) == 0) { if ((p.prev = loTail) == null) lo = p; else loTail.next = p; loTail = p; ++lc; } else { if ((p.prev = hiTail) == null) hi = p; else hiTail.next = p; hiTail = p; ++hc; } } ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : (hc != 0) ? new TreeBin<K,V>(lo) : t; hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : (lc != 0) ? new TreeBin<K,V>(hi) : t; setTabAt(nextTab, i, ln); setTabAt(nextTab, i + n, hn); setTabAt(tab, i, fwd); advance = true; } } } } } }
2、图解
四、jdk1.8多线程扩容效率改进
多线程协助扩容的操作会在两个地方被触发:
① 当添加元素时,发现添加的元素对用的桶位为fwd节点,就会先去协助扩容,然后再添加元素
② 当添加完元素后,判断当前元素个数达到了扩容阈值,此时发现sizeCtl的值小于0,并且新数组不为空,这个时候,会去协助扩容
1、源码分析
1.1、元素未添加,先协助扩容,扩容完后再添加元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
final V putVal(K key, V value, boolean onlyIfAbsent){ if (key == null || value == null) thrownew NullPointerException(); int hash = spread(key.hashCode()); int binCount = 0; for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) tab = initTable(); elseif ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } //发现此处为fwd节点,协助扩容,扩容结束后,再循环回来添加元素 elseif ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); //省略代码
privatefinalvoidfullAddCount(long x, boolean wasUncontended){ int h; //获取当前线程的hash值 if ((h = ThreadLocalRandom.getProbe()) == 0) { ThreadLocalRandom.localInit(); // force initialization h = ThreadLocalRandom.getProbe(); wasUncontended = true; } //标识是否有冲突,如果最后一个桶不是null,那么为true boolean collide = false; // True if last slot nonempty for (;;) { CounterCell[] as; CounterCell a; int n; long v; //数组不为空,优先对数组中CouterCell的value累加 if ((as = counterCells) != null && (n = as.length) > 0) { //线程对应的桶位为null if ((a = as[(n - 1) & h]) == null) { if (cellsBusy == 0) { // Try to attach new Cell //创建CounterCell对象 CounterCell r = new CounterCell(x); // Optimistic create //利用CAS修改cellBusy状态为1,成功则将刚才创建的CounterCell对象放入数组中 if (cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { boolean created = false; try { // Recheck under lock CounterCell[] rs; int m, j; //桶位为空, 将CounterCell对象放入数组 if ((rs = counterCells) != null && (m = rs.length) > 0 && rs[j = (m - 1) & h] == null) { rs[j] = r; //表示放入成功 created = true; } } finally { cellsBusy = 0; } if (created) //成功退出循环 break; //桶位已经被别的线程放置了已给CounterCell对象,继续循环 continue; // Slot is now non-empty } } collide = false; } //桶位不为空,重新计算线程hash值,然后继续循环 elseif (!wasUncontended) // CAS already known to fail wasUncontended = true; // Continue after rehash //重新计算了hash值后,对应的桶位依然不为空,对value累加 //成功则结束循环 //失败则继续下面判断 elseif (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) break; //数组被别的线程改变了,或者数组长度超过了可用cpu大小,重新计算线程hash值,否则继续下一个判断 elseif (counterCells != as || n >= NCPU) collide = false; // At max size or stale //当没有冲突,修改为有冲突,并重新计算线程hash,继续循环 elseif (!collide) collide = true; //如果CounterCell的数组长度没有超过cpu核数,对数组进行两倍扩容 //并继续循环 elseif (cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { try { if (counterCells == as) {// Expand table unless stale CounterCell[] rs = new CounterCell[n << 1]; for (int i = 0; i < n; ++i) rs[i] = as[i]; counterCells = rs; } } finally { cellsBusy = 0; } collide = false; continue; // Retry with expanded table } h = ThreadLocalRandom.advanceProbe(h); } //CounterCell数组为空,并且没有线程在创建数组,修改标记,并创建数组 elseif (cellsBusy == 0 && counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { boolean init = false; try { // Initialize table if (counterCells == as) { CounterCell[] rs = new CounterCell[2]; rs[h & 1] = new CounterCell(x); counterCells = rs; init = true; } } finally { cellsBusy = 0; } if (init) break; } //数组为空,并且有别的线程在创建数组,那么尝试对baseCount做累加,成功就退出循环,失败就继续循环 elseif (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x)) break; // Fall back on using base } }
2、图解
fullAddCount方法中,当as数组不为空的逻辑图解
六、jdk1.8集合长度获取
1、源码分析
1.1、size方法
1 2 3 4 5 6
publicintsize(){ long n = sumCount(); return ((n < 0L) ? 0 : (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)n); }
1.2、sumCount方法
1 2 3 4 5 6 7 8 9 10 11 12 13
finallongsumCount(){ CounterCell[] as = counterCells; CounterCell a; //获取baseCount的值 long sum = baseCount; if (as != null) { //遍历CounterCell数组,累加每一个CounterCell的value值 for (int i = 0; i < as.length; ++i) { if ((a = as[i]) != null) sum += a.value; } } return sum; }