2010-05-04 17 views
15

Necesito incluir aproximadamente 1 MByte de datos en una aplicación Java, para un acceso muy rápido y fácil en el resto del código fuente. Mi formación principal no es Java, así que mi idea inicial era convertir los datos directamente en el código fuente de Java, definiendo 1MByte de matrices constantes, clases (en lugar de struct C++), etc., algo como esto:Gran cantidad de constantes en Java

public final/immutable/const MyClass MyList[] = { 
    { 23012, 22, "Hamburger"} , 
    { 28375, 123, "Kieler"} 
}; 

Sin embargo , parece que Java no es compatible con tales construcciones. ¿Es esto correcto? Si es así, ¿cuál es la mejor solución para este problema?

NOTA: Los datos constan de 2 tablas con aproximadamente 50000 registros de datos, que se deben buscar de varias maneras. Esto puede requerir algunos índices más adelante, con más registros significativos, quizás 1 millón de registros, guardados de esta manera. Espero que la aplicación se inicie muy rápido, sin iterar a través de estos registros.

Respuesta

22

Personalmente no lo pondría en fuente.

En su lugar, incluya los datos en algún formato sin formato apropiado en su archivo jar (supongo que empaquetará la aplicación o la biblioteca) y use Class.getResourceAsStream o ClassLoader.getResourceAsStream para cargarlo.

Es posible que desee una clase para encapsular carga, almacenamiento en caché y proporcionar estos datos, pero no veo mucho beneficio de convertirlo en código fuente.

+0

¿Cuál es el formato de datos estándar en Java para esto? –

+2

@Lars: para pares K/V, * clave = valor * en el archivo '.properties' (marque javadoc para la clase' Propiedades'), para las listas solo el cielo es el límite aunque le sugiero que use algo simple que se ajuste tus necesidades. Tal vez XML si lo desea, pero generalmente eso no es realmente necesario. – Esko

+0

@Lars D: Realmente depende de cuáles sean sus necesidades de datos. Los archivos de propiedades están bien para los pares clave/valor, pero pueden no ser muy eficientes si tiene muchos datos numéricos. (Lo mismo se aplica a XML). Hay varias bibliotecas de serialización que pueden facilitarle la vida o simplemente usar su propio formato de datos personalizado. –

3

Una idea es que uses enumeradores, pero no estoy seguro de si esto se adapta a tu implementación, y también depende de cómo planeas usar los datos.

public enum Stuff { 

HAMBURGER (23012, 22), 
KIELER (28375, 123); 

private int a; 
private int b; 

//private instantiation, does not need to be called explicitly. 
private Stuff(int a, int b) { 
    this.a = a; 
    this.b = b; 
    } 

public int getAvalue() { 
    return this.a; 
} 

public int getBvalue() { 
    return this.b; 
} 

}

Estos continuación, se puede acceder como:

Stuff someThing = Stuff.HAMBURGER; 
int hamburgerA = Stuff.HAMBURGER.getA() // = 23012 

Otra idea está utilizando un inicializador static para establecer campos privados de una clase.

+0

@Lars, elimine "en los argumentos de la llamada enum-constructor. – aioobe

+0

@aioobe, muchas gracias! A menos que desee almacenar esos números como cadenas (entonces el constructor privado debe cambiar en consecuencia), no" son necesario. –

+0

@Lars D (otros Lars), así nunca tendrá que llamar explícitamente al constructor, solo necesita definir cada elemento como en el ejemplo anterior. –

0

También podría declarar una clase estática (o un conjunto de clases estáticas) exponiendo los valores deseados como métodos. Después de todo, desea que su código pueda encontrar el valor para un nombre dado, y no quiere que el valor cambie.

Así:. = MyLibOfConstants.returnHamburgerLocation ubicación() código postal

y puede almacenar este material en una tabla hash con lazyinitialization, si lo que el cálculo sobre la marcha sería una pérdida de tiempo.

7

Debido a las limitaciones de los archivos bytecode de java, los archivos de clase no pueden ser mayores de 64k iirc. (Ellos simplemente no están destinados para este tipo de datos.)

Me cargar los datos al iniciar el programa, usando algo como las siguientes líneas de código:

import java.io.*; 
import java.util.*; 

public class Test { 
    public static void main(String... args) throws IOException { 
     List<DataRecord> records = new ArrayList<DataRecord>(); 
     BufferedReader br = new BufferedReader(new FileReader("data.txt")); 
     String s; 
     while ((s = br.readLine()) != null) { 
      String[] arr = s.split(" "); 
      int i = Integer.parseInt(arr[0]); 
      int j = Integer.parseInt(arr[1]); 
      records.add(new DataRecord(i, j, arr[0])); 
     } 
    } 
} 


class DataRecord { 
    public final int i, j; 
    public final String s; 
    public DataRecord(int i, int j, String s) { 
     this.i = i; 
     this.j = j; 
     this.s = s; 
    } 
} 

(NB: El escáner es bastante lento, así que no se sienta tentado a usarlo solo porque tiene una interfaz simple. Quédese con alguna forma de BufferedReader y split, o StringTokenizer.)

La eficiencia puede mejorarse si transforma los datos en un formato binario.En ese caso, se puede hacer uso de la DataInputStream (pero no se olvide de pasar por algún BufferedInputStream o BufferedReader)

Dependiendo de cómo desea acceder a los datos, que puede ser mejor almacenar los registros en un hash -map (HashMap<Integer, DataRecord>) (teniendo i o j como la clave).

Si desea cargar los datos al mismo tiempo que la JVM carga el archivo de clase en sí (más o menos!) Podría hacer la lectura/inicialización, no dentro de un método, sino ecapsulated en static { ... }.


Para un enfoque de mapeo de memoria, echar un vistazo a la java.nio.channels -El paquete de java. Especialmente el método

public abstract MappedByteBuffer map(FileChannel.MapMode mode, long position,long size) throws IOException

ejemplos de código completas se pueden encontrar here.


Dan Bornstein (el desarrollador principal de DalvikVM) explica una solución a su problema en this talk (Mire a su alrededor 0:30:00). Sin embargo, dudo que la solución se aplique a tantos datos como un megabyte.

+0

Cargando datos no es una opción, eso sería demasiado lento. La asignación de memoria tendría más sentido, pero no creo que la asignación de memoria esté disponible en mi plataforma de destino (Android) –

+0

Eche un vistazo a http://www.developer.com/java/other/article.php/ 1548681/Introducción a la memoria-Asignado-IO-en-Java.htm – aioobe

+0

Los archivos de clase * pueden * ser mayores de 64k; son métodos individuales (y bloques de inicialización) que no pueden ser. –

1

convertir los datos directamente en el código fuente de Java, definiendo 1MByte de matrices constantes, clases

Tenga en cuenta que existen restricciones estrictas sobre el tamaño de las clases y sus estructuras [ref JVM Spec.

0

¿No es un caché lo que necesita? Como las clases se cargan en la memoria, no están limitadas a un tamaño definido, deberían ser tan rápidas como usar constantes ... En realidad, incluso puede buscar datos con algún tipo de índice (por ejemplo, con el código hash del objeto ...) Puede, por ejemplo, crear todas sus matrices de datos (ex {23012, 22, "Hamburger"}) y luego crear 3 hashmap: map1.put (23012, hamburgerItem); map2.put (22, hamburgerItem); map3.put ("Hamburger", hamburgerItem); De esta manera puede buscar muy rápido en uno de los mapas de acuerdo con el parámetro que tiene ... (pero esto funciona solo si sus claves son únicas en el mapa ... esto es solo un ejemplo que podría inspirarlo)

En el trabajo tenemos una aplicación web muy grande (80 instancias weblogic) y es casi lo que hacemos: almacenar en caché en todas partes. Desde un CountryList la base de datos, crear una caché ...

Hay muchos tipos diferentes de cachés, usted debe comprobar el enlace y elegir lo que necesita ... http://en.wikipedia.org/wiki/Cache_algorithms

+0

El criterio principal es que los datos están presentes en el inicio del programa, de modo que no necesito iterar a través de él o analizarlo. No estoy seguro de cómo un caché puede ayudar a hacer eso? –

+0

@Lars: lo que quiere no tiene sentido. Cargar una clase Java implica iterar a través del código de bytes y analizarlo también. Es imposible cargar ningún tipo de datos sin recorrerlos y analizarlos de alguna forma. Solo se trata de qué tan costosos son esos pasos. –

+0

@Michael: Buen punto. –

1

Así es como se define en Java, si he entendido lo que está buscando:

public final Object[][] myList = { 
      { 23012, 22, "Hamburger"} , 
      { 28375, 123, "Kieler"} 
     }; 
+0

Un huevo de Columbus ... Me pregunto cómo se ve en bytecode. –

+0

Simplemente haga 'javac TheAboveCode.java && javap -v TheAboveCode' tenga en cuenta que el Android utiliza un formato de archivo completamente diferente (.dex) – aioobe

+0

La asignación se ejecuta como parte del constructor, que tiene un límite de tamaño de 64 kbytes , que es violado Otros métodos también están limitados a 64 kbytes, por lo que se necesitarían al menos 16 métodos para implementar esto. –

3

Poner los datos en la fuente podría en realidad no ser la solución más rápida, no por un tiro largo. Cargar una clase Java es bastante complejo y lento (al menos en una plataforma que hace una verificación de bytecode, no estoy seguro acerca de Android).

La forma más rápida de hacerlo sería definir su propio formato de índice binario.A continuación, podría leerlo como byte[] (posiblemente usando asignación de memoria) o incluso RandomAccessFile sin interpretarlo de ninguna manera hasta que comience a acceder a él. El costo de esto sería la complejidad del código que accede a él. Con los registros de tamaño fijo, una lista ordenada de registros a la que se accede a través de la búsqueda binaria todavía sería bastante simple, pero cualquier otra cosa se pondrá fea.

Aunque antes de hacer eso, ¿estás seguro de que esto no es una optimización prematura? La solución más fácil (y probablemente aún bastante rápida) sería la de serializar un mapa, una lista o una matriz, ¿ha probado esto y ha determinado que, de hecho, es demasiado lento?

0

La serialización en Java suena como algo que necesita ser analizado ... no es bueno. ¿No hay algún tipo de formato estándar para almacenar datos en una secuencia, que se puede leer/buscar usando una API estándar sin analizarlo?

Si tuviera que crear los datos en el código, todo se cargaría en el primer uso. Es poco probable que sea mucho más eficiente que cargar desde un archivo separado; además de analizar los datos en el archivo de clase, la JVM debe verificar y compilar los códigos de bytes para crear cada objeto un millón de veces, en lugar de solo una vez si cargarlo desde un bucle.

Si desea acceso aleatorio y no puede usar un archivo mapeado en memoria, entonces existe un RandomAccessFile que podría funcionar. Necesita cargar un índice al inicio, o necesita hacer las entradas de una longitud fija.

Es posible que desee comprobar si las bibliotecas HDF5 se ejecutan en su plataforma; sin embargo, puede ser exagerado para un conjunto de datos tan simple y pequeño.

1

Parece que va a escribir su propia base de datos liviana.
Si puede limitar la longitud de la cadena a un tamaño máximo realista el siguiente podría funcionar:

  • escribir cada entrada en un archivo binario, las entradas tienen el mismo tamaño, por lo que los residuos algunos bytes con cada entrada (int a, int b, int stringsize, string, padding)
  • Para leer una entrada, abra el archivo como un archivo de acceso aleatorio, multiplique el índice con la longitud de una entrada para obtener el desplazamiento y buscar la posición.
  • Coloque los bytes en un bytebuffer y lea los valores, la cadena se debe convertir con la cadena (byte [], int start, int length, Charset) ctor.

Si no puede limitar la longitud de un bloque vuelque las cadenas en un archivo adicional y solo almacene las compensaciones en su tabla. Esto requiere un acceso de archivo adicional y hace que la modificación de los datos sea difícil.
Puede encontrar alguna información sobre el acceso aleatorio a archivos en java aquí http://java.sun.com/docs/books/tutorial/essential/io/rafs.html.

Para un acceso más rápido, puede almacenar en caché algunas de sus entradas leídas en un Hashmap y siempre eliminar las más antiguas del mapa al leer una nueva.
Pseudo código (costumbre de compilación):

class MyDataStore 
{ 
    FileChannel fc = null; 
    Map<Integer,Entry> mychace = new HashMap<Integer, Entry>(); 
    int chaceSize = 50000; 
    ArrayList<Integer> queue = new ArrayList(); 
    static final int entryLength = 100;//byte 
    void open(File f)throws Exception{fc = f.newByteChannel()} 
    void close()throws Exception{fc.close();fc = null;} 
    Entry getEntryAt(int index) 
    { 
     if(mychace.contains(index))return mychace.get(index); 

     long pos = index * entryLength; fc.seek(pos);ByteBuffer 
     b = new ByteBuffer(100); 
     fc.read(b); 
     Entry a = new Entry(b); 
     queue.add(index); 
     mychace.put(index,a); 
     if(queue.size()>chacesize)mychace.remove(queue.remove(0)); 
     return a; 
    } 

} 
class Entry{ 
    int a; int b; String s; 
    public Entry(Bytebuffer bb) 
    { 
    a = bb.getInt(); 
    b = bb.getInt(); 
    int size = bb.getInt(); 
    byte[] bin = new byte[size]; 
    bb.get(bin); 
    s = new String(bin); 
    } 
} 

falta en el pseudocódigo:

  • la escritura, ya que lo necesite para los datos constantes
  • número total de archivo de entradas/sizeof, sólo se necesita una número entero al principio del archivo y un desplazamiento adicional de 4 bytes para cada operación de acceso.
0

Recomendaría utilizar recursos para almacenar tales datos.

Cuestiones relacionadas