2009-05-18 6 views
17

Tengo un archivo grande, se espera que sea de alrededor de 12 GB. Quiero cargar todo en la memoria en una máquina de 64 bits fornido con 16 GB de RAM, pero creo que Java no soporta matrices de bytes tan grande:matriz de Java con elementos de más de 4gb

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

¿Es posible con Java?

El error de compilación del compilador Eclipse es:

Type mismatch: cannot convert from long to int 

javac da:

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

Simplemente curioso: ¿Por qué necesita mantener esa cantidad de datos en la memoria al mismo tiempo? ¿No sería posible dividir eso en trozos? –

+1

+1 al comentario de bruno. La única forma de tener un archivo completo en la memoria será una ventaja si necesita realizar accesos aleatorios en diferentes puntos del archivo, y en este caso casi con certeza sería mejor analizarlo en una representación más computable – kdgregory

+0

Yo soy Intentando usar un árbol de prefijos (trie) para conservar los datos, esto puede reducirlo lo suficiente como para caber en 2 gb de memoria. –

Respuesta

18

índices de matriz de Java son de tipo int (4 bytes o 32 bits), así que me temo está limitado a 2 - 1 o 2147483647 ranuras en su matriz. Leí los datos en otra estructura de datos, como una matriz 2D.

+0

gracias a todos. Bill obtuvo la respuesta primero. –

+0

@OmryYadan, el [límite real será en realidad menor] (http://stackoverflow.com/questions/3038392/do-java-arrays-have-amaximum-size/8381338#comment45805541_3039805) que 2147483647. – Pacerier

+0

se refiere a MAX_INT - 8? https: // github.com/omry/banana/blob/1621638d6eb4db773045af66eac66be0fffa91fa/banana/src/net/yadan/banana/memory/block/BigBlockAllocator.java # L25 –

6

Si es necesario, puede cargar los datos en una matriz de matrices, que le dará un máximo de int.maxValue cuadrados bytes, más de lo que la máquina beefiest sostendría bien en la memoria.

+0

ese sería mi próximo paso. ya que tengo la intención de hacer una búsqueda binaria en los datos, se reescribirá el código, pero me temo que no hay otra opción. –

+0

Puede crear una clase que administre una matriz de matrices, pero que proporcione una abstracción similar a una matriz regular, por ejemplo, con get y set que toman un índice largo. –

2

Sugiero que defina algunos objetos de "bloque", cada uno de los cuales contiene (digamos) 1Gb en una matriz, y luego haga una matriz de esos.

1

Las matrices de Java usan números enteros para sus índices. Como resultado, el tamaño máximo de la matriz es Integer.MAX_VALUE.

(Por desgracia, no puedo encontrar ninguna prueba de Sun a sí mismos acerca de esto, pero hay plenty de discussions en su forums sobre él ya.)

Creo que la mejor solución que podría hacer mientras tanto haría ser para hacer una matriz 2D, es decir:

byte[][] data; 
2

No, arrays son indexados por int s (excepto algunas versiones de JavaCard que utilizan short s). Tendrá que dividirlo en matrices más pequeñas, probablemente envolviendo en un tipo que le proporcione get(long), set(long,byte), etc. Con secciones de datos tan grandes, es posible que desee mapear el archivo use java.nio.

1

Como otros han dicho, todas las matrices de Java de todos los tipos son indexados por int, y así pueden ser de tamaño máximo 2 -1, o 2147483647 elementos (~ 2 mil millones). Esto se especifica en el Java Language Specification por lo que cambiar a otro sistema operativo o Java Virtual Machine no será de ayuda.

Si quería escribir una clase para superar este como se sugirió anteriormente se puede, que podría utilizar una matriz de matrices (por mucha flexibilidad) o cambiar los tipos (un long es de 8 bytes por lo que un long[] puede ser 8 veces más grande que byte[]).

2

debería considerar el uso FileChannel y MappedByteBuffer a la memoria un mapa del archivo,

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

Editar:

Ok, soy un idiota que parece ByteBuffer sólo tiene un índice de 32 bits, así que es extraño ya que el parámetro de tamaño para FileChannel.map es un largo ...Pero si decides dividir el archivo en varios fragmentos de 2 Gb para cargar, aún así recomiendo el IO asignado a la memoria, ya que puede haber beneficios de rendimiento bastante grandes. Básicamente estás moviendo toda la responsabilidad IO al kernel del sistema operativo.

+0

También toco la misma limitación de 'ByteBuffer', que creo que debería poder tratar con desplazamientos largos e índices al menos a nivel de interfaz. La implementación concreta debe verificar los rangos de manera explícita. Desafortunadamente no es posible mapear más de 2GB de archivos en la memoria. –

+0

Vota que este es el camino correcto a seguir, incluso si tienes que dividir los datos en fragmentos de 2G: envuelve los fragmentos en una clase que indexe con un largo si lo deseas. –

+0

MappedByteBuffer también tiene un límite de 2GB, prácticamente inútil. Consulte http://nyeggen.com/post/2014-05-18-memory-mapping-%3E2gb-of-data-in-java/ para obtener una solución que llame a los métodos internos de JNI para solucionar esto. – AqD

12
package com.deans.rtl.util; 

import java.io.FileInputStream; 
import java.io.IOException; 

/** 
* 
* @author [email protected] 
* 
* 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; 
    } 
} 
} 
+0

Una buena idea para implementar su propio ByteArray para resolver este caso. Si no fuera por tu respuesta, probablemente no habría pensado en hacerlo. – UnixShadow

+0

¿A alguien le importa agregar un método de actualización (byte [] b, int start, int size)? :) – rogerdpack

0

Java no soporta matriz directa con más de 2^32 elementos de la actualidad,

esperanza de ver esta característica de java en el futuro

+0

No, el límite es 2^31 - 1 elementos. Y su segunda línea no cita ninguna referencia. – Nayuki

1

Creo que la idea de mapeo de memoria en el archivo (utilizando el hardware de memoria virtual de la CPU) es el enfoque correcto. Excepto que MappedByteBuffer tiene la misma limitación de 2Gb que las matrices nativas. Este hombre afirma haber resuelto el problema con una alternativa bastante simple de MappedByteBuffer:

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

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

Desafortunadamente la JVM se bloquea cuando se lee más allá de 500 Mb.

+0

Mientras que en este ejemplo específico mi caso de uso era leer un archivo, este no es el único caso de uso para arreglos grandes. –

1

no limitan su auto con Integer.MAX_VALUE

aunque esta pregunta se ha hecho hace muchos años, pero ai quería participar con un ejemplo sencillo utilizando sólo Java SE y sin ninguna biblioteca externa

en un primer momento digamos que es teóricamente imposible, pero prácticamente posible

una nueva mirada: si la matriz es un objeto de elementos de lo que trata de tener un objeto que es matriz de matrices

aquí está el ejemplo

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]; 
} 

}

y aquí está la prueba

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 "); 

} 

este código sólo se limita sólo Long.MAX_VALUE y Java montón pero se puede superar que a medida que quiero (lo hice 3800 MB)

Espero que esto sea útil y brinde una respuesta simple

+1

desde entonces escribí Banana: https://github.com/omry/banana, una lib que te permite hacer eso, entre otras cosas. –

+0

buen trabajo @OmryYadan tengo una mirada en algunos ejemplos buena bro (: -. – Anas

+0

El wiki es bastante agradable https://github.com/omry/banana/wiki/Block-Allocators –

Cuestiones relacionadas