【问题标题】:Java array with more than 4gb elements具有超过 4gb 元素的 Java 数组
【发布时间】:2010-10-27 01:43:04
【问题描述】:

我有一个大文件,预计大约 12 GB。我想将它全部加载到具有 16 GB RAM 的强大 64 位机器上的内存中,但我认为 Java 不支持那么大的字节数组:

File f = new File(file);
long size = f.length();
byte data[] = new byte[size]; // <- does not compile, not even on 64bit JVM

Java 可以吗?

Eclipse 编译器的编译错误是:

Type mismatch: cannot convert from long to int

javac 给出:

possible loss of precision
found   : long
required: int
         byte data[] = new byte[size];

【问题讨论】:

  • 只是好奇:为什么需要同时在内存中保存这么多数据?难道不能把它分成块吗?
  • +1 布鲁诺的评论。将整个文件保存在内存中的唯一方法是,如果您需要对文件的不同点进行随机访问,那么在这种情况下,您几乎肯定会更好地将其解析为更可计算的表示
  • 我将尝试使用前缀树 (trie) 来保存数据,这可能会将其缩小到足以容纳 2gb 的内存。
  • 哇。非常沮丧。 Java 必须在未来 5 年内解决这个问题。

标签: java arrays 64-bit


【解决方案1】:

Java 数组索引的类型为 int(4 字节或 32 位),所以恐怕您的数组中仅限于 231 - 1 或 2147483647 个插槽。我会将数据读入另一个数据结构,比如二维数组。

【讨论】:

【解决方案2】:

如有必要,您可以将数据加载到数组数组中,这将为您提供最大 int.maxValue squared 字节,甚至比最强大的机器在内存中的容量还要多。

【讨论】:

  • 那将是我的下一步。由于我打算对数据进行二分查找,所以会丑化代码,但恐怕别无选择。
  • 您可以创建一个类来管理数组数组,但提供类似于常规数组的抽象,例如,使用长索引的 get 和 set。
【解决方案3】:

我建议你定义一些“块”对象,每个对象在一个数组中保存(比如说)1Gb,然后将这些对象组成一个数组。

【讨论】:

    【解决方案4】:

    Java 数组使用整数作为索引。因此,最大数组大小为 Integer.MAX_VALUE。

    (不幸的是,我无法从 Sun 自己那里找到任何关于此的证据,但在他们的 forums 上已经有 plentydiscussions 与此有关。)

    我认为您在此期间可以做的最佳解决方案是制作一个二维数组,即:

    byte[][] data;
    

    【讨论】:

      【解决方案5】:

      不,数组由ints 索引(除了一些使用shorts 的JavaCard 版本)。您需要将其分割成更小的数组,可能包装在一个类型中,为您提供get(long)set(long,byte) 等。对于这么大的数据部分,您可能希望使用 java.nio 映射文件。

      【讨论】:

        【解决方案6】:

        正如其他人所说,所有类型的所有 Java 数组都由 int 索引,因此最大大小可以是 231 - 1 或 2147483647 个元素(约 20 亿个)。这是由 Java Language Specification 指定的,因此切换到另一个操作系统或 Java 虚拟机将无济于事。

        如果您想按照上面的建议编写一个类来克服这个问题,您可以使用数组数组(以获得很大的灵活性)或更改类型(long 是 8 个字节,所以 long[] 可以比 byte[] 大 8 倍)。

        【讨论】:

          【解决方案7】:

          您可以考虑使用 FileChannel 和 MappedByteBuffer 来对文件进行内存映射,

          FileChannel fCh = new RandomAccessFile(file,"rw").getChannel();
          long size = fCh.size();
          ByteBuffer map = fCh.map(FileChannel.MapMode.READ_WRITE, 0, fileSize);
          

          编辑:

          好吧,我是个白痴,看起来 ByteBuffer 也只需要一个 32 位索引,这很奇怪,因为 FileChannel.map 的 size 参数很长...但是如果您决定将文件分解为用于加载的多个 2Gb 块我仍然推荐内存映射 IO,因为它可以带来相当大的性能优势。您基本上是将所有 IO 责任转移到操作系统内核。

          【讨论】:

          • 我也遇到了ByteBuffer 的相同限制,我认为至少在接口级别应该能够处理长偏移和索引。具体实施应明确检查范围。不幸的是,无法将超过 2GB 的文件映射到内存中。
          • 赞成,因为这是正确的方法,即使您必须将数据划分为 2G 块 - 如果您愿意,可以将这些块包装在一个索引为 long 的类中。
          • MappedByteBuffer 也有 2GB 的上限,几乎没用。有关调用内部 JNI 方法来解决此问题的解决方案,请参阅 nyeggen.com/post/…
          【解决方案8】:
          package com.deans.rtl.util;
          
          import java.io.FileInputStream;
          import java.io.IOException;
          
          /**
           * 
           * @author william.deans@gmail.com
           *
           * Written to work with byte arrays requiring address space larger than 32 bits. 
           * 
           */
          
          public class ByteArray64 {
          
              private final long CHUNK_SIZE = 1024*1024*1024; //1GiB
          
              long size;
              byte [][] data;
          
              public ByteArray64( long size ) {
                  this.size = size;
                  if( size == 0 ) {
                      data = null;
                  } else {
                      int chunks = (int)(size/CHUNK_SIZE);
                      int remainder = (int)(size - ((long)chunks)*CHUNK_SIZE);
                      data = new byte[chunks+(remainder==0?0:1)][];
                      for( int idx=chunks; --idx>=0; ) {
                          data[idx] = new byte[(int)CHUNK_SIZE];
                      }
                      if( remainder != 0 ) {
                          data[chunks] = new byte[remainder];
                      }
                  }
              }
              public byte get( long index ) {
                  if( index<0 || index>=size ) {
                      throw new IndexOutOfBoundsException("Error attempting to access data element "+index+".  Array is "+size+" elements long.");
                  }
                  int chunk = (int)(index/CHUNK_SIZE);
                  int offset = (int)(index - (((long)chunk)*CHUNK_SIZE));
                  return data[chunk][offset];
              }
              public void set( long index, byte b ) {
                  if( index<0 || index>=size ) {
                      throw new IndexOutOfBoundsException("Error attempting to access data element "+index+".  Array is "+size+" elements long.");
                  }
                  int chunk = (int)(index/CHUNK_SIZE);
                  int offset = (int)(index - (((long)chunk)*CHUNK_SIZE));
                  data[chunk][offset] = b;
              }
              /**
               * Simulates a single read which fills the entire array via several smaller reads.
               * 
               * @param fileInputStream
               * @throws IOException
               */
              public void read( FileInputStream fileInputStream ) throws IOException {
                  if( size == 0 ) {
                      return;
                  }
                  for( int idx=0; idx<data.length; idx++ ) {
                      if( fileInputStream.read( data[idx] ) != data[idx].length ) {
                          throw new IOException("short read");
                      }
                  }
              }
              public long size() {
                  return size;
              }
          }
          }
          

          【讨论】:

          • 实现自己的 ByteArray 来解决这种情况的好主意。如果不是你的回答,我可能不会想到这样做。
          • 有人关心添加一个 update(byte[] b, int start, int size) 方法吗? :)
          【解决方案9】:

          java 目前不支持超过 2^32 个元素的直接数组,

          希望以后能看到java的这个特性

          【讨论】:

          • 不,限制是 2^31 - 1 个元素。而且您的第二行没有引用任何参考资料。
          【解决方案10】:

          我认为内存映射文件的想法(使用 CPU 的虚拟内存硬件)是正确的方法。除了 MappedByteBuffer 与本机数组具有相同的 2Gb 限制。这家伙声称用一个非常简单的 MappedByteBuffer 替代方案解决了这个问题:

          http://nyeggen.com/post/2014-05-18-memory-mapping-%3E2gb-of-data-in-java/

          https://gist.github.com/bnyeggen/c679a5ea6a68503ed19f#file-mmapper-java

          不幸的是,当您读取超过 500Mb 时,JVM 会崩溃。

          【讨论】:

          • 虽然在这个特定示例中我的用例是读取文件,但这并不是大型数组的唯一用例。
          【解决方案11】:

          不要用 Integer.MAX_VALUE 限制自己

          虽然这个问题在很多年前就被问过了,但是我想通过一个简单的例子来参与,只使用 java se 而没有任何外部库

          首先让我们说理论上不可能,但实际上可能

          新外观:如果数组是元素的对象,那么拥有一个数组数组的对象呢

          这是一个例子

          import java.lang.reflect.Array;
          import java.util.ArrayList;
          import java.util.List;
          
          /**
          *
          * @author Anosa
          */
           public class BigArray<t>{
          
          private final static int ARRAY_LENGTH = 1000000;
          
          public final long length;
          private List<t[]> arrays;
          
          public BigArray(long length, Class<t> glasss)
          {
              this.length = length;
              arrays = new ArrayList<>();
              setupInnerArrays(glasss);
          
          }
          
          private void setupInnerArrays(Class<t> glasss)
          {
              long numberOfArrays = length / ARRAY_LENGTH;
              long remender = length % ARRAY_LENGTH;
              /*
                  we can use java 8 lambdas and streams:
                  LongStream.range(0, numberOfArrays).
                                  forEach(i ->
                                  {
                                      arrays.add((t[]) Array.newInstance(glasss, ARRAY_LENGTH));
                                  });
               */
          
              for (int i = 0; i < numberOfArrays; i++)
              {
                  arrays.add((t[]) Array.newInstance(glasss, ARRAY_LENGTH));
              }
              if (remender > 0)
              {
                  //the remainer will 100% be less than the [ARRAY_LENGTH which is int ] so
                  //no worries of casting (:
                  arrays.add((t[]) Array.newInstance(glasss, (int) remender));
              }
          }
          
          public void put(t value, long index)
          {
              if (index >= length || index < 0)
              {
                  throw new IndexOutOfBoundsException("out of the reange of the array, your index must be in this range [0, " + length + "]");
              }
              int indexOfArray = (int) (index / ARRAY_LENGTH);
              int indexInArray = (int) (index - (indexOfArray * ARRAY_LENGTH));
              arrays.get(indexOfArray)[indexInArray] = value;
          
          }
          
          public t get(long index)
          {
              if (index >= length || index < 0)
              {
                  throw new IndexOutOfBoundsException("out of the reange of the array, your index must be in this range [0, " + length + "]");
              }
              int indexOfArray = (int) (index / ARRAY_LENGTH);
              int indexInArray = (int) (index - (indexOfArray * ARRAY_LENGTH));
              return arrays.get(indexOfArray)[indexInArray];
          }
          

          }

          这是测试

          public static void main(String[] args)
          {
              long length = 60085147514l;
              BigArray<String> array = new BigArray<>(length, String.class);
              array.put("peace be upon you", 1);
              array.put("yes it worj", 1755);
              String text = array.get(1755);
              System.out.println(text + "  i am a string comming from an array ");
          
          }
          

          此代码仅受 Long.MAX_VALUE 和 Java heap 的限制,但您可以随意超过它(我将其设置为 3800 MB)

          我希望这很有用并提供一个简单的答案

          【讨论】:

          • 从那时起我就写了 Banana : github.com/omry/banana ,这是一个可以让你做到这一点的库。
          • @OmryYadan 干得好,我看看一些好兄弟的例子(:-
          猜你喜欢
          • 2022-10-04
          • 2016-06-09
          • 2011-06-29
          • 2011-09-27
          • 2018-05-29
          • 1970-01-01
          • 2013-02-10
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多