【问题标题】:Sparse matrices / arrays in JavaJava中的稀疏矩阵/数组
【发布时间】:2010-09-28 06:12:11
【问题描述】:

我正在开发一个用 Java 编写的项目,这需要我构建一个非常大的二维稀疏数组。非常稀疏,如果这有所作为。无论如何:这个应用程序最关键的方面是时间方面的效率(假设内存负载,虽然几乎没有无限到允许我使用标准二维数组 - 关键范围在两个维度上都是数十亿)。

在数组中的数以千计的单元格中,将有数十万个单元格包含一个对象。我需要能够非常快速地修改单元格内容。

无论如何:有人知道为此目的特别好的图书馆吗?它必须是 Berkeley、LGPL 或类似的许可证(没有 GPL,因为该产品不能完全开源)。或者,如果只有一种非常简单的方法可以制作自制的稀疏数组对象,那也可以。

我正在考虑MTJ,但没有听到任何关于其质量的意见。

【问题讨论】:

  • 这是一篇您可能感兴趣的论文,其中讨论了用于矩阵计算的数据结构,包括稀疏数组:http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.13.7544您可以以 PDF 或 PS 格式下载该论文。它也包括源代码。
  • 也许Colt 有帮助。它提供了一个稀疏矩阵的实现。
  • 我刚刚使用了Trove,它在使用 int->int 映射(用于实现稀疏矩阵)时提供了比 Colt 更好的性能。

标签: java algorithm sparse-matrix sparse-array


【解决方案1】:

使用 hashmap 构建的稀疏数组对于频繁读取的数据效率非常低。最有效的实现使用允许访问分段分布的单个向量的 Trie。

Trie 可以通过只执行只读的 TWO 数组索引来计算表中是否存在元素,以获取存储元素的有效位置,或者知道它是否在底层存储中不存在。

它还可以为稀疏数组的默认值在后备存储中提供默认位置,这样您就不需要对返回的索引进行任何测试,因为 Trie 保证所有可能的源索引至少会映射到后备存储中的默认位置(您将经常存储零、空字符串或空对象)。

存在支持快速更新 Tries 的实现,使用可选的“compact()”操作在多个操作结束时优化后备存储的大小。尝试比哈希图快得多,因为它们不需要任何复杂的哈希函数,也不需要处理读取冲突(使用哈希图,读取和写入都有冲突,这需要一个循环跳到下一个候选位置,并对它们中的每一个进行测试以比较有效源索引...)

此外,Java Hashmaps 只能在 Objects 上建立索引,并且为每个散列源索引创建一个 Integer 对象(每次读取都需要创建这个对象,而不仅仅是写入)在内存操作方面是昂贵的,因为它强调垃圾收集器。

我真的希望 JRE 包含一个 IntegerTrieMap 作为慢速 HashMap 或 LongTrieMap 的默认实现作为更慢的 HashMap 的默认实现...但事实并非如此。



您可能想知道什么是 Trie?

它只是一个小的整数数组(范围小于矩阵的整个坐标范围),允许将坐标映射到向量中的整数位置。

例如,假设您想要一个 1024*1024 矩阵,其中仅包含几个非零值。与其将该矩阵存储在包含 1024*1024 个元素(超过 100 万个)的数组中,不如将其拆分为大小为 16*16 的子范围,而您只需要 64*64 个这样的子范围。

在这种情况下,Trie 索引将仅包含 64*64 整数 (4096),并且将至少有 16*16 数据元素(包含默认零,或稀疏矩阵中最常见的子范围)。

并且用于存储值的向量将仅包含 1 个彼此相等的子范围的副本(它们中的大多数都是零,它们将由相同的子范围表示)。

因此,不要使用 matrix[i][j] 之类的语法,而是使用以下语法:

trie.values[trie.subrangePositions[(i & ~15) + (j >> 4)] +
            ((i & 15) << 4) + (j & 15)]

使用 trie 对象的访问方法会更方便地处理。

这是一个示例,内置在一个注释类中(我希望它可以编译,因为它被简化了;如果有错误要纠正,请告诉我):

/**
 * Implement a sparse matrix. Currently limited to a static size
 * (<code>SIZE_I</code>, <code>SIZE_I</code>).
 */
public class DoubleTrie {

    /* Matrix logical options */        
    public static final int SIZE_I = 1024;
    public static final int SIZE_J = 1024;
    public static final double DEFAULT_VALUE = 0.0;

    /* Internal splitting options */
    private static final int SUBRANGEBITS_I = 4;
    private static final int SUBRANGEBITS_J = 4;

    /* Internal derived splitting constants */
    private static final int SUBRANGE_I =
        1 << SUBRANGEBITS_I;
    private static final int SUBRANGE_J =
        1 << SUBRANGEBITS_J;
    private static final int SUBRANGEMASK_I =
        SUBRANGE_I - 1;
    private static final int SUBRANGEMASK_J =
        SUBRANGE_J - 1;
    private static final int SUBRANGE_POSITIONS =
        SUBRANGE_I * SUBRANGE_J;

    /* Internal derived default values for constructors */
    private static final int SUBRANGES_I =
        (SIZE_I + SUBRANGE_I - 1) / SUBRANGE_I;
    private static final int SUBRANGES_J =
        (SIZE_J + SUBRANGE_J - 1) / SUBRANGE_J;
    private static final int SUBRANGES =
        SUBRANGES_I * SUBRANGES_J;
    private static final int DEFAULT_POSITIONS[] =
        new int[SUBRANGES](0);
    private static final double DEFAULT_VALUES[] =
        new double[SUBRANGE_POSITIONS](DEFAULT_VALUE);

    /* Internal fast computations of the splitting subrange and offset. */
    private static final int subrangeOf(
            final int i, final int j) {
        return (i >> SUBRANGEBITS_I) * SUBRANGE_J +
               (j >> SUBRANGEBITS_J);
    }
    private static final int positionOffsetOf(
            final int i, final int j) {
        return (i & SUBRANGEMASK_I) * MAX_J +
               (j & SUBRANGEMASK_J);
    }

    /**
     * Utility missing in java.lang.System for arrays of comparable
     * component types, including all native types like double here.
     */
    public static final int arraycompare(
            final double[] values1, final int position1,
            final double[] values2, final int position2,
            final int length) {
        if (position1 >= 0 && position2 >= 0 && length >= 0) {
            while (length-- > 0) {
                double value1, value2;
                if ((value1 = values1[position1 + length]) !=
                    (value2 = values2[position2 + length])) {
                    /* Note: NaN values are different from everything including
                     * all Nan values; they are are also neigher lower than nor
                     * greater than everything including NaN. Note that the two
                     * infinite values, as well as denormal values, are exactly
                     * ordered and comparable with <, <=, ==, >=, >=, !=. Note
                     * that in comments below, infinite is considered "defined".
                     */
                    if (value1 < value2)
                        return -1;        /* defined < defined. */
                    if (value1 > value2)
                        return 1;         /* defined > defined. */
                    if (value1 == value2)
                        return 0;         /* defined == defined. */
                    /* One or both are NaN. */
                    if (value1 == value1) /* Is not a NaN? */
                        return -1;        /* defined < NaN. */
                    if (value2 == value2) /* Is not a NaN? */
                        return 1;         /* NaN > defined. */
                    /* Otherwise, both are NaN: check their precise bits in
                     * range 0x7FF0000000000001L..0x7FFFFFFFFFFFFFFFL
                     * including the canonical 0x7FF8000000000000L, or in
                     * range 0xFFF0000000000001L..0xFFFFFFFFFFFFFFFFL.
                     * Needed for sort stability only (NaNs are otherwise
                     * unordered).
                     */
                    long raw1, raw2;
                    if ((raw1 = Double.doubleToRawLongBits(value1)) !=
                        (raw2 = Double.doubleToRawLongBits(value2)))
                        return raw1 < raw2 ? -1 : 1;
                    /* Otherwise the NaN are strictly equal, continue. */
                }
            }
            return 0;
        }
        throw new ArrayIndexOutOfBoundsException(
                "The positions and length can't be negative");
    }

    /**
     * Utility shortcut for comparing ranges in the same array.
     */
    public static final int arraycompare(
            final double[] values,
            final int position1, final int position2,
            final int length) {
        return arraycompare(values, position1, values, position2, length);
    }

    /**
     * Utility missing in java.lang.System for arrays of equalizable
     * component types, including all native types like double here.
     */ 
    public static final boolean arrayequals(
            final double[] values1, final int position1,
            final double[] values2, final int position2,
            final int length) {
        return arraycompare(values1, position1, values2, position2, length) ==
            0;
    }

    /**
     * Utility shortcut for identifying ranges in the same array.
     */
    public static final boolean arrayequals(
            final double[] values,
            final int position1, final int position2,
            final int length) {
        return arrayequals(values, position1, values, position2, length);
    }

    /**
     * Utility shortcut for copying ranges in the same array.
     */
    public static final void arraycopy(
            final double[] values,
            final int srcPosition, final int dstPosition,
            final int length) {
        arraycopy(values, srcPosition, values, dstPosition, length);
    }

    /**
     * Utility shortcut for resizing an array, preserving values at start.
     */
    public static final double[] arraysetlength(
            double[] values,
            final int newLength) {
        final int oldLength =
            values.length < newLength ? values.length : newLength;
        System.arraycopy(values, 0, values = new double[newLength], 0,
            oldLength);
        return values;
    }

    /* Internal instance members. */
    private double values[];
    private int subrangePositions[];
    private bool isSharedValues;
    private bool isSharedSubrangePositions;

    /* Internal method. */
    private final reset(
            final double[] values,
            final int[] subrangePositions) {
        this.isSharedValues =
            (this.values = values) == DEFAULT_VALUES;
        this.isSharedsubrangePositions =
            (this.subrangePositions = subrangePositions) ==
                DEFAULT_POSITIONS;
    }

    /**
     * Reset the matrix to fill it with the same initial value.
     *
     * @param initialValue  The value to set in all cell positions.
     */
    public reset(final double initialValue = DEFAULT_VALUE) {
        reset(
            (initialValue == DEFAULT_VALUE) ? DEFAULT_VALUES :
                new double[SUBRANGE_POSITIONS](initialValue),
            DEFAULT_POSITIONS);
    }

    /**
     * Default constructor, using single default value.
     *
     * @param initialValue  Alternate default value to initialize all
     *                      positions in the matrix.
     */
    public DoubleTrie(final double initialValue = DEFAULT_VALUE) {
        this.reset(initialValue);
    }

    /**
     * This is a useful preinitialized instance containing the
     * DEFAULT_VALUE in all cells.
     */
    public static DoubleTrie DEFAULT_INSTANCE = new DoubleTrie();

    /**
     * Copy constructor. Note that the source trie may be immutable
     * or not; but this constructor will create a new mutable trie
     * even if the new trie initially shares some storage with its
     * source when that source also uses shared storage.
     */
    public DoubleTrie(final DoubleTrie source) {
        this.values = (this.isSharedValues =
            source.isSharedValues) ?
            source.values :
            source.values.clone();
        this.subrangePositions = (this.isSharedSubrangePositions =
            source.isSharedSubrangePositions) ?
            source.subrangePositions :
            source.subrangePositions.clone());
    }

    /**
     * Fast indexed getter.
     *
     * @param i  Row of position to set in the matrix.
     * @param j  Column of position to set in the matrix.
     * @return   The value stored in matrix at that position.
     */
    public double getAt(final int i, final int j) {
        return values[subrangePositions[subrangeOf(i, j)] +
                      positionOffsetOf(i, j)];
    }

    /**
     * Fast indexed setter.
     *
     * @param i      Row of position to set in the sparsed matrix.
     * @param j      Column of position to set in the sparsed matrix.
     * @param value  The value to set at this position.
     * @return       The passed value.
     * Note: this does not compact the sparsed matric after setting.
     * @see compact(void)
     */
    public double setAt(final int i, final int i, final double value) {
       final int subrange       = subrangeOf(i, j);
       final int positionOffset = positionOffsetOf(i, j);
       // Fast check to see if the assignment will change something.
       int subrangePosition, valuePosition;
       if (Double.compare(
               values[valuePosition =
                   (subrangePosition = subrangePositions[subrange]) +
                   positionOffset],
               value) != 0) {
               /* So we'll need to perform an effective assignment in values.
                * Check if the current subrange to assign is shared of not.
                * Note that we also include the DEFAULT_VALUES which may be
                * shared by several other (not tested) trie instances,
                * including those instanciated by the copy contructor. */
               if (isSharedValues) {
                   values = values.clone();
                   isSharedValues = false;
               }
               /* Scan all other subranges to check if the position in values
                * to assign is shared by another subrange. */
               for (int otherSubrange = subrangePositions.length;
                       --otherSubrange >= 0; ) {
                   if (otherSubrange != subrange)
                       continue; /* Ignore the target subrange. */
                   /* Note: the following test of range is safe with future
                    * interleaving of common subranges (TODO in compact()),
                    * even though, for now, subranges are sharing positions
                    * only between their common start and end position, so we
                    * could as well only perform the simpler test <code>
                    * (otherSubrangePosition == subrangePosition)</code>,
                    * instead of testing the two bounds of the positions
                    * interval of the other subrange. */
                   int otherSubrangePosition;
                   if ((otherSubrangePosition =
                           subrangePositions[otherSubrange]) >=
                           valuePosition &&
                           otherSubrangePosition + SUBRANGE_POSITIONS <
                           valuePosition) {
                       /* The target position is shared by some other
                        * subrange, we need to make it unique by cloning the
                        * subrange to a larger values vector, copying all the
                        * current subrange values at end of the new vector,
                        * before assigning the new value. This will require
                        * changing the position of the current subrange, but
                        * before doing that, we first need to check if the
                        * subrangePositions array itself is also shared
                        * between instances (including the DEFAULT_POSITIONS
                        * that should be preserved, and possible arrays
                        * shared by an external factory contructor whose
                        * source trie was declared immutable in a derived
                        * class). */
                       if (isSharedSubrangePositions) {
                           subrangePositions = subrangePositions.clone();
                           isSharedSubrangePositions = false;
                       }
                       /* TODO: no attempt is made to allocate less than a
                        * fully independant subrange, using possible
                        * interleaving: this would require scanning all
                        * other existing values to find a match for the
                        * modified subrange of values; but this could
                        * potentially leave positions (in the current subrange
                        * of values) unreferenced by any subrange, after the
                        * change of position for the current subrange. This
                        * scanning could be prohibitively long for each
                        * assignement, and for now it's assumed that compact()
                        * will be used later, after those assignements. */
                       values = setlengh(
                           values,
                           (subrangePositions[subrange] =
                            subrangePositions = values.length) +
                           SUBRANGE_POSITIONS);
                       valuePosition = subrangePositions + positionOffset;
                       break;
                   }
               }
               /* Now perform the effective assignment of the value. */
               values[valuePosition] = value;
           }
       }
       return value;
    }

    /**
     * Compact the storage of common subranges.
     * TODO: This is a simple implementation without interleaving, which
     * would offer a better data compression. However, interleaving with its
     * O(N²) complexity where N is the total length of values, should
     * be attempted only after this basic compression whose complexity is
     * O(n²) with n being SUBRANGE_POSITIIONS times smaller than N.
     */
    public void compact() {
        final int oldValuesLength = values.length;
        int newValuesLength = 0;
        for (int oldPosition = 0;
                 oldPosition < oldValuesLength;
                 oldPosition += SUBRANGE_POSITIONS) {
            int oldPosition = positions[subrange];
            bool commonSubrange = false;
            /* Scan values for possible common subranges. */
            for (int newPosition = newValuesLength;
                    (newPosition -= SUBRANGE_POSITIONS) >= 0; )
                if (arrayequals(values, newPosition, oldPosition,
                        SUBRANGE_POSITIONS)) {
                    commonSubrange = true;
                    /* Update the subrangePositions|] with all matching
                     * positions from oldPosition to newPosition. There may
                     * be several index to change, if the trie has already
                     * been compacted() before, and later reassigned. */
                    for (subrange = subrangePositions.length;
                         --subrange >= 0; )
                        if (subrangePositions[subrange] == oldPosition)
                            subrangePositions[subrange] = newPosition;
                    break;
                }
            if (!commonSubrange) {
                /* Move down the non-common values, if some previous
                 * subranges have been compressed when they were common.
                 */
                if (!commonSubrange && oldPosition != newValuesLength) {
                    arraycopy(values, oldPosition, newValuesLength,
                        SUBRANGE_POSITIONS);
                    /* Advance compressed values to preserve these new ones. */
                    newValuesLength += SUBRANGE_POSITIONS;
                }
            }
        }
        /* Check the number of compressed values. */
        if (newValuesLength < oldValuesLength) {
            values = values.arraysetlength(newValuesLength);
            isSharedValues = false;
        }
    }

}

注意:此代码不完整,因为它处理单个矩阵大小,并且它的压缩器仅限于检测常见的子范围,而不是交错。

此外,代码不会根据矩阵大小确定用于将矩阵拆分为子范围(对于 x 或 y 坐标)的最佳宽度或高度。它只使用相同的静态子范围大小 16(对于两个坐标),但它可以方便地使用 2 的任何其他小幂(但非 2 的幂会减慢 int indexOf(int, int)int offsetOf(int, int) 内部方法的速度),独立对于两个坐标,直到矩阵的最大宽度或高度。理想情况下,compact() 方法应该能够确定最佳拟合尺寸。

如果这些拆分子范围的大小可以变化,则需要为这些子范围大小添加实例成员,而不是静态的SUBRANGE_POSITIONS,并使静态方法int subrangeOf(int i, int j)int positionOffsetOf(int i, int j) 变为非静态;并且初始化数组 DEFAULT_POSITIONSDEFAULT_VALUES 将需要以不同方式删除或重新定义。

如果您想支持交错,基本上您将首先将现有值分成两个大小大致相同的值(两者都是最小子范围大小的倍数,第一个子范围可能比第二个子范围多一个),您将在所有连续位置扫描较大的一个,以找到匹配的交错;那么您将尝试匹配这些值。然后,您将通过将子集分成两半(也是最小子范围大小的倍数)来递归循环,然后再次扫描以匹配这些子集(这会将子集的数量乘以 2:您必须想知道是否加倍subrangePositions 索引的大小与值的现有大小相比值得该值,以查看它是否提供有效的压缩(如果没有,则停止:您已直接从交错压缩过程中找到最佳子范围大小)。 case; 在压缩过程中,子范围的大小是可变的。

但是此代码显示了如何分配非零值并为其他(非零)子范围重新分配 data 数组,然后如何优化(在使用 @987654334 执行分配后使用 compact() @方法)当数据中可能存在重复的子范围时存储此数据,并在subrangePositions数组中的相同位置重新索引。

不管怎样,trie 的所有原理都在那里实现:

  1. 使用单个向量而不是数组的双索引数组(每个单独分配)来表示矩阵总是更快(并且在内存中更紧凑,意味着更好的局部性)。在double getAt(int, int) 方法中可以看到改进!

  2. 您可以节省大量空间,但在分配值时可能需要一些时间来重新分配新的子范围。出于这个原因,子范围不应该太小,否则重新分配会过于频繁地发生以设置您的矩阵。

  3. 通过检测常见的子范围,可以将初始大矩阵自动转换为更紧凑的矩阵。一个典型的实现将包含一个方法,例如上面的compact()。但是,如果 get() 访问非常快而 set() 非常快,那么如果有很多公共子范围要压缩(例如,用自身减去一个大的非稀疏随机填充矩阵时,compact() 可能会非常慢,或将其乘以零:在这种情况下,通过实例化一个新的树并删除旧的树来重置树会更简单、更快)。

  4. 公共子范围在数据中使用公共存储,因此该共享数据必须是只读的。如果您必须更改单个值而不更改矩阵的其余部分,则必须首先确保它在subrangePositions 索引中仅被引用一次。否则,您需要在values 向量的任何位置(方便地在末尾)分配一个新的子范围,然后将这个新子范围的位置存储到subrangePositions 索引中。



请注意,通用的 Colt 库虽然非常好,但在处理稀疏矩阵时却没有那么好,因为它使用散列(或行压缩)技术,目前不支持尝试,尽管这是一个出色的优化,既节省空间节省时间,特别是对于最频繁的 getAt() 操作。

即使是这里描述的 setAt() 操作也可以节省很多时间(这里是实现的方式,即设置后没有自动压缩,仍然可以根据需求和估计的时间来实现,压缩仍然可以节省很多以时间为代价的存储空间):节省的时间与子范围中的单元格数量成正比,而节省的空间与每个子范围的单元格数量成反比。一个很好的折衷方案是使用子范围大小,例如每个子范围的单元格数是 2D 矩阵中单元格总数的平方根(使用 3D 矩阵时它将是立方根)。

Colt 稀疏矩阵实现中使用的散列技术的不便之处在于它们会增加大量存储开销,并且由于可能的冲突而导致访问时间变慢。尝试可以避免所有冲突,然后可以保证在最坏的情况下将线性 O(n) 时间节省到 O(1) 时间,其中 (n) 是可能的冲突数(在稀疏矩阵的情况下,可能是最多为矩阵中非默认值单元的数量,即最多为矩阵大小的总数乘以与散列填充因子成比例的因子,对于非稀疏即完整矩阵)。

Colt 中使用的 RC(行压缩)技术与 Tries 更接近,但这是另一个代价,这里使用的压缩技术对于最频繁的只读 get() 操作的访问时间非常慢, setAt() 操作的压缩速度非常慢。此外,使用的压缩不是正交的,这与保留正交性的 Tries 演示不同。尝试还可以为相关的查看操作保留这种正交性,例如跨步、转置(被视为基于整数循环模操作的跨步操作)、子范围(以及一般的子选择,包括排序视图)。

我只是希望 Colt 将来会更新以使用 Tries 实现另一个实现(即 TrieSparseMatrix 而不仅仅是 HashSparseMatrix 和 RCSparseMatrix)。这些想法都在这篇文章中。

Trove 实现(基于 int->int 映射)也基于类似于 Colt 的 HashedSparseMatrix 的散列技术,即它们具有相同的不便之处。尝试会快得多,消耗的额外空间适中(但这个空间可以优化,甚至在延迟时间内变得比 Trove 和 Colt 更好,对结果矩阵/trie 使用最终的 compact()ion 操作)。

注意:此 Trie 实现绑定到特定的本机类型(此处为双精度)。这是自愿的,因为使用装箱类型的通用实现具有巨大的空间开销(并且访问时间要慢得多)。在这里,它只使用双精度的原生一维数组,而不是通用向量。但是对于 Tries 来说,当然也可以派生一个泛型实现......不幸的是,Java 仍然不允许编写具有本地类型的所有优点的真正泛型类,除非通过编写多个实现(对于泛型 Object 类型或为每个本机类型),并通过类型工厂提供所有这些操作。该语言应该能够自动实例化本机实现并自动构建工厂(现在即使在 Java 7 中也不是这种情况,这是 .Net 对于与本机一样快的真正泛型类型仍然保持其优势的地方类型)。

【讨论】:

  • 写得很好,很有教育意义。谢谢!
  • 文本很有用,它是一个很好的数据结构,但代码甚至还没有接近编译,而且在很多地方都“聪明”或“微妙”。所以我不太确定这段代码实际上添加了什么,因为从头开始实现比从上面提供的代码开始要快。相反,它会影响原本出色的讨论。
  • 我从一个真实世界的应用程序中提取代码,试图删除与它无关的东西,但是它不直接编译,代码是自愿简化的各种待办事项,但它给出了 Trie's 使用的一般概念。值得注意的是,下面的文本中有一些提示,说明它应该如何针对特定元素类型进行调整/调整。
  • 终于,多年后,我为另一个项目重新审视了这个问题,并尝试了 Trie 结构(编写我自己的代码)。像你说的那样快速燃烧。所以,现在这是正确的答案。
  • @DanM 我认为你是那种会整理出整理好的人! (尝试过 Trie);-)
【解决方案2】:

以下测试 Java 矩阵库的框架,还提供了一个很好的列表! https://lessthanoptimal.github.io/Java-Matrix-Benchmark/

经过测试的库:

* Colt
* Commons Math
* Efficient Java Matrix Library (EJML)
* Jama
* jblas
* JScience (Older benchmarks only)
* Matrix Toolkit Java (MTJ)
* OjAlgo
* Parallel Colt
* Universal Java Matrix Package (UJMP) 

【讨论】:

    【解决方案3】:

    这似乎很简单。

    您可以使用以 row*maxcolums+column 作为索引的数据的二叉树。

    要查找项目,您只需计算 row*maxcolums+column 并二进制搜索查找它的树,如果它不存在,您可以返回 null(它是 О(log n) 其中 n 是包含一个对象)。

    【讨论】:

      【解决方案4】:

      可能不是最快的运行时解决方案,但我能想到的最快的解决方案似乎可行。创建一个 Index 类并将其用作 SortedMap 的键,例如:

          SortedMap<Index, Object> entries = new TreeMap<Index, Object>();
          entries.put(new Index(1, 4), "1-4");
          entries.put(new Index(5555555555l, 767777777777l), "5555555555l-767777777777l");
          System.out.println(entries.size());
          System.out.println(entries.get(new Index(1, 4)));
          System.out.println(entries.get(new Index(5555555555l, 767777777777l)));
      

      我的 Index 类看起来像这样(在 Eclipse 代码生成器的帮助下)。

      public static class Index implements Comparable<Index>
      {
          private long x;
          private long y;
      
          public Index(long x, long y)
          {
              super();
              this.x = x;
              this.y = y;
          }
      
          public int compareTo(Index index)
          {
              long ix = index.x;
              if (ix == x)
              {
                  long iy = index.y;
                  if (iy == y)
                  {
                      return 0;
                  }
                  else if (iy < y)
                  {
                      return -1;
                  }
                  else
                  {
                      return 1;
                  }
              }
              else if (ix < x)
              {
                  return -1;
              }
              else
              {
                  return 1;
              }
          }
      
          public int hashCode()
          {
              final int PRIME = 31;
              int result = 1;
              result = PRIME * result + (int) (x ^ (x >>> 32));
              result = PRIME * result + (int) (y ^ (y >>> 32));
              return result;
          }
      
          public boolean equals(Object obj)
          {
              if (this == obj)
                  return true;
              if (obj == null)
                  return false;
              if (getClass() != obj.getClass())
                  return false;
              final Index other = (Index) obj;
              if (x != other.x)
                  return false;
              if (y != other.y)
                  return false;
              return true;
          }
      
          public long getX()
          {
              return x;
          }
      
          public long getY()
          {
              return y;
          }
      }
      

      【讨论】:

        【解决方案5】:

        您可以查看la4j(Java 的线性代数)库。它支持CRS (Compressed Row Storage) 以及CCS (Compressed Column Storage) 稀疏矩阵的内部表示。因此,这些是稀疏数据最高效、最快速的内部结构。

        这是在la4j 中使用稀疏矩阵的简单示例:

        Matrix a = new CRSMatrix(new double[][]{ // 'a' - CRS sparse matrix
           { 1.0, 0.0, 3.0 },
           { 0.0, 5.0, 0.0 },
           { 7.0, 0.0. 9.0 }
        });
        
        Matrix b = a.transpose(); // 'b' - CRS sparse matrix
        
        Matrix c = b.multiply(a, Matrices.CCS_FACTORY); // 'c' = 'b' * 'a'; 
                                                        // 'c' - CCS sparse matrix
        

        【讨论】:

          【解决方案6】:

          您可以只使用嵌套映射,但如果您需要对其进行矩阵演算,这可能不是最佳选择

           Map<Integer, Map<integer, Object>> matrix;
          

          也许可以用一些元组代替对象来存储实际数据,以便在提取后更轻松地使用它,例如:

          class Tuple<T extends yourDataObject> {
            public final int x;
            public final int y;
            public final T object;
          }
          
          class Matrix {
            private final Map<Integer, Map<interger, Tupple>> data = new...;
          
           void add(int x, int y, Object object) {
               data.get(x).put(new Tupple(x,y,object);
           }
          }
          
          
          //etc
          

          为简洁起见省略了空值检查等

          【讨论】:

          • 首选 long iso int 用于“以十亿为单位”的范围。当“将有数十万个包含一个对象的单元格”时嵌套地图是很多开销。在 Tuple 中存储坐标是多余的,并且增加了操作的维护。索引没有抽象:不灵活/可扩展。
          • 以上是最简单的不可扩展的解决方案,对于非常有限的元素,但这不应该是-1。
          【解决方案7】:

          HashMap 摇滚。只需使用 StringBuilder(不是 + 或 String.format)将索引(作为字符串)与分隔符连接起来,比如“/”,并将其用作键。没有比这更快、更节省内存的了。稀疏矩阵是 20 世纪的。 :-)

          【讨论】:

          • 正如我在文章/回复中所说,HashMap 并没有我们想象的那么快,因为它需要对您要使用的每个索引键进行哈希处理(对于读取和写入访问),这是有代价的(并且对于最小化冲突的编程并不那么明显)。
          • 并且因为必须始终考虑冲突(以减少风险或冲突或长循环查找其他位置的风险),您需要增加哈希表的内部大小,保持最小数量未使用的插槽,但您会浪费空间。同样,一旦您在哈希索引中获得位置,您需要比较有效键(而不仅仅是它们的哈希值)。在全球范围内,它需要数百个 CPU 周期(在最坏的情况下需要数千个,当哈希表几乎已满时存在高度冲突时),而不是仅 2 个表间接循环,所有读取和大多数写入都使用 Trie!
          • 请注意,在某些编程语言中,您别无选择:数组只能用作哈希表(例如 PHP、Lua),但它们经过优化以补偿表具有非稀疏范围的情况(内部他们使用两个独立的存储:一个哈希表(用于非常稀疏的部分)和一个简单的 1D 索引(有时是 2D Trie),用于更紧凑的区域,或者仅用于一些具有小值的整数键类型。
          猜你喜欢
          • 2018-08-12
          • 1970-01-01
          • 1970-01-01
          • 2012-06-20
          • 1970-01-01
          • 2018-01-19
          • 1970-01-01
          • 2011-12-02
          相关资源
          最近更新 更多