2016-05-27 6 views
3

У меня очень большой (11GB) .json файл (да, кто бы ни думал , что отличная идея?), Что мне нужно пробовать (читайте k случайных строк).Java - получить строку из файла случайного доступа на основе смещений

Я не очень подкованных в Java файл IO, но я, конечно, нашел этот пост: How to get a random line of a text file in Java?

Я сбросив принятый ответ, потому что это явно способ слишком медленно, чтобы прочитать каждый строка 11GB-файла, чтобы выбрать один (или, скорее, k) из строк около 100k.

К счастью, есть второе предложение размещены там, что я думаю, что может быть лучше использование для меня:

Использование RandomAccessFile стремиться к случайной позиции байта в файле.

Ищите влево и вправо до следующего терминатора линии. Пусть L - прямая между ними.

С вероятностью (MIN_LINE_LENGTH/L.length) возвращение L. В противном случае, начните заново с шага 1.

до сих пор так хорошо, но мне было интересно, о том, что «пусть L будет линия между ними» ,

я сделал бы что-то вроде этого (непроверенные):

RandomAccessFile raf = ... 
long pos = ... 
String line = getLine(raf,pos); 
... 

где

private String getLine(RandomAccessFile raf, long start) throws IOException{ 
    long pos = (start % 2 == 0) ? start : start -1; 

    if(pos == 0) return raf.readLine(); 

    do{ 
     pos -= 2; 
     raf.seek(pos); 
    }while(pos > 0 && raf.readChar() != '\n'); 

    pos = (pos <= 0) ? 0 : pos + 2; 
    raf.seek(pos); 
    return raf.readLine(); 
} 

, а затем работать с line.length(), который воздерживается необходимость явно искать правильный конец линии.

Итак, почему «искать влево и вправо на следующую строку терминатора»? Есть ли более удобный способ получить линию от этих двух смещений?

ответ

2

Похоже, что это будет примерно то же самое - raf.readLine()является, ища права на следующий терминатор линии; это просто делает это для вас.


Одна вещь, чтобы отметить, что RandomAccessFile.readLine() не поддерживает чтение юникод строки из файла:

Каждый байт преобразуется в символ, принимая значение байта, для нижних восьми битам символа и установки высоких восьми бит символа в ноль. Поэтому этот метод не поддерживает полный набор символов Юникода.

Демо некорректного чтения:

import java.io.*; 
import java.nio.charset.StandardCharsets; 

class Demo { 
    public static void main(String[] args) throws IOException { 
    try (FileOutputStream fos = new FileOutputStream("output.txt"); 
     OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8); 
     BufferedWriter writer = new BufferedWriter(osw)) { 
     writer.write("ⵉⵎⴰⵣⵉⵖⵏ"); 
    } 

    try (RandomAccessFile raf = new RandomAccessFile("output.txt", "r")) { 
     System.out.println(raf.readLine()); 
    } 
    } 
} 

Выход:

âµâµâ´°âµ£âµâµâµ 

Но output.txt действительно содержит правильные данные:

$ cat output.txt 
ⵉⵎⴰⵣⵉⵖⵏ 

Таким образом, вы можете делайте поиск самостоятельно или явно c переведите результат raf.readLine() в правильную кодировку:

String line = new String(
    raf.readLine().getBytes(StandardCharsets.ISO_8859_1),  
    StandardCharsets.UTF_8); 
+0

Большое спасибо. Но как «делать поиск себя» отличается от выполнения '' raf.readLine() '' и затем преобразования? Могу ли я каким-то образом определить InputStreamReader, который начинается в начале строки? – User1291

+1

Логично, не было никакой разницы; он может просто включать выделение меньшего количества объектов, если вы сделаете это самостоятельно. Я начну с подхода readline/convert и пересматриваю его позже, если это окажется узким местом производительности. –

Смежные вопросы