2012-04-04 20 views
5

Tengo un gran archivo de texto (5Mb) que uso en mi aplicación Android. Creo el archivo como una lista de cadenas ordenadas previamente, y el archivo no cambia una vez que se ha creado. ¿Cómo puedo realizar una búsqueda binaria en el contenido de este archivo sin leer línea por línea para encontrar la cadena correspondiente?Cómo realizar una búsqueda binaria de un archivo de texto

+0

Lea línea por línea y use el método 'contains()' de la clase 'String' en cada línea. –

+0

use el método Arrays.binarySearch() –

+0

No puedo leer todo el archivo. Obtengo una excepción de falla y memoria. Línea por línea es demasiado lento – Beno

Respuesta

5

Dado que el contenido del archivo no cambia, puede dividir el archivo en varias partes. Di A-G, H-N, 0-T y U-Z. Esto le permite verificar el primer carácter e inmediatamente cortar el conjunto posible a un cuarto del tamaño original. Ahora una búsqueda lineal no tomará tanto tiempo o la lectura del archivo completo podría ser una opción. Este proceso podría extenderse si n/4 aún es demasiado grande, pero la idea es la misma. Cree los desgloses de búsqueda en la estructura del archivo en lugar de tratar de hacerlo todo en la memoria.

+0

Yo lo secundo. Además, dado que (según su descripción) usted sabría el contenido del archivo en el momento de su creación, puede dividir el archivo según la longitud de la cadena que contiene. Así que A-G (1-5 caracteres), A-G (5- * caracteres) y así sucesivamente. Entonces, en el momento de la búsqueda, sabría qué archivo abrir. En esencia, omitirá N/4 elementos en el momento de leer el archivo. –

+0

He intentado esta solución, hay una gran diferencia entre n/4 para registrar (n) esta solución muy fea (lo siento) Gracias de todos modos. – Beno

+1

@Beno: El punto es que si n/4 __can__ cabe en la memoria, entonces puede leer en el fragmento más pequeño y hacer una búsqueda binaria -> 1 + log (n) = log (n). Todo lo que hace es tratar la primera iteración del algoritmo de búsqueda binaria ligeramente diferente a las siguientes iteraciones. – unholysampler

1

Un archivo de 5MB no es tan grande; debe poder leer cada línea en una matriz String[], que luego puede usar java.util.Arrays.binarySearch() para encontrar la línea que desea. Este es mi enfoque recomendado.

Si no quiere leer todo el archivo en su aplicación, se vuelve más complicado. Si cada línea del archivo es la misma longitud, y el archivo ya está ordenado, entonces se puede abrir el archivo en RandomAccessFile y llevar a cabo una búsqueda binaria mediante el uso de seek() así ...

// open the file for reading 
RandomAccessFile raf = new RandomAccessFile("myfile.txt","r"); 
String searchValue = "myline"; 
int lineSize = 50; 
int numberOfLines = raf.length()/lineSize; 

// perform the binary search... 
byte[] lineBuffer = new byte[lineSize]; 
int bottom = 0; 
int top = numberOfLines; 
int middle; 
while (bottom <= top){ 
    middle = (bottom+top)/2; 
    raf.seek(middle*lineSize); // jump to this line in the file 
    raf.read(lineBuffer); // read the line from the file 
    String line = new String(lineBuffer); // convert the line to a String 

    int comparison = line.compareTo(searchValue); 
    if (comparison == 0){ 
    // found it 
    break; 
    } 
    else if (comparison < 0){ 
    // line comes before searchValue 
    bottom = middle + 1; 
    } 
    else { 
    // line comes after searchValue 
    top = middle - 1; 
    } 
    } 

raf.close(); // close the file when you're finished 

Sin embargo, si el archivo no tiene líneas de ancho fijo, entonces no se puede realizar fácilmente una búsqueda binaria sin cargarla primero en la memoria, ya que no se puede saltar rápidamente a una línea específica en el archivo como se puede con líneas de ancho fijo .

+2

Tengo 65000 líneas, cada línea es palabra. Me cuelgo cuando leo el archivo a String []. cada palabra tiene una longitud diferente. – Beno

1

En un archivo de texto de longitud de carácter uniforme puede buscar hasta la mitad del intervalo en cuestión, empiece a leer caracteres hasta llegar al delimitador, luego use la cadena siguiente como aproximación para el elemento medio sabio. El problema con hacer esto en Android, sin embargo, es que aparentemente no puedes get random access to a resource (aunque supongo que podrías volver a abrirlo cada vez). Además, esta técnica no se generaliza a mapas y conjuntos de otros tipos.

Otra opción sería (usando un RandomAccessFile) escribir una "matriz" de entradas - una para cada cadena - al principio del archivo, luego retroceder y actualizarlas con las ubicaciones de sus cadenas correspondientes. De nuevo, la búsqueda requerirá saltar alrededor.

Lo que haría (y lo hice en mi propia aplicación) es implementar un hash set en un archivo. Este sí separa el encadenamiento con árboles.

import java.io.BufferedInputStream; 
import java.io.DataInputStream; 
import java.io.File; 
import java.io.FileInputStream; 
import java.io.IOException; 
import java.io.RandomAccessFile; 
import java.util.ArrayList; 
import java.util.Collections; 
import java.util.LinkedList; 
import java.util.Set; 

class StringFileSet { 

    private static final double loadFactor = 0.75; 

    public static void makeFile(String fileName, String comment, Set<String> set) throws IOException { 
     new File(fileName).delete(); 
     RandomAccessFile fout = new RandomAccessFile(fileName, "rw"); 

     //Write comment 
     fout.writeUTF(comment); 

     //Make bucket array 
     int numBuckets = (int)(set.size()/loadFactor); 

     ArrayList<ArrayList<String>> bucketArray = new ArrayList<ArrayList<String>>(numBuckets); 
     for (int ii = 0; ii < numBuckets; ii++){ 
      bucketArray.add(new ArrayList<String>()); 
     } 

     for (String key : set){ 
      bucketArray.get(Math.abs(key.hashCode()%numBuckets)).add(key); 
     } 

     //Sort key lists in preparation for creating trees 
     for (ArrayList<String> keyList : bucketArray){ 
      Collections.sort(keyList); 
     } 

     //Make queues in preparation for creating trees 
     class NodeInfo{ 

      public final int lower; 
      public final int upper; 
      public final long callingOffset; 

      public NodeInfo(int lower, int upper, long callingOffset){ 
       this.lower = lower; 
       this.upper = upper; 
       this.callingOffset = callingOffset; 
      } 

     } 

     ArrayList<LinkedList<NodeInfo>> queueList = new ArrayList<LinkedList<NodeInfo>>(numBuckets); 
     for (int ii = 0; ii < numBuckets; ii++){ 
      queueList.add(new LinkedList<NodeInfo>()); 
     } 

     //Write bucket array 
     fout.writeInt(numBuckets); 
     for (int index = 0; index < numBuckets; index++){ 
      queueList.get(index).add(new NodeInfo(0, bucketArray.get(index).size()-1, fout.getFilePointer())); 
      fout.writeInt(-1); 
     } 

     //Write trees 
     for (int bucketIndex = 0; bucketIndex < numBuckets; bucketIndex++){ 
      while (queueList.get(bucketIndex).size() != 0){ 
       NodeInfo nodeInfo = queueList.get(bucketIndex).poll(); 
       if (nodeInfo.lower <= nodeInfo.upper){ 
        //Set respective pointer in parent node 
        fout.seek(nodeInfo.callingOffset); 
        fout.writeInt((int)(fout.length() - (nodeInfo.callingOffset + 4))); //Distance instead of absolute position so that the get method can use a DataInputStream 
        fout.seek(fout.length()); 

        int middle = (nodeInfo.lower + nodeInfo.upper)/2; 

        //Key 
        fout.writeUTF(bucketArray.get(bucketIndex).get(middle)); 

        //Left child 
        queueList.get(bucketIndex).add(new NodeInfo(nodeInfo.lower, middle-1, fout.getFilePointer())); 
        fout.writeInt(-1); 

        //Right child 
        queueList.get(bucketIndex).add(new NodeInfo(middle+1, nodeInfo.upper, fout.getFilePointer())); 
        fout.writeInt(-1); 
       } 
      } 
     } 

     fout.close(); 
    } 

    private final String fileName; 
    private final int numBuckets; 
    private final int bucketArrayOffset; 

    public StringFileSet(String fileName) throws IOException { 
     this.fileName = fileName; 

     DataInputStream fin = new DataInputStream(new BufferedInputStream(new FileInputStream(fileName))); 

     short numBytes = fin.readShort(); 
     fin.skipBytes(numBytes); 
     this.numBuckets = fin.readInt(); 
     this.bucketArrayOffset = numBytes + 6; 

     fin.close(); 
    } 

    public boolean contains(String key) throws IOException { 
     boolean containsKey = false; 

     DataInputStream fin = new DataInputStream(new BufferedInputStream(new FileInputStream(this.fileName))); 

     fin.skipBytes(4*(Math.abs(key.hashCode()%this.numBuckets)) + this.bucketArrayOffset); 

     int distance = fin.readInt(); 
     while (distance != -1){ 
      fin.skipBytes(distance); 

      String candidate = fin.readUTF(); 
      if (key.compareTo(candidate) < 0){ 
       distance = fin.readInt(); 
      }else if (key.compareTo(candidate) > 0){ 
       fin.skipBytes(4); 
       distance = fin.readInt(); 
      }else{ 
       fin.skipBytes(8); 
       containsKey = true; 
       break; 
      } 
     } 

     fin.close(); 

     return containsKey; 
    } 

} 

Un programa de prueba

import java.io.File; 
import java.io.IOException; 
import java.util.HashSet; 

class Test { 
    public static void main(String[] args) throws IOException { 
     HashSet<String> stringMemorySet = new HashSet<String>(); 

     stringMemorySet.add("red"); 
     stringMemorySet.add("yellow"); 
     stringMemorySet.add("blue"); 

     StringFileSet.makeFile("stringSet", "Provided under ... included in all copies and derivatives ...", stringMemorySet); 
     StringFileSet stringFileSet = new StringFileSet("stringSet"); 

     System.out.println("orange -> " + stringFileSet.contains("orange")); 
     System.out.println("red -> " + stringFileSet.contains("red")); 
     System.out.println("yellow -> " + stringFileSet.contains("yellow")); 
     System.out.println("blue -> " + stringFileSet.contains("blue")); 

     new File("stringSet").delete(); 

     System.out.println(); 
    } 
} 

También tendrá a pass a Context a la misma, siempre y cuando lo modifica para Android, por lo que puede acceder a los getResources método().

Probablemente también va a querer stop the android build tools from compressing the file, que aparentemente solo se puede hacer, si está trabajando con la GUI, cambiando la extensión del archivo a algo como jpg. Esto hizo que el proceso sea de 100 a 300 veces más rápido en mi aplicación.

También puede consultar giving yourself more memory utilizando NDK.

0

He aquí algo que preparo rápidamente. Utiliza dos archivos, uno con las palabras y el otro con los desplazamientos.El formato del archivo de desplazamiento es el siguiente: los primeros 10 bits contienen el tamaño de la palabra, los últimos 22 bits contienen el desplazamiento (la posición de la palabra, por ejemplo, aaah sería 0, abasementable sería 4, etc.). Está codificado en big endian (estándar de Java). Espero que ayude a alguien.

word.dat:

aaahabasementableabnormalabnormalityabortionistabortion-rightsabracadabra

wordx.dat:

00 80 00 00 01 20 00 04 00 80 00 0D 01 00 00 11 _____ __________ 
01 60 00 19 01 60 00 24 01 E0 00 2F 01 60 00 3E _`___`_$___/_`_> 

creé estos archivos en C#, pero aquí está el código para ello (que utiliza un archivo txt con palabras separadas por crlfs)

static void Main(string[] args) 
{ 
    const string fIn = @"C:\projects\droid\WriteFiles\input\allwords.txt"; 
    const string fwordxOut = @"C:\projects\droid\WriteFiles\output\wordx.dat"; 
    const string fWordOut = @"C:\projects\droid\WriteFiles\output\word.dat"; 

    int i = 0; 
    int offset = 0; 
    int j = 0; 
    var lines = File.ReadLines(fIn); 

    FileStream stream = new FileStream(fwordxOut, FileMode.Create, FileAccess.ReadWrite); 
    using (EndianBinaryWriter wwordxOut = new EndianBinaryWriter(EndianBitConverter.Big, stream)) 
    { 
     using (StreamWriter wWordOut = new StreamWriter(File.Open(fWordOut, FileMode.Create))) 
     { 
      foreach (var line in lines) 
      { 
       wWordOut.Write(line); 
       i = offset | ((int)line.Length << 22); //first 10 bits to the left is the word size 
       offset = offset + (int)line.Length; 
       wwordxOut.Write(i); 
       //if (j == 7) 
        // break; 
       j++; 
      } 
     } 
    } 
} 

Y este es el código Java para la búsqueda de archivos binarios:

public static void binarySearch() { 
    String TAG = "TEST"; 
    String wordFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/word.dat"; 
    String wordxFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/wordx.dat"; 

    String target = "abracadabra"; 
    boolean targetFound = false; 
    int searchCount = 0; 

    try { 
     RandomAccessFile raf = new RandomAccessFile(wordxFilePath, "r"); 
     RandomAccessFile rafWord = new RandomAccessFile(wordFilePath, "r"); 
     long low = 0; 
     long high = (raf.length()/4) - 1; 
     int cur = 0; 
     long wordOffset = 0; 
     int len = 0; 

     while (high >= low) { 
      long mid = (low + high)/2; 
      raf.seek(mid * 4); 
      cur = raf.readInt(); 
      Log.v(TAG + "-cur", String.valueOf(cur)); 

      len = cur >> 22; //word length 

      cur = cur & 0x3FFFFF; //first 10 bits are 0 

      rafWord.seek(cur); 
      byte [] bytes = new byte[len]; 

      wordOffset = rafWord.read(bytes, 0, len); 
      Log.v(TAG + "-wordOffset", String.valueOf(wordOffset)); 

      searchCount++; 

      String str = new String(bytes); 

      Log.v(TAG, str); 

      if (target.compareTo(str) < 0) { 
       high = mid - 1; 
      } else if (target.compareTo(str) == 0) { 
       targetFound = true; 
       break; 
      } else { 
       low = mid + 1; 
      } 
     } 

     raf.close(); 
     rafWord.close(); 
    } catch (FileNotFoundException e) { 
     e.printStackTrace(); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 

    if (targetFound == true) { 
     Log.v(TAG + "-found " , String.valueOf(searchCount)); 
    } else { 
     Log.v(TAG + "-not found " , String.valueOf(searchCount)); 
    } 

} 
0

Aunque podría sonar como una exageración, no almacene los datos que hay que hacer esto con un archivo plano. Haga una base de datos y consulte los datos en la base de datos. Esto debería ser efectivo y rápido.

Cuestiones relacionadas