Groovy: чтение диапазона строк из файла - PullRequest
13 голосов
/ 03 ноября 2010

У меня есть текстовый файл с довольно большим объемом данных около 2 000 000 строк.Пролистать файл с помощью следующего фрагмента кода легко, но это не то, что мне нужно; -)

def f = new File("input.txt")
f.eachLine() {
    // Some code here
}

Мне нужно прочитать только определенный диапазон строк из файла.Есть ли способ указать начальную и конечную строку, как это (псевдокод)?Я бы хотел избежать загрузки всех строк в память с помощью readLines () перед выбором диапазона.

// Read all lines from 4 to 48
def f = new File("input.txt")
def start = 4
def end = 48
f.eachLine(start, end) {
    // Some code here
}

Если это невозможно с Groovy, приветствуется любое решение Java: -)

Ура, Роберт

Ответы [ 9 ]

8 голосов
/ 03 ноября 2010

Решение Java:

BufferedReader r = new BufferedReader(new FileReader(f));
String line;
for ( int ln = 0; (line = r.readLine()) != null && ln <= end; ln++ ) {
    if ( ln >= start ) {
        //Some code here
    }
}

Брутто, а?

К сожалению, если ваши строки не имеют фиксированной длины, вы не сможете перейти к start thстрока эффективно, так как каждая строка может быть произвольно длинной и, следовательно, все данные должны быть прочитаны.Это не исключает более приятного решения.

Java 8

Я подумал, что стоит обновить, чтобы показать, как сделать это эффективно с потоками:

int start = 5;
int end = 12;
Path file = Paths.get("/tmp/bigfile.txt");

try (Stream<String> lines = Files.lines(file)) {
    lines.skip(start).limit(end-start).forEach(System.out::println);
}

Поскольку потоки лениво оцениваются, он будет читать только строки вплоть до end (плюс любую внутреннюю буферизацию, которую он выберет).

4 голосов
/ 22 октября 2015

Groovy теперь имеет возможность начинать с какой-то специальной строки.Вот две цитаты из документов в файле

Object eachLine(int firstLine, Closure closure) 

Object eachLine(String charset, int firstLine, Closure closure) 
4 голосов
/ 04 ноября 2010

Вот решение Groovy. К сожалению, это будет читать каждую строку файла после start

def start = 4
def end = 48

new File("input.txt").eachLine(start) {lineNo, line ->

    if (lineNo <= end) {
        // Process the line
    }
}
3 голосов
/ 03 ноября 2010

Я не верю, что существует какой-либо «волшебный» способ пропустить произвольную «строку» в файле. Строки определяются символами новой строки, поэтому без чтения файла невозможно узнать, где они будут. Я полагаю, у вас есть два варианта:

  1. Следуйте ответу Марка Питера и используйте BufferedReader , чтобы прочитать файл по одной строке за раз, пока не достигнете желаемой строки. Это, очевидно, будет медленно.
  2. Выясните, сколько байт (а не строк) должно начинаться при следующем чтении и непосредственно искать в этой точке файла, используя что-то вроде RandomAccessFile . Возможность эффективного определения правильного количества байтов зависит от вашего приложения. Например, если вы читаете файл последовательно, по одному фрагменту за раз, вы просто записываете позицию, на которой остановились. Если все строки имеют фиксированную длину L байтов, то переход к строке N - это просто поиск позиции N * L. Если это операция, которую вы часто повторяете, может помочь некоторая предварительная обработка: например, прочитать весь файл один раз и записать начальную позицию каждой строки в HashMap в памяти. В следующий раз, когда вам нужно перейти к строке N, просто посмотрите на ее положение в HashMap и перейдите непосредственно к этой точке.
2 голосов
/ 26 сентября 2013

В Groovy вы можете использовать Категория

class FileHelper {
    static eachLineInRange(File file, IntRange lineRange, Closure closure) {
        file.withReader { r->
            def line
            for(; (line = r.readLine()) != null;) {
                def lineNo = r.lineNumber
                if(lineNo < lineRange.from) continue
                if(lineNo > lineRange.to) break
                closure.call(line, lineNo)
            }
        }
    }
}

def f = '/path/to/file' as File
use(FileHelper) {
    f.eachLineInRange(from..to){line, lineNo ->
        println "$lineNo) $line"
    }
}

или ExpandoMetaClass

File.metaClass.eachLineInRange = { IntRange lineRange, Closure closure ->
    delegate.withReader { r->
        def line
        for(; (line = r.readLine()) != null;) {
            def lineNo = r.lineNumber
            if(lineNo < lineRange.from) continue
            if(lineNo > lineRange.to) break
            closure.call(line, lineNo)
        }
    }
}

def f = '/path/to/file' as File
f.eachLineInRange(from..to){line, lineNo ->
    println "$lineNo) $line"
}

В этом решении вы читаете каждую строку из файла последовательно, но не сохраняете их все в памяти.

2 голосов
/ 04 февраля 2011

Это должно сделать это.Я полагаю, что после "end" эта строка не читается.

def readRange = {file ->
    def start = 10
    def end = 20
    def fileToRead = new File(file)
    fileToRead.eachLine{line, lineNo = 0 ->
        lineNo++
        if(lineNo > end) {
            return
        }
        if(lineNo >= start) {
            println line                
        }            
    }
}
1 голос
/ 03 ноября 2010

Спасибо за все ваши советы.Из того, что вы написали, я сделал свой собственный кусок кода, который, кажется, работает.Не элегантно, но служит своей цели: -)

def f = new RandomAccessFile("D:/input.txt", "r")
def start = 3
def end = 6
def current = start-1
def BYTE_OFFSET = 11
def resultList = []

if ((end*BYTE_OFFSET) <= f.length()) {
    while ((current*BYTE_OFFSET) < (end*BYTE_OFFSET)) {
        f.seek(current*BYTE_OFFSET)
        resultList << f.readLine()
        current++
    }
}
1 голос
/ 03 ноября 2010

Вам нужно перебрать строки с начала, чтобы добраться до начальной позиции, но вы можете использовать LineNumberReader (вместо BufferedReader), потому что он будет отслеживать номера строк для вас.

    final int start = 4;
    final int end = 48;

    final LineNumberReader in = new LineNumberReader(new FileReader(filename));
    String line=null;
    while ((line = in.readLine()) != null && in.getLineNumber() <= end) {
        if (in.getLineNumber() >= start) {
            //process line
        }
    }
0 голосов
/ 03 ноября 2010

Вот еще одно Java-решение, использующее LineIterator и FileUtils из Commons / IO :

public static Collection<String> readFile(final File f,
    final int startOffset,
    final int lines) throws IOException{
    final LineIterator it = FileUtils.lineIterator(f);
    int index = 0;
    final Collection<String> coll = new ArrayList<String>(lines);
    while(index++ < startOffset + lines && it.hasNext()){
        final String line = it.nextLine();
        if(index >= startOffset){
            coll.add(line);
        }
    }
    it.close();
    return coll;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...