All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.collection.trackable.HeapTrackingArrayList Maven / Gradle / Ivy

There is a newer version: 5.25.1
Show newest version
/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [https://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.collection.trackable;

import static org.neo4j.internal.helpers.ArrayUtil.MAX_ARRAY_SIZE;
import static org.neo4j.memory.HeapEstimator.shallowSizeOfInstance;
import static org.neo4j.memory.HeapEstimator.shallowSizeOfObjectArray;
import static org.neo4j.util.Preconditions.requireNonNegative;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Consumer;
import org.neo4j.memory.MemoryTracker;

/**
 * A heap tracking array list. It only tracks the internal structure, not the elements within.
 * 

* This is mostly a copy of {@link ArrayList} to expose the {@link #grow(int)} method. * * @param element type */ @SuppressWarnings("unchecked") public class HeapTrackingArrayList implements List, AutoCloseable { private static final long SHALLOW_SIZE = shallowSizeOfInstance(HeapTrackingArrayList.class); private final MemoryTracker memoryTracker; private long trackedSize; private int size; private int modCount; private Object[] elementData; /** * @return a new heap tracking array list with initial size 1 */ public static HeapTrackingArrayList newArrayList(MemoryTracker memoryTracker) { return newArrayListWithInitialTrackedSize(1, memoryTracker, 0L); } /** * @return a new heap tracking array list with initial size 1 and the specified initial tracked memory size */ public static HeapTrackingArrayList newArrayListWithInitialTrackedSize( MemoryTracker memoryTracker, long initialTrackedSize) { return newArrayListWithInitialTrackedSize(1, memoryTracker, initialTrackedSize); } /** * @return a new heap tracking array list with the specified initial capacity */ public static HeapTrackingArrayList newArrayList(int initialCapacity, MemoryTracker memoryTracker) { return newArrayListWithInitialTrackedSize(initialCapacity, memoryTracker, 0L); } /** * @return a new heap tracking array list with the specified exact size, filled with nulls */ public static HeapTrackingArrayList newEmptyArrayList(int exactSize, MemoryTracker memoryTracker) { HeapTrackingArrayList list = newArrayListWithInitialTrackedSize(exactSize, memoryTracker, exactSize); list.size = exactSize; return list; } /** * @return a new heap tracking array list with the specified initial size and initial tracked memory size */ public static HeapTrackingArrayList newArrayListWithInitialTrackedSize( int initialCapacity, MemoryTracker memoryTracker, long initialTrackedSize) { requireNonNegative(initialCapacity); long trackedSize = shallowSizeOfObjectArray(initialCapacity) + initialTrackedSize; memoryTracker.allocateHeap(SHALLOW_SIZE + trackedSize); return new HeapTrackingArrayList<>(initialCapacity, memoryTracker, trackedSize); } @SuppressWarnings("CopyConstructorMissesField") private HeapTrackingArrayList(HeapTrackingArrayList other) { int otherSize = other.size; this.size = otherSize; this.elementData = new Object[otherSize]; System.arraycopy(other.elementData, 0, this.elementData, 0, otherSize); this.memoryTracker = other.memoryTracker; this.trackedSize = shallowSizeOfObjectArray(otherSize); memoryTracker.allocateHeap(SHALLOW_SIZE + trackedSize); } private HeapTrackingArrayList(int initialSize, MemoryTracker memoryTracker, long trackedSize) { this.elementData = new Object[initialSize]; this.memoryTracker = memoryTracker; this.trackedSize = trackedSize; } /* * Compacts the elementData list of the original array */ @Override public HeapTrackingArrayList clone() { return new HeapTrackingArrayList<>(this); } @Override public boolean add(E item) { modCount++; add(item, elementData, size); return true; } @Override public boolean containsAll(Collection c) { for (Object e : c) { if (!contains(e)) { return false; } } return true; } @Override public boolean addAll(Collection c) { Object[] a = c.toArray(); modCount++; int numNew = a.length; if (numNew == 0) { return false; } Object[] elementData; final int s; if (numNew > (elementData = this.elementData).length - (s = size)) { elementData = grow(s + numNew); } System.arraycopy(a, 0, elementData, s, numNew); size = s + numNew; return true; } @Override public boolean addAll(int index, Collection c) { rangeCheckForAdd(index); Object[] a = c.toArray(); modCount++; int numNew = a.length; if (numNew == 0) { return false; } Object[] elementData; final int s; if (numNew > (elementData = this.elementData).length - (s = size)) { elementData = grow(s + numNew); } int numMoved = s - index; if (numMoved > 0) { System.arraycopy(elementData, index, elementData, index + numNew, numMoved); } System.arraycopy(a, 0, elementData, index, numNew); size = s + numNew; return true; } @Override public boolean removeAll(Collection c) { return batchRemove(c, false, 0, size); } @Override public boolean retainAll(Collection c) { return batchRemove(c, true, 0, size); } @Override public E get(int index) { Objects.checkIndex(index, size); return elementData(index); } public E last() { return get(size - 1); } @Override public E set(int index, E element) { Objects.checkIndex(index, size); E oldValue = elementData(index); elementData[index] = element; return oldValue; } @Override public void add(int index, E element) { rangeCheckForAdd(index); modCount++; final int s; Object[] elementData; if ((s = size) == (elementData = this.elementData).length) { elementData = grow(size + 1); } System.arraycopy(elementData, index, elementData, index + 1, s - index); elementData[index] = element; size = s + 1; } @Override public E remove(int index) { Objects.checkIndex(index, size); final Object[] es = elementData; E oldValue = (E) es[index]; fastRemove(es, index); return oldValue; } public E removeLast() { return remove(size - 1); } @Override public int indexOf(Object o) { Object[] es = elementData; int size = this.size; if (o == null) { for (int i = 0; i < size; i++) { if (es[i] == null) { return i; } } } else { for (int i = 0; i < size; i++) { if (o.equals(es[i])) { return i; } } } return -1; } @Override public int lastIndexOf(Object o) { Object[] es = elementData; int size = this.size; if (o == null) { for (int i = size - 1; i >= 0; i--) { if (es[i] == null) { return i; } } } else { for (int i = size - 1; i >= 0; i--) { if (o.equals(es[i])) { return i; } } } return -1; } @Override public ListIterator listIterator() { return new ListItr(0); } @Override public ListIterator listIterator(int index) { rangeCheckForAdd(index); return new ListItr(index); } @Override public List subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); } public void truncate(int newSize) { if (newSize >= size) { return; } for (int i = newSize; i < size; i++) { elementData[i] = null; } this.size = newSize; } @Override public Iterator iterator() { return new Itr(); } @Override public Object[] toArray() { return Arrays.copyOf(elementData, size); } @Override public 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; } @Override public void close() { if (elementData != null) { memoryTracker.releaseHeap(trackedSize + SHALLOW_SIZE); elementData = null; } } public Iterator autoClosingIterator() { return new Iterator<>() { int index; @Override public boolean hasNext() { if (index >= size) { close(); return false; } return true; } @Override public E next() { if (!hasNext()) { throw new NoSuchElementException(); } return elementData(index++); } }; } @Override public void sort(Comparator c) { final int expectedModCount = modCount; Arrays.sort((E[]) elementData, 0, size, c); if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; } @Override public int size() { return size; } @Override public boolean isEmpty() { return size == 0; } public boolean notEmpty() { return size != 0; } @Override public boolean contains(Object o) { return indexOf(o) >= 0; } @Override public boolean remove(Object o) { final Object[] es = elementData; final int size = this.size; int i = 0; found: { if (o == null) { for (; i < size; i++) { if (es[i] == null) { break found; } } } else { for (; i < size; i++) { if (o.equals(es[i])) { break found; } } } return false; } fastRemove(es, i); return true; } @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof List)) { return false; } final int expectedModCount = modCount; boolean equal = (o.getClass() == HeapTrackingArrayList.class) ? equalsArrayList((HeapTrackingArrayList) o) : equalsRange((List) o, 0, size); checkForComodification(expectedModCount); return equal; } @Override public int hashCode() { int expectedModCount = modCount; int hash = hashCodeRange(0, size); checkForComodification(expectedModCount); return hash; } @Override public String toString() { StringBuilder sb = new StringBuilder("["); forEach(elem -> sb.append(elem).append(",")); if (size() > 0) { sb.deleteCharAt(sb.length() - 1); } sb.append("]"); return sb.toString(); } @Override public void clear() { modCount++; final Object[] es = elementData; for (int to = size, i = size = 0; i < to; i++) { es[i] = null; } } @Override public void forEach(Consumer action) { Objects.requireNonNull(action); final int expectedModCount = modCount; final Object[] es = elementData; final int size = this.size; for (int i = 0; modCount == expectedModCount && i < size; i++) { action.accept(elementAt(es, i)); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } /** * Grow and report size change to tracker */ private Object[] grow(int minimumCapacity) { int newCapacity = newCapacity(minimumCapacity, elementData.length); long oldHeapUsage = trackedSize; trackedSize = shallowSizeOfObjectArray(newCapacity); memoryTracker.allocateHeap(trackedSize); Object[] newItems = new Object[newCapacity]; System.arraycopy(elementData, 0, newItems, 0, Math.min(size, newCapacity)); elementData = newItems; memoryTracker.releaseHeap(oldHeapUsage); return elementData; } static int newCapacity(int minimumCapacity, int oldCapacity) { int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minimumCapacity <= 0) { if (minimumCapacity < 0) // overflow { throw new OutOfMemoryError(); } return minimumCapacity; } return newCapacity - MAX_ARRAY_SIZE <= 0 ? newCapacity : hugeCapacity(minimumCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow { throw new OutOfMemoryError(); } return minCapacity > MAX_ARRAY_SIZE ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } @SuppressWarnings("unchecked") private E elementData(int index) { return (E) elementData[index]; } @SuppressWarnings("unchecked") private static E elementAt(Object[] es, int index) { return (E) es[index]; } private void add(E e, Object[] elementData, int s) { if (s == elementData.length) { elementData = grow(size + 1); } elementData[s] = e; size = s + 1; } private void fastRemove(Object[] es, int i) { modCount++; final int newSize; if ((newSize = size - 1) > i) { System.arraycopy(es, i + 1, es, i, newSize - i); } es[size = newSize] = null; } private void checkForComodification(final int expectedModCount) { if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } private boolean equalsRange(List other, int from, int to) { final Object[] es = elementData; if (to > es.length) { throw new ConcurrentModificationException(); } var oit = other.iterator(); for (; from < to; from++) { if (!oit.hasNext() || !Objects.equals(es[from], oit.next())) { return false; } } return !oit.hasNext(); } private boolean equalsArrayList(HeapTrackingArrayList other) { final int otherModCount = other.modCount; final int s = size; boolean equal; if (equal = s == other.size) { final Object[] otherEs = other.elementData; final Object[] es = elementData; if (s > es.length || s > otherEs.length) { throw new ConcurrentModificationException(); } for (int i = 0; i < s; i++) { if (!Objects.equals(es[i], otherEs[i])) { equal = false; break; } } } other.checkForComodification(otherModCount); return equal; } private int hashCodeRange(int from, int to) { final Object[] es = elementData; if (to > es.length) { throw new ConcurrentModificationException(); } int hashCode = 1; for (int i = from; i < to; i++) { Object e = es[i]; hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode()); } return hashCode; } private boolean batchRemove(Collection c, boolean complement, final int from, final int end) { Objects.requireNonNull(c); final Object[] es = elementData; int r; // Optimize for initial run of survivors for (r = from; ; r++) { if (r == end) { return false; } if (c.contains(es[r]) != complement) { break; } } int w = r++; try { for (Object e; r < end; r++) { if (c.contains(e = es[r]) == complement) { es[w++] = e; } } } catch (Throwable ex) { System.arraycopy(es, r, es, w, end - r); w += end - r; throw ex; } finally { modCount += end - w; shiftTailOverGap(es, w, end); } return true; } private void shiftTailOverGap(Object[] es, int lo, int hi) { System.arraycopy(es, hi, es, lo, size - hi); for (int to = size, i = size -= hi - lo; i < to; i++) { es[i] = null; } } private void rangeCheckForAdd(int index) { if (index > size || index < 0) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); } } private class Itr implements Iterator { int cursor; int lastRet = -1; int expectedModCount = modCount; Itr() {} @Override public boolean hasNext() { return cursor != size; } @Override @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) { throw new NoSuchElementException(); } Object[] elementData = HeapTrackingArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } cursor = i + 1; return (E) elementData[lastRet = i]; } @Override public void remove() { if (lastRet < 0) { throw new IllegalStateException(); } checkForComodification(); try { HeapTrackingArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } @Override public void forEachRemaining(Consumer action) { Objects.requireNonNull(action); final int size = HeapTrackingArrayList.this.size; int i = cursor; if (i < size) { final Object[] es = elementData; if (i >= es.length) { throw new ConcurrentModificationException(); } for (; i < size && modCount == expectedModCount; i++) { action.accept(elementAt(es, i)); } // update once at end to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } } final void checkForComodification() { if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } } private class ListItr extends Itr implements ListIterator { ListItr(int index) { super(); cursor = index; } @Override public boolean hasPrevious() { return cursor != 0; } @Override public int nextIndex() { return cursor; } @Override public int previousIndex() { return cursor - 1; } @Override @SuppressWarnings("unchecked") public E previous() { checkForComodification(); int i = cursor - 1; if (i < 0) { throw new NoSuchElementException(); } Object[] elementData = HeapTrackingArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } cursor = i; return (E) elementData[lastRet = i]; } @Override public void set(E e) { if (lastRet < 0) { throw new IllegalStateException(); } checkForComodification(); try { HeapTrackingArrayList.this.set(lastRet, e); } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } @Override public void add(E e) { checkForComodification(); try { int i = cursor; HeapTrackingArrayList.this.add(i, e); cursor = i + 1; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy