Ordenando millones de pares int/string usando Java

Tengo 50,000,000 (entero, cadena) pares en un archivo de texto. Los números enteros son tiempos en milisegundos, por lo que tienen 13 dígitos (por ejemplo, 1337698339089).

Las entradas en el archivo de texto son así:

1337698339089|blaasdasd
1337698339089|asdasdas
1337698338089|kasda

Puede haber entradas idénticas.

Quiero ordenar las entradas en los números enteros (en orden ascendente) preservando cualquier número entero duplicado y preservando los pares (entero, cadena). El enfoque que he tomado conduce a errores de memoria, por lo que estoy buscando enfoques alternativos.

Mi enfoque es algo como esto (usando algún pseudocódigo):

// declare TreeMap to do the sorting
TreeMap<Double, String> sorted = new TreeMap<Double, String>();

// loop through entries in text file, and put each in the treemap:
for each entry (integer, string) in the text file:

   Random rand = new Random();
   double inc = 0.0;

   while (sorted.get(integer + inc) != null) {
       inc = rand.nextDouble();
   }

   sorted.put(integer + inc, string);

Estoy usando números aleatorios aquí para asegurar que se puedan ingresar enteros duplicados en el diagrama de árbol (incrementándolos en un doble entre 0 y 1).

// to print the sorted entries:
for (Double d : sorted.KeySet()) {
    System.out.println(Math.round(d) + "|" + sorted.get(d));
}

Este enfoque funciona, pero se descompone para 50,000,000 XNUMX XNUMX de entradas (creo que porque el diagrama de árbol se está volviendo demasiado grande; o posiblemente porque el ciclo while se está ejecutando durante demasiado tiempo).

Me gustaría saber qué enfoque tomarían los programadores más experimentados.

Muchas gracias!

preguntado el 22 de mayo de 12 a las 16:05

¿Tarea? ¿Por qué no usar métodos Java integrados? O semicopiarlos. Están bastante optimizados. -

Simplemente llenaría un vector/lista de matrices y luego lo ordenaría:

No uses Java para esto. sort -n es tu amigo. -

¿Es obligatorio usar java? Usar una base de datos y sql para eso podría ser una mejor idea. -

¿Intentaste usar el tiempo: pares de números de línea? No necesitará las cadenas hasta que escriba el resultado. Solo necesita leer el archivo dos veces. -

8 Respuestas

Debería poder hacer esto con una lista, si tiene suficiente memoria. Crearía una clase separada para la entrada:

class Foo : Comparable<Foo> {
    private final long time;
    private final String text;

    // Constructor etc
}

En términos de memoria, debe poder almacenar 50 millones de instancias y referencias a ellas. En una JVM de 32 bits, eso sería:

  • 8 bytes de sobrecarga por objeto (IIRC)
  • 8 bytes para el time
  • 4 bytes para el text campo
  • ~54 bytes para la cadena (sobrecarga de 8 bytes + tres int campos IIRC + char[] referencia de matriz + ~32 bytes para una matriz de 10 caracteres)
  • 4 bytes para la referencia en la matriz o ArrayList

Eso es alrededor de 80 bytes por instancia, digamos 100 para redondear. Almacenar 50,000,000 5,000,000,000 5 de ellos requeriría 32 XNUMX XNUMX XNUMX bytes, también conocidos como XNUMX GB, que es más de lo que creo que puede soportar una JVM de XNUMX bits.

Entonces, para hacer todo esto en la memoria, necesitará una máquina de 64 bits y una JVM de 64 bits, y luego la sobrecarga potencialmente aumenta un poco debido a referencias más grandes, etc. Factible, pero no terriblemente agradable.

Sin embargo, una gran parte de esto se debe a las cuerdas. Si realmente querías ser eficiente, podría cree una matriz de caracteres gigante, luego almacene compensaciones en ella dentro Foo. Lea en la matriz a medida que lee los datos de texto y luego utilícelo para escribir los datos después de ordenarlos. Más complejo y feo, pero considerablemente más eficiente en memoria.

Alternativamente, podrías hacer esto no todo en la memoria: estoy seguro de que si busca encontrará mucha información sobre la clasificación a través del sistema de archivos.

contestado el 22 de mayo de 12 a las 16:05

Jon, disculpe, ¿cómo se comporta c# en un escenario similar? ¿Cuánta memoria usará? - BigMike

@BigMike: la implementación de la cadena .NET solo usaría un objeto en lugar de dos, lo que reduciría un poco la sobrecarga. Además, al usar un tipo de valor personalizado, podría evitar una buena cantidad de gastos generales por objeto para Foo - a costa de encarecer la copia de valores de tipo Foo. El principio general sería el mismo, pero es posible que con la sobrecarga reducida pueda colocar los 50 millones de entradas en la memoria. - jon skeet

gracias por la aclaración, pero sospecho que .NET es un poco más amigable en el sistema operativo de 64 bits que JVM. - BigMike

@JonSkeet - gracias. Voy a probar este enfoque... No entiendo completamente la sugerencia de la matriz de caracteres. Lo que puedo hacer es asignar un número de índice a cada cadena y almacenarlos en un archivo de texto, para poder reemplazar las cadenas con números enteros. Pasará un poco de tiempo hasta que pueda implementar sus sugerencias; me pondré en contacto cuando lo haya intentado. - Andrés

@JonSkeet En ese punto... si es necesario, ¿por qué no hacer un desglose en rangos de tiempo... digamos por mes, trimestre o año... analizar el archivo grande en archivos separados que se pueden ordenar y recombinar individualmente: "Foreach línea en el archivo => Extraer fecha => imprimir en (aaaamm.txt, aaaaQ#, etc.) ... ordenar cada archivo y agregarlo a sorted.txt". - Werner CD

Podría considerar usar una base de datos (como H2; lo cual es conveniente ya que puede incorporarla directamente a su proyecto Java) y configurar el índice de la forma que desee. Las bases de datos ya han resuelto el problema de manejar una gran cantidad de datos y organizarlos. Luego puede hacer una consulta SQL para obtener los resultados en orden y volver a escribirlos.

El conjunto de resultados transmitirá los datos en fragmentos; No intente cargar todo en una sola lista.

Mientras que H2 es compatible con la memoria; Lo configuraría para usar un disco en este caso a menos que tenga mucha RAM y Java de 64 bits.

contestado el 22 de mayo de 12 a las 16:05

+1 para una gran solución práctica. H2 es una buena opción para este tipo de tareas. - Zsolt Török

Creo que encontrará que H2 no funciona bien con 50 millones de registros. He encontrado sqlite para escalar mejor que H2 (bitbucket.org/xerial/sqlite-jdbc) - Jay Askren

¿Por qué usar un double para almacenar un long?

A Map<Long, String> no puede tener llaves duplicadas. Uno sobrescribirá al otro.

Dudo que puedas encajar todo esto en la memoria. Eso es 0.5 GB solo para almacenar los largos, más para las cuerdas. Probablemente no puedas hacerlo con JVM de 32 bits.

contestado el 22 de mayo de 12 a las 16:05

¿Le diste a la JVM más memoria? Intente ejecutarlo con una opción de línea de comando -Xmx1024M. Y el treeMap parece innecesariamente complicado, puede usar los comandos integrados de Java

contestado el 22 de mayo de 12 a las 16:05

Su problema parece ser de 2 partes:

  1. El algoritmo: Recomendaría usar algunos de los algoritmos de clasificación incorporados en Java. Fácil de encontrar referencias en google, como este.
  2. La JVM: La raíz de su problema parece que no tiene suficiente memoria asignada a su máquina virtual Java. Recomendaría aumentar el tamaño máximo, ya que se trata de una cantidad de información decente.

Los argumentos de JVM que está buscando deben ser:

  • -Xms especifica el tamaño inicial del almacenamiento dinámico de Java y

  • -Xmx el tamaño máximo de almacenamiento dinámico de Java.

Referencia: http://www.rgagnon.com/javadetails/java-0131.html

respondido 03 nov., 13:08

¿Cuál fue el error arrojado? ¿Puedes cargar con éxito todos los datos en la memoria? Le sugiero que pruebe la clase Java Comparator. Tal vez intente algo como crear un objeto personalizado para representar el par:

class Entry{
    long i;
    String s;
}

Luego crea un comparador personalizado

class IComp implements Comparator<Entry>{
    public int compare(Entry e1, Entry e2){
      if(e1.i < e2.i) return -1;
      //complete the rest

    }
}

Luego coloque todos los objetos en una entrada de matriz Entry[] y cree un comparador IComp icomp Use Arrays.sort(entry, icomp)

Como creará 50 millones de objetos, debe asegurarse de que haya suficiente espacio de almacenamiento dinámico.

Si tiene una gran cantidad de cadenas duplicadas y si estas cadenas son inmutables; puede crear un conjunto para almacenar las cadenas y reciclarlas para crear objetos más livianos en su entrada

Entrada.s = set.get()...

contestado el 22 de mayo de 12 a las 16:05

Me encantaría resolver esto clasificando fragmentos de datos y escribiéndolos en diferentes archivos y aplicando una ordenación de combinación en esos archivos. Aquí está demostración de trabajo, que podría ser útil para su escenario.

contestado el 23 de mayo de 12 a las 05:05

No estoy seguro de si va a utilizar todos los valores cuando termine de ordenar. Pero el número 50 millones me da una pista de que es posible que solo tome los valores X superiores después de la ordenación y haga algo con ellos.

En ese caso: solo use un montón mínimo, cada vez que encuentre un número que sea más grande que la parte superior del montón, elimine el mínimo del montón y agregue el nuevo número. De esta forma no tienes que guardar todos los números en la memoria, solo X de ellos.

contestado el 27 de mayo de 12 a las 12:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.