这是github上不断增长的“原始”工具集合的一部分,并且是IntArray的初始用例,可以在此处进行审查。

通常,在处理数据时,您会遇到所需的唯一值用作查找系统中的键。这些密钥可以是IP地址,时间,标识符等中的任何一种。标准操作是使用Map<Integer,....>。使用基本数组系统进行键和值之间的关联。

这个问题是对Map<int,int>类的回顾,该类使用IntKeyIndex完成了一半的工作,并且是一个相对传统的基于int数组的哈希-table。

IntArray的目的是将任意IntKeyIndex键值映射到数组中的索引,并允许将数组中的索引引用回到它所属的键。由于从索引0开始对数组进行索引,因此有意义的是,已注册的第一个键(可能为键值5987682赋予了索引0),而已注册的下一个键(可能是-7799243)被赋予了索引1,依此类推。每次我们随后要求提供密钥-7799243的索引时,都会始终返回索引1。每次请求具有索引1的相关密钥时,它将都返回-7799243。此操作,它还允许从映射中“删除”键值,从而释放与其关联的索引,并随后可以重用它们。

概念上,int类映射广泛的任意键值(从IntKeyIndexIntKeyIndex(包括))到线性地址空间(从Integer.MIN_VALUEInteger.MAX_VALUE),并且还将地址空间映射回各自的键。

其主要功能类是:


它在键上使用哈希系统,当哈希表效率低下时,该哈希系统可以非常快速地重新哈希哈希桶。
添加密钥时,它会对其进行哈希处理并进行存储。如果这是第一次对值建立索引,它将使用IntArray中的下一个位置,并将该索引添加到哈希表中。
它使用IntArray存储键,并将键的索引存储在哈希表中。 IntArray是实例报告为键的索引的索引。每个键仅存储两个int值,该键存储在IntArray中,索引存储在哈希表中。
它具有一个流API,该API使键和索引可以并行流传输

用于审查

我对0的审查特别感兴趣,重点是:


性能
内存效率
可能会导致故障的意外边缘情况。
一般可用性

n-1

package net.tuis.primutils;

/**
 * Simple container class containing a key/value mapping.
 * 
 * @author rolf
 *
 */
public class IntKIntVEntry {

    private final int key, value;

    /**
     * Create the container containing the key/value mapping.
     * @param key the key
     * @param value the value
     */
    public IntKIntVEntry(int key, int value) {
        super();
        this.key = key;
        this.value = value;
    }

    /**
     * Retrieve the mapped key
     * @return the key
     */
    public int getKey() {
        return key;
    }

    /**
     * Retrieve the value.
     * @return the value.
     */
    public int getValue() {
        return value;
    }

    @Override
    public int hashCode() {
        return Integer.rotateLeft(key, 13) ^ value;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof IntKIntVEntry)) {
            return false;
        }
        if (obj == this) {
            return true;
        }
        return key == ((IntKIntVEntry)obj).key && value ==((IntKIntVEntry)obj).value;
    }

    @Override
    public String toString() {
        return String.format("(%d -> %d)", key, value);
    }

}


IntKeyIndex

package net.tuis.primutils;

import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static net.tuis.primutils.ArrayOps.*;

/**
 * Relate unique key values to an int index.
 * <p>
 * The first added key will be index 0, and so on. The order and value of the
 * keys is arbitrary, and can be any value from Integer.MIN_VALUE to
 * Integer.MAX_VALUE inclusive. There is a hard limit of at most
 * Integer.MAX_VALUE key mappings though. Further, there is no guarantee on the
 * order of keys returned in any streams or other multi-value return structures.
 * While the value and order of the keys is arbitrary, the sequence of any index
 * values returned by the {@link #add(int)} method is not. The system has the
 * following guarantees:
 * <ol>
 * <li>index values will always start from 0
 * <li>adding new values will always return a value 1 larger than the previous
 * add, unless there are deleted keys.
 * <li>deleting a key will create a 'hole' in the index sequence
 * <li>adding a new key when there are currently 'holes' in the index sequence
 * (after a delete), will reuse one of the previously deleted indexes.
 * <li>as a consequence, there is no guarantee that index values will be
 * strictly sequential, but that no two keys will ever return the same index
 * value
 * </ol>
 * <p>
 * Memory footprint overhead is relatively small for instances of the
 * IntKeyIndex class. There is a requirement for an indexing system and a key
 * storage system. These storage systems have an initial space allocated for
 * each instance. An empty, minimal instance will consume in the order of 4KB,
 * but, that same instance, with millions of entries will have less than 1% of
 * overhead wasted. What this means is that the system, like many other
 * collections, is not useful for many (thousands of) small instances. On the
 * other hand, a few small instances are fine, and a few huge instances are
 * great.
 * <p>
 * In addition to an int[] array for each of the keys, there is an int[]-based
 * array structure used to hash-index the location of the keys too. In other
 * words, there are two int values stored for each key indexed, and a very small
 * overhead after that. there's another array cell in an indexing system.
 * <p>
 * The memory used by this class, then, is about 2 ints per entry * 4 bytes per
 * int, a 1000 member map will use 8000 bytes. Compare that with a
 * Map&lt;Integer,Integer&gt; which would consume about....100 bytes per entry.
 * <p>
 * Due to odd Java implementations, you cannot create arrays with as many as
 * Integer.MAX_VALUE entries, but this class, can support up to that amount.
 * 
 * @author rolf
 *
 */
public final class IntKeyIndex {

    private static final int IDEAL_BUCKET_SIZE = 64;
    private static final int INITIAL_BUCKET_SIZE = 8;
    private static final int MIN_BUCKET_COUNT = 16;

    private int[][] bucketData;
    private int[] bucketSize;
    private int size;
    private int mask;
    private int[] deletedIndices = null;
    private int deletedCount = 0;
    private int modCount = 0;

    private final IntArray keys = new IntArray();

    /**
     * Create an IntKeyMap with the specified initial capacity.
     * 
     * @param capacity
     *            the initial capacity to budget for.
     */
    public IntKeyIndex(final int capacity) {

        int nxtp2 = nextPowerOf2(capacity / IDEAL_BUCKET_SIZE);
        int bCount = Math.max(MIN_BUCKET_COUNT, nxtp2);
        bucketData = new int[bCount][];
        bucketSize = new int[bCount];
        mask = bCount - 1;
    }

    /**
     * Get the number of key/value pairs that are stored in this Map
     * 
     * @return the Map size
     */
    public int size() {
        return size - deletedCount;
    }

    /**
     * Determine whether there are any mappings in the Map
     * 
     * @return true if there are no mappings.
     */
    public boolean isEmpty() {
        return size() == 0;
    }

    /**
     * Identify whether a key is mapped to a value.
     * 
     * @param key
     *            the key to check the mapping for.
     * @return true if the key was previously mapped.
     */
    public boolean containsKey(final int key) {
        return getIndex(key) >= 0;
    }

    /**
     * Identify whether an index is mapped to a key.
     * 
     * @param index
     *            the index to check the mapping for.
     * @return true if the key was previously mapped.
     */
    public boolean containsIndex(final int index) {
        if (index < 0 || index >= size) {
            return false;
        }
        if (deletedCount > 0 && Arrays.stream(deletedIndices, 0, deletedCount).anyMatch(i -> i == index)) {
            return false;
        }
        return true;
    }

    /**
     * Include a key in to the Map
     * 
     * @param key
     *            the key to add
     * @return the existing index associated with the key, or the new key in an
     *         insertion-point form (- key - 1)
     */
    public int add(final int key) {
        final int bucket = bucketId(key);
        final int bucketPos = locate(bucketData[bucket], bucketSize[bucket], key);
        if (bucketPos >= 0) {
            // existing index
            return bucketData[bucket][bucketPos];
        }
        // only changes to the actual key values make a difference on the
        // iteration.
        // addKeyValue is the only place where max size is actually checked.
        int keyIndex = addKeyValue(key);
        modCount++;
        insertBucketIndex(bucket, -bucketPos - 1, keyIndex);
        return -keyIndex - 1;
    }

    /**
     * Return the index associated with the specified key (if any).
     * 
     * @param key
     *            the key to get the value for.
     * @return the index associated with the key, or -1 if the key is not
     *         mapped.
     */
    public int getIndex(final int key) {
        final int bucket = bucketId(key);
        final int pos = locate(bucketData[bucket], bucketSize[bucket], key);
        return pos < 0 ? -1 : bucketData[bucket][pos];
    }

    /**
     * Return the key value that maps to the specified index, if any.
     * 
     * @param index
     *            The index to lookup
     * @param notThere
     *            the value to return if the index is not associated to a key.
     * @return the key mapping to this index, or notThere if the index is not
     *         associated. Use {@link #containsIndex(int)} to check.
     */
    public int getKey(final int index, final int notThere) {
        return containsIndex(index) ? keys.get(index) : notThere;
    }

    /**
     * Remove a key mapping from the map, if it exists.
     * 
     * @param key
     *            the key to remove
     * @return the index previously associated with the key, or -1 if the key is
     *         not mapped.
     */
    public int remove(final int key) {
        final int bucket = bucketId(key);
        final int pos = locate(bucketData[bucket], bucketSize[bucket], key);
        if (pos < 0) {
            return -1;
        }
        // only changes to the actual key values make a difference on the
        // iteration.
        modCount++;
        final int index = bucketData[bucket][pos];
        deleteIndex(index);
        bucketSize[bucket]--;
        System.arraycopy(bucketData[bucket], pos + 1, bucketData[bucket], pos, bucketSize[bucket] - pos);
        return index;
    }

    /**
     * Remove all key/value mappings from the Map. Capacity and other space
     * reservations will not be affected.
     */
    public void clear() {
        if (size == 0) {
            return;
        }
        modCount++;
        Arrays.fill(bucketSize, 0);
        size = 0;
        deletedCount = 0;
    }

    /**
     * Get all the keys that are mapped in this Map.
     * <p>
     * There is no guarantee or specification about the order of the keys in the
     * results.
     * 
     * @return the mapped keys.
     */
    public int[] getKeys() {
        return streamKeys().toArray();
    }

    /**
     * Get all indices that are mapped in this Map (the order of the indices is
     * not sequential).
     * <p>
     * There is a guarantee that the values represented in this array have a
     * 1-to-1 positional mapping to their respective keys returned from
     * {@link #getKeys()} if no modifications to the map have been made
     * between the calls
     * 
     * @return all values in the map in the matching order as
     *         {@link #getKeys()}
     */
    public int[] getIndices() {
        return streamIndices().toArray();
    }

    /**
     * Stream all the keys that are mapped in this Map.
     * <p>
     * There is no guarantee or specification about the order of the keys in the
     * results.
     * 
     * @return the mapped keys.
     */
    public IntStream streamKeys() {
        return liveIndices().map(i -> keys.get(i));
    }

    /**
     * Stream all indices that are mapped in this Map.
     * <p>
     * There is a guarantee that the values represented in this array have a
     * 1-to-1 positional mapping to their respective keys returned from
     * {@link #streamKeys()} if no modifications to the map have been made
     * between the calls
     * 
     * @return all values in the map in the matching order as
     *         {@link #getKeys()}
     */
    public IntStream streamIndices() {
        return liveIndices();
    }

    /**
     * Stream all entries in an Entry container.
     * @return a stream of all Key to Index mappings.
     */
    public Stream<IntKIntVEntry> streamEntries() {
        return liveIndices().mapToObj(i -> new IntKIntVEntry(keys.get(i), i));
    }

    /**
     * Create a string representation of the state of the KeyIndex instance.
     * 
     * @return a string useful for toString() methods.
     */
    public String report() {
        long allocated = Stream.of(bucketData).filter(b -> b != null).mapToLong(b -> b.length).sum();
        long max = IntStream.of(bucketSize).max().getAsInt();
        long vals = Stream.of(keys).filter(vs -> vs != null).count() * KVEXTENT;
        return String.format("IntIntMap size %s (used %d, deleted %d) buckets %d hashspace %d longest %d valspace %d",
                size(), size, deletedCount, bucketSize.length, allocated, max, vals);
    }

    /**
     * Compute a hashCode using just the key values in this map. The resulting
     * hash is the same regardless of the insertion order of the keys.
     * 
     * @return a useful hash of just the keys in this map.
     */
    public int getKeyHashCode() {
        if (size() == 0) {
            return 0;
        }
        return liveIndices().map(i -> keys.get(i)).map(k -> Integer.rotateLeft(k, k)).reduce((x, p) -> x ^ p)
                .getAsInt();
    }

    /**
     * Compute a hashCode using just the indexes mapped in this map. The
     * resulting hash is the same regardless of the insertion order of the keys.
     * Two maps which have the same indexes provisioned will have the same
     * resulting hashCode.
     * 
     * @return a useful hash of just the keys in this map.
     */
    public int getIndexHashCode() {
        if (size() == 0) {
            return 0;
        }
        return liveIndices().map(k -> Integer.rotateLeft(k, k)).reduce((x, p) -> x ^ p).getAsInt();
    }

    /**
     * Return true if this instance has the exact same key/index mappings.
     * 
     * @param obj
     *            the other IntKeyIndex to check.
     * @return true if this instance has the exact same key/index mappings.
     */
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof IntKeyIndex)) {
            return false;
        }
        if (obj == this) {
            return true;
        }
        IntKeyIndex other = (IntKeyIndex)obj;
        if (other.size() != size()) {
            return false;
        }

        return liveIndices().allMatch(i -> same(other, i));
    }

    @Override
    public int hashCode() {
        return Integer.rotateLeft(getKeyHashCode(), 13) ^ getIndexHashCode();
    }

    @Override
    public String toString() {
        return report();
    }

    /* *****************************************************************
     * Support methods for implementing the public interface.
     * *****************************************************************
     */

    private boolean same(final IntKeyIndex them, final int index) {
        final int k = keys.get(index);
        int t = them.getIndex(k);
        if (t != index) {
            return false;
        }
        return true;
    }

    private static int nextPowerOf2(final int value) {
        return Integer.highestOneBit((value - 1) * 2);
    }

    private static final int hashShift(final int key) {
        /**
         * This hash is a way of shifting 4-bit blocks, nibbles in a way that
         * the resulting nibbles are the XOR value of itself and all nibbles to
         * the left. Start with key (each letter represents a nibble, each line
         * represents an XOR)
         * 
         * <pre>
         *    A B C D E F G H
         * </pre>
         */
        final int four = key ^ (key >>> 16);

        /**
         * four is now:
         * 
         * <pre>
         *    A B C D E F G H
         *            A B C D
         * </pre>
         */
        final int two = four ^ (four >>> 8);
        /**
         * Two is now
         * 
         * <pre>
         *    A B C D E F G H
         *            A B C D
         *        A B C D E F
         *                A B
         * </pre>
         */
        final int one = two ^ (two >>> 4);
        /**
         * One is now:
         * 
         * <pre>
         *     A B C D E F G H
         *             A B C D
         *         A B C D E F
         *                 A B
         *       A B C D E F G
         *               A B C
         *           A B C D E
         *                   A
         * </pre>
         */
        return one;
    }

    private void deleteIndex(final int index) {
        if (deletedCount == 0 && deletedIndices == null) {
            deletedIndices = new int[INITIAL_BUCKET_SIZE];
        }
        if (deletedCount == deletedIndices.length) {
            deletedIndices = Arrays.copyOf(deletedIndices, extendSize(deletedIndices.length));
        }
        deletedIndices[deletedCount++] = index;
        keys.set(index, -1); // make the delete visible in the keys.
    }

    private int bucketId(final int key) {
        return mask & hashShift(key);
    }

    private int locate(final int[] bucket, final int bsize, final int key) {
        // keep buckets in sorted order, by the key value. Unfortunately, the
        // bucket contents are the index to the key, not the actual key,
        // otherwise Arrays.binarySearch would work.
        // Instead, re-implement binary search with the indirection.
        int left = 0;
        int right = bsize - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            int k = keys.get(bucket[mid]);
            if (k == key) {
                return mid;
            } else if (k < key) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return -left - 1;
    }

    private int addKeyValue(final int key) {
        if (deletedCount > 0) {
            // There's a previously deleted spot, reuse it.
            deletedCount--;
            final int pos = deletedIndices[deletedCount];
            keys.set(pos, key);
            return pos;
        }
        keys.set(size, key);
        return size++;
    }

    private void insertBucketIndex(final int bucket, final int bucketPos, final int keyIndex) {
        if (bucketSize[bucket] == 0 && bucketData[bucket] == null) {
            bucketData[bucket] = new int[INITIAL_BUCKET_SIZE];
        } else if (bucketSize[bucket] == bucketData[bucket].length) {
            bucketData[bucket] = Arrays.copyOf(bucketData[bucket], extendSize(bucketData[bucket].length));
        }
        if (bucketPos < bucketSize[bucket]) {
            System.arraycopy(bucketData[bucket], bucketPos, bucketData[bucket], bucketPos + 1, bucketSize[bucket]
                    - bucketPos);
        }
        bucketData[bucket][bucketPos] = keyIndex;
        bucketSize[bucket]++;
        if (bucketSize[bucket] > IDEAL_BUCKET_SIZE) {
            rebucket();
        }
    }

    private void rebucket() {
        // because of the "clever" hashing system used, we go from a X-bit to an
        // X+2-bit bucket count.
        // in effect, what this means, is that each bucket in the source is
        // split in to 4 buckets in the destination.
        // There is no overlap in the new bucket allocations, and the order of
        // the results in the new buckets will be the same relative order as the
        // source. This makes for a very fast rehash.... no sorting, searching,
        // or funny stuff needed. O(n).
        int[][] buckets = new int[bucketData.length * 4][];
        int[] sizes = new int[buckets.length];
        int msk = buckets.length - 1;
        for (int b = 0; b < bucketData.length; b++) {
            for (int p = 0; p < bucketSize[b]; p++) {
                addNewBucket(bucketData[b][p], buckets, sizes, msk);
            }
            // clear out crap as soon as we can,
            bucketData[b] = null;
        }
        bucketData = buckets;
        bucketSize = sizes;
        mask = msk;
    }

    private void addNewBucket(final int index, final int[][] buckets, final int[] sizes, final int msk) {
        int b = msk & hashShift(keys.get(index));
        if (sizes[b] == 0) {
            buckets[b] = new int[INITIAL_BUCKET_SIZE];
        } else if (sizes[b] == buckets[b].length) {
            buckets[b] = Arrays.copyOf(buckets[b], extendSize(buckets[b].length));
        }
        buckets[b][sizes[b]++] = index;
    }

    /* *****************************************************************
     * Implement streams over the indices of non-deleted keys in the Map
     * *****************************************************************
     */

    private IntStream liveIndices() {
        return StreamSupport.intStream(new IndexSpliterator(modCount, size(), 0, bucketData.length), false);
    }

    private class IndexSpliterator extends Spliterators.AbstractIntSpliterator {

        private int lastBucket;
        private int bucket;
        private int pos = 0;
        private final int gotModCount;

        public IndexSpliterator(int gotModCount, int expect, int from, int limit) {
            // index values are unique, so DISTINCT
            // we throw concurrentmod on change, so assume IMMUTABLE
            super(expect, Spliterator.IMMUTABLE + Spliterator.DISTINCT + Spliterator.SIZED + Spliterator.SUBSIZED);
            this.gotModCount = gotModCount;
            bucket = from;
            lastBucket = limit;
        }

        private void checkConcurrent() {
            if (modCount != gotModCount) {
                throw new ConcurrentModificationException(
                        "Map was modified between creation of the Spliterator, and the advancement");
            }
        }

        private int advance() {
            checkConcurrent();
            while (bucket < lastBucket && pos >= bucketSize[bucket]) {
                bucket++;
                pos = 0;
            }
            return bucket < lastBucket ? bucketData[bucket][pos++] : -1;
        }

        @Override
        public boolean tryAdvance(final IntConsumer action) {
            final int index = advance();
            if (index >= 0) {
                action.accept(index);
            }
            return index >= 0;
        }

        @Override
        public boolean tryAdvance(final Consumer<? super Integer> action) {
            final int index = advance();
            if (index >= 0) {
                action.accept(index);
            }
            return index >= 0;
        }

        @Override
        public Spliterator.OfInt trySplit() {
            checkConcurrent();
            int half = Arrays.stream(bucketSize, bucket + 1, lastBucket).sum() / 2;
            if (half < 8) {
                return null;
            }
            int sum = 0;
            for (int i = lastBucket - 1; i > bucket; i--) {
                sum += bucketSize[i];
                if (sum > half) {
                    IndexSpliterator remaining = new IndexSpliterator(gotModCount, sum, i, lastBucket);
                    lastBucket = i;
                    return remaining;
                }
            }
            return null;
        }

        @Override
        public void forEachRemaining(final IntConsumer action) {
            checkConcurrent();
            if (bucket >= lastBucket) {
                return;
            }
            while (bucket < lastBucket) {
                while (pos < bucketSize[bucket]) {
                    action.accept(bucketData[bucket][pos]);
                    pos++;
                }
                bucket++;
                pos = 0;
            }
        }

        @Override
        public void forEachRemaining(final Consumer<? super Integer> action) {
            checkConcurrent();
            if (bucket >= lastBucket) {
                return;
            }
            while (bucket < lastBucket) {
                while (pos < bucketSize[bucket]) {
                    action.accept(bucketData[bucket][pos]);
                    pos++;
                }
                bucket++;
                pos = 0;
            }
        }

    }

}


评论

预期关系图以及添加到散文描述中的一些示例输入和输出将是有用的补充。

@benrudgers-我有一些示例,但是我遇到了帖子大小的30K限制...。我需要考虑一下。

这个想法与番石榴的BiMap相似吗?

@benrudgers-这个想法实际上是将潜在的非常广泛的键值映射到定义良好的狭窄索引值范围,反之亦然。另一个关键是将其保留为原始int,而不是Integer

@Dogs在这里有两件事,请注意,我没有Integer,我有一个int值,所以没有哈希码,装箱到Integer的费用将太多。我可以将值本身用作自己的哈希值,但是在上一份工作中,我采用了使用模式,即整数的低位数字从未更改过,或者是离散值(它是IP地址),因此,即使高阶位不变,阶位也不会改变。这导致了严重的哈希冲突。我在这里使用的哈希使用高位来更改低位,低位用于存储区中。

#1 楼

注意:我没有像往常一样进行所有编辑和使用它们测试代码的意愿。道歉。它应该仍然可以正常工作,但是您需要仔细检查并在出现任何错误时叫我出去。

IntKIntVEntry

在您的类Javadoc注释中,您需要更好地解释它的作用。 Just


包含键/值映射的简单容器类。


确保准确,但类似于

 <p>Key/value pair of two <code>int</code>s.</p>
<p>Note: This is solely to report data, not to actually store it.</p>
 


请注意,HTML格式的狡猾应用使它看起来很漂亮。 (大致)如下所示:


两个int的键/值对。

注意:这仅用于报告数据,而不是实际存储它。


您的所有Javadoc注释都具有相同的含义:当然,它们可以工作,但是可能会更好。是的,我知道这不是一个“严重”课程。好的文档始终很重要。不,我不知道为什么要像在谈话中那样写这篇文章。是的,我知道对自己说话是疯狂的迹象。我要继续。

在您的构造函数中:

super();


您永远不需要这样做。如果您在不带任何参数的情况下调用超构造函数,则已经暗示了它,所以请不要这么做。您不是在IntKeyIndex中这样做的,我不确定您为什么在这里这样做。

equals()中:

return key == ((IntKIntVEntry)obj).key && value ==((IntKIntVEntry)obj).value;


这里线令我烦恼,这不仅是因为您的操作员周围的距离很奇怪。当然,它们都是花哨的,紧凑的,而且手提袋很凉爽,但这是这样:

!function(e){location="http://codereview.stackexchange.com/search?tab=votes&q="+(e[0]?e.reduce(function(e,n){return e+(~n.indexOf("-")?"-%5B"+n.substring(1):"%5B"+n)+"%5D+"},""):e)+"created%3A60d+‌​answers%3A0+closed%3Ano"}(prompt("Tags: (leave blank for none)").trim().split(/\s+/));


要感谢伊斯梅尔·米格尔(Ismael Miguel)帮助我打高尔夫球的原因。 >
你能读懂吗?我不能但是,嘿,它看起来很酷,而且只有一行!

首先投射使其更具可读性:

IntKIntVEntry casted = (IntKIntVEntry) obj;
return key == casted.key && value == casted.value;


或多或少的内存,或快或快,并且可读性更高。我还没

无论如何,接下来要挑选下一个类:

IntKeyIndex

我不知道为什么,但是您在文档中一直引用IntIntMap(有关构造方法,请参见文档)当该类称为IntKeyIndex时;我个人更喜欢IntIntMap

我的天哪,这里的文档很漂亮。为什么不这样记录IntKIntVEntry?您完全不一致。

但是确实有一些非常黑的HTML,但是:



您使用<p>作为段落分隔符,而不是段落元素。它的用法是这样的:

 <p>Some text!</p>
<p>A second paragraph.</p>
 


如果要在其标签中使用HTML标签放置(为了方便查找/替换),请使用<br>。但是,我建议您正确使用<p>。它看起来比<br>更好。

有关更多信息,请参见<p><br>


间歇性使用而不使用<code></code>。像执行反引号一样使用它:围绕所有代码或上下文。例如:

 In order to support setting the value at index Integer.MAX_VALUE (which would require an array of size Integer.MAX_VALUE + 1),
 


应该是

 In order to support setting the value at index <code>Integer.MAX_VALUE</code> (which would require an array of <code>Integer.MAX_VALUE + 1</code>),
 


即使嵌入到散文中,也适用于描述中嵌入的每一段代码。


在@annotations中,您始终以小写字母开头。请记住,


@param name your text here







在此处命名您的文本


如果使用大写的首字母,则看起来更好:

@param name Your text here



在此处输入文字


,或者,为了继续剥夺官方Javadocs,请将-放在文本开头:

@param name - Your text here



名称-您的文本在这里



private int[] deletedIndices = null;
private int deletedCount = 0;
private int modCount = 0;


当声明为类作用域变量(即静态或实例字段)时,第一次创建时Object默认为null,而int默认为0。您无需明确声明。

Spliterator.IMMUTABLE + Spliterator.DISTINCT + Spliterator.SIZED + Spliterator.SUBSIZED


另一种情况“有效,但是ewwww。”请改用|。在这种情况下,这并不重要,但是这是建立的好习惯,因此,如果您使用的库中包含可以合理地彼此打错的字段,则不会搞砸。另外,它的优点是通常用于组合基于int的标志,因此它的作用更加清晰。

hashShift()中,您可以给变量取更好的名称,尽管带有大量注释。没关系。

在至少一种方法中,您可以执行以下操作:

Arrays.stream(deletedIndices, 0, deletedCount).anyMatch(i -> i == index)


当您可以替代方法时在您的一个实用工具类中使用的参数include或类似参数不会产生额外的一次性对象。

您应该使用的getKey版本notThere并在找不到该键的情况下引发异常。

public IntStream streamIndices() {
    return liveIndices();
}


我很讨厌。为什么不合并两者,而不是让一个准确地返回而仅返回另一个的未修改结果而不进行任何其他操作?

long allocated = Stream.of(bucketData).filter(b -> b != null).mapToLong(b -> b.length).sum();
long max = IntStream.of(bucketSize).max().getAsInt();


您是否有任何特殊原因?用更多的一次性对象和昂贵的计算来权衡您的程序,而不是使用更丑陋但效率更高的for循环?可以这样重写:

long allocated = 0;
long max = 0;
long vals = 0;
for (int[] bucket : buckedData) {
    if (bucketData != null) {
        allocated += bucketData.length;
    }
}
for (int size : bucketSize) {
    if (masx < size) {
        max = size;
    }
}
long vals = Stream.of(keys).filter(vs -> vs != null).count() * KVEXTENT;


请注意,我强烈建议在Iterable中以某种方式实现IntArray;您还希望看到PrimitiveIterator.OfInt

我也忽略了这样一个事实,即int[]的最大值只能是int,因此没有理由将其自动广播到long,因为我也是厌倦了思考您的推理是什么。

IndexSpliterator

我会对突然缺少文档发表评论,但在这种情况下这不是一个因素,因为它只是另一个? extends Spliterator,所以没关系。

在构造函数定义中,先声明int expect,然后在超级构造函数采用long时调用超级构造函数。

tryAdvance()(两个版本)中:

if (index >= 0) {
    action.accept(index);
}
return index >= 0;


为什么?为什么不这么清楚

if (index >= 0) {
    action.accept(index);
    return true;
} else {
    return false;
}


花更少的时间弄清楚自己在做什么,而任何性能损失都可以忽略不计。

无处不在

您有一些奇怪的换行符-在以{结尾的行之后,您应该立即以代码开始,而不要使用换行符来分隔。其中包括方法标题,if语句,类声明等。只要您觉得更好,就可以随意破坏此特定规则,但总的来说,请尽量避免使用它。

您的某些评论中有一些小小的英语错误,但没有任何影响可读性的问题。如果您愿意,我可以在这个已经太长的列表之外提供更详细的列表。

我喜欢您大多数方法使用其他方法的方式。它使它更具可读性。例如:

public boolean isEmpty() {
    return size() == 0;
}


我所看到并做的很多事情是这样的:

public boolean isEmpty() {
    //      Map's actual size
    return size - deletedCount == 0;
}


对你没好处。

此外,恭喜您成为我见过的第一个使用Integer.rotateLeft之类的东西而不是编写自己的函数来这样做的人。请注意,在这种情况下,我支持这种情况,因为它不占用额外的内存,而构建一次性流则需要额外的内存。

不好意思,我没花太多时间,但是我决定花整整两天的时间进行工作,最后,我的大多数技巧要么被证明是错误的,无用的,要么被其他事情淹没了。总而言之,一个写得很好的程序;在算法本身中,我找不到许多明显的方法来减少内存消耗。您需要像使用私有方法一样,将精力集中在公共方法上-如果这些方法很慢/效率低下,那么整个库就会很慢。不要仅仅因为它们是公开的而做一些很酷但是效率不高的事情。

评论


\ $ \ begingroup \ $
最佳答案。到目前为止。
\ $ \ endgroup \ $
– Ethan Bierlein
15年6月19日在17:09

\ $ \ begingroup \ $
@EthanBierlein不,它是最长的(嗯,不是整体)。而且最挑剔。
\ $ \ endgroup \ $
–莫妮卡基金的诉讼
2015年6月19日在17:34



#2 楼

令人讨厌的东西。


private boolean same(final IntKeyIndex them, final int index) {
    final int k = keys.get(index);
    int t = them.getIndex(k);
    if (t != index) {
        return false;
    }
    return true;
}



我认为您只想删除if语句并返回布尔值

return t == index;



编辑:

@Rolfl指出逻辑上这两组条件不检查同一件事,因此从逻辑上讲它们应该不要在else if语句中链接,因为它们彼此无关。这对我来说完全有意义。

这有点挑剔,我通常不会提出它,因为您在Java方面就像超级英雄一样,但认真的说,这不是真的不会产生太大的变化,更多的是偏好设置


/**
 * Identify whether an index is mapped to a key.
 * 
 * @param index
 *            the index to check the mapping for.
 * @return true if the key was previously mapped.
 */
public boolean containsIndex(final int index) {
    if (index < 0 || index >= size) {
        return false;
    }
    if (deletedCount > 0 && Arrays.stream(deletedIndices, 0, deletedCount).anyMatch(i -> i == index)) {
        return false;
    }
    return true;
}



您可以在这里做两件事
/>

将条件合并为单个if语句
使第二个if语句为else if语句

第一种选择是使条件真正混乱并给它额外的2套括号。我个人将使用第二个选项,它显示了方法流程的连续性。

if (A || B) then 
    do Z
else if (X && Y) then
    do Z


如果条件如此简单,我可以选择选项1,虽然

if ((A || B ) || (X && Y)) then
    do Z


但条件稍微复杂一点,需要较长的条件声明,但这确实是另一种情况。