diff --git a/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java b/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java index 2066558a61d64..b7f7ffe6ef29c 100644 --- a/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java +++ b/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java @@ -9,7 +9,6 @@ import java.util.AbstractMap; import java.util.AbstractSet; -import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -64,25 +63,21 @@ class SmallSortedMap, V> extends AbstractMap { * Creates a new instance for mapping FieldDescriptors to their values. The {@link * #makeImmutable()} implementation will convert the List values of any repeated fields to * unmodifiable lists. - * - * @param arraySize The size of the entry array containing the lexicographically smallest - * mappings. */ - static > - SmallSortedMap newFieldMap() { - return new SmallSortedMap() { + static > + SmallSortedMap newFieldMap() { + return new SmallSortedMap() { @Override - @SuppressWarnings("unchecked") public void makeImmutable() { if (!isImmutable()) { for (int i = 0; i < getNumArrayEntries(); i++) { - final Map.Entry entry = getArrayEntryAt(i); + final Map.Entry entry = getArrayEntryAt(i); if (entry.getKey().isRepeated()) { final List value = (List) entry.getValue(); entry.setValue(Collections.unmodifiableList(value)); } } - for (Map.Entry entry : getOverflowEntries()) { + for (Map.Entry entry : getOverflowEntries()) { if (entry.getKey().isRepeated()) { final List value = (List) entry.getValue(); entry.setValue(Collections.unmodifiableList(value)); @@ -99,10 +94,14 @@ static , V> SmallSortedMap newInstanceForTest() { return new SmallSortedMap<>(); } - // The "entry array" is actually a List because generic arrays are not - // allowed. ArrayList also nicely handles the entry shifting on inserts and - // removes. - private List entryList; + // Only has Entry elements inside. + // Can't declare this as Entry[] because Entry is generic, so you get "generic array creation" + // error. Instead, use an Object[], and cast to Entry on read. + // null Object[] means 'empty'. + private Object[] entries; + // Number of elements in entries that are valid, like ArrayList.size. + private int entriesSize; + private Map overflowEntries; private boolean isImmutable; // The EntrySet is a stateless view of the Map. It's initialized the first @@ -112,7 +111,6 @@ static , V> SmallSortedMap newInstanceForTest() { private volatile DescendingEntrySet lazyDescendingEntrySet; private SmallSortedMap() { - this.entryList = Collections.emptyList(); this.overflowEntries = Collections.emptyMap(); this.overflowEntriesDescending = Collections.emptyMap(); } @@ -120,8 +118,8 @@ private SmallSortedMap() { /** Make this map immutable from this point forward. */ public void makeImmutable() { if (!isImmutable) { - // Note: There's no need to wrap the entryList in an unmodifiableList - // because none of the list's accessors are exposed. The iterator() of + // Note: There's no need to wrap the entries in an unmodifiableList + // because none of the array's accessors are exposed. The iterator() of // overflowEntries, on the other hand, is exposed so it must be made // unmodifiable. overflowEntries = @@ -143,12 +141,17 @@ public boolean isImmutable() { /** @return The number of entries in the entry array. */ public int getNumArrayEntries() { - return entryList.size(); + return entriesSize; } /** @return The array entry at the given {@code index}. */ public Map.Entry getArrayEntryAt(int index) { - return entryList.get(index); + if (index >= entriesSize) { + throw new ArrayIndexOutOfBoundsException(index); + } + @SuppressWarnings("unchecked") + Entry e = (Entry) entries[index]; + return e; } /** @return There number of overflow entries. */ @@ -165,7 +168,7 @@ public Iterable> getOverflowEntries() { @Override public int size() { - return entryList.size() + overflowEntries.size(); + return entriesSize + overflowEntries.size(); } /** @@ -191,7 +194,9 @@ public V get(Object o) { final K key = (K) o; final int index = binarySearchInArray(key); if (index >= 0) { - return entryList.get(index).getValue(); + @SuppressWarnings("unchecked") + Entry e = (Entry) entries[index]; + return e.getValue(); } return overflowEntries.get(key); } @@ -202,7 +207,9 @@ public V put(K key, V value) { final int index = binarySearchInArray(key); if (index >= 0) { // Replace existing array entry. - return entryList.get(index).setValue(value); + @SuppressWarnings("unchecked") + Entry e = (Entry) entries[index]; + return e.setValue(value); } ensureEntryArrayMutable(); final int insertionPoint = -(index + 1); @@ -211,20 +218,26 @@ public V put(K key, V value) { return getOverflowEntriesMutable().put(key, value); } // Insert new Entry in array. - if (entryList.size() == DEFAULT_FIELD_MAP_ARRAY_SIZE) { + if (entriesSize == DEFAULT_FIELD_MAP_ARRAY_SIZE) { // Shift the last array entry into overflow. - final Entry lastEntryInArray = entryList.remove(DEFAULT_FIELD_MAP_ARRAY_SIZE - 1); + @SuppressWarnings("unchecked") + final Entry lastEntryInArray = (Entry) entries[DEFAULT_FIELD_MAP_ARRAY_SIZE - 1]; + entriesSize--; getOverflowEntriesMutable().put(lastEntryInArray.getKey(), lastEntryInArray.getValue()); } - entryList.add(insertionPoint, new Entry(key, value)); + System.arraycopy( + entries, insertionPoint, entries, insertionPoint + 1, entries.length - insertionPoint - 1); + entries[insertionPoint] = new Entry(key, value); + entriesSize++; return null; } @Override public void clear() { checkMutable(); - if (!entryList.isEmpty()) { - entryList.clear(); + if (entriesSize != 0) { + entries = null; + entriesSize = 0; } if (!overflowEntries.isEmpty()) { overflowEntries.clear(); @@ -256,12 +269,17 @@ public V remove(Object o) { private V removeArrayEntryAt(int index) { checkMutable(); - final V removed = entryList.remove(index).getValue(); + @SuppressWarnings("unchecked") + final V removed = ((Entry) entries[index]).getValue(); + // shift items across + System.arraycopy(entries, index + 1, entries, index, entriesSize - index - 1); + entriesSize--; if (!overflowEntries.isEmpty()) { // Shift the first entry in the overflow to be the last entry in the // array. final Iterator> iterator = getOverflowEntriesMutable().entrySet().iterator(); - entryList.add(new Entry(iterator.next())); + entries[entriesSize] = new Entry(iterator.next()); + entriesSize++; iterator.remove(); } return removed; @@ -274,13 +292,14 @@ private V removeArrayEntryAt(int index) { */ private int binarySearchInArray(K key) { int left = 0; - int right = entryList.size() - 1; + int right = entriesSize - 1; // Optimization: For the common case in which entries are added in // ascending tag order, check the largest element in the array before // doing a full binary search. if (right >= 0) { - int cmp = key.compareTo(entryList.get(right).getKey()); + @SuppressWarnings("unchecked") + int cmp = key.compareTo(((Entry) entries[right]).getKey()); if (cmp > 0) { return -(right + 2); // Insert point is after "right". } else if (cmp == 0) { @@ -290,7 +309,8 @@ private int binarySearchInArray(K key) { while (left <= right) { int mid = (left + right) / 2; - int cmp = key.compareTo(entryList.get(mid).getKey()); + @SuppressWarnings("unchecked") + int cmp = key.compareTo(((Entry) entries[mid]).getKey()); if (cmp < 0) { right = mid - 1; } else if (cmp > 0) { @@ -344,11 +364,13 @@ private SortedMap getOverflowEntriesMutable() { return (SortedMap) overflowEntries; } - /** Lazily creates the entry list. Any code that adds to the list must first call this method. */ + /** + * Lazily creates the entry array. Any code that adds to the array must first call this method. + */ private void ensureEntryArrayMutable() { checkMutable(); - if (entryList.isEmpty() && !(entryList instanceof ArrayList)) { - entryList = new ArrayList<>(DEFAULT_FIELD_MAP_ARRAY_SIZE); + if (entries == null) { + entries = new Object[DEFAULT_FIELD_MAP_ARRAY_SIZE]; } } @@ -498,7 +520,7 @@ private class EntryIterator implements Iterator> { @Override public boolean hasNext() { - return (pos + 1) < entryList.size() + return (pos + 1) < entriesSize || (!overflowEntries.isEmpty() && getOverflowIterator().hasNext()); } @@ -507,8 +529,10 @@ public Map.Entry next() { nextCalledBeforeRemove = true; // Always increment pos so that we know whether the last returned value // was from the array or from overflow. - if (++pos < entryList.size()) { - return entryList.get(pos); + if (++pos < entriesSize) { + @SuppressWarnings("unchecked") + Entry e = (Entry) entries[pos]; + return e; } return getOverflowIterator().next(); } @@ -521,7 +545,7 @@ public void remove() { nextCalledBeforeRemove = false; checkMutable(); - if (pos < entryList.size()) { + if (pos < entriesSize) { removeArrayEntryAt(pos--); } else { getOverflowIterator().remove(); @@ -547,12 +571,12 @@ private Iterator> getOverflowIterator() { */ private class DescendingEntryIterator implements Iterator> { - private int pos = entryList.size(); + private int pos = entriesSize; private Iterator> lazyOverflowIterator; @Override public boolean hasNext() { - return (pos > 0 && pos <= entryList.size()) || getOverflowIterator().hasNext(); + return (pos > 0 && pos <= entriesSize) || getOverflowIterator().hasNext(); } @Override @@ -560,7 +584,9 @@ public Map.Entry next() { if (getOverflowIterator().hasNext()) { return getOverflowIterator().next(); } - return entryList.get(--pos); + @SuppressWarnings("unchecked") + Entry e = (Entry) entries[--pos]; + return e; } @Override @@ -621,7 +647,7 @@ public int hashCode() { int h = 0; final int listSize = getNumArrayEntries(); for (int i = 0; i < listSize; i++) { - h += entryList.get(i).hashCode(); + h += entries[i].hashCode(); } // Avoid the iterator allocation if possible. if (getNumOverflowEntries() > 0) {