Groovy: чтение ряда строк из файла
У меня есть текстовый файл с довольно большим объемом данных около 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: -)
Cheers,
Роберт
Ответы
Ответ 1
Я не верю, что есть какой-либо "волшебный" способ пропустить произвольную "строку" в файле. Линии просто определяются символами новой строки, поэтому, не просматривая файл, нет способа узнать, где они будут. Я считаю, что у вас есть два варианта:
- Следуйте указаниям Марка Питера и используйте BufferedReader, чтобы прочитать файл в одной строке за раз, пока не достигнете нужной строки. Это, очевидно, будет медленным.
- Выясните, сколько байтов (а не строк) ваше следующее чтение должно начинаться и искать прямо в этой точке файла, используя что-то вроде RandomAccessFile. Независимо от того, возможно ли эффективно знать правильное количество байтов, зависит от вашего приложения. Например, если вы читаете файл последовательно, по одному фрагменту за раз, вы просто записываете позицию, в которой вы остановились. Если все строки имеют фиксированную длину L байтов, то переход к строке N - это просто поиск позиции N * L. Если это операция, которую вы часто повторяете, некоторая предварительная обработка может помочь: например, прочитать весь файл один раз и записать начальную позицию каждой строки в HashMap в памяти. В следующий раз, когда вам нужно перейти к строке N, просто найдите его позицию в HashMap и обратитесь непосредственно к этой точке.
Ответ 2
Решение 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
}
}
Gross, eh?
К сожалению, если ваши строки не являются фиксированной длиной, вы не сможете эффективно перейти к строке start
th, так как каждая строка может быть сколь угодно длинной, и поэтому все данные должны быть прочитаны. Это не исключает более приятного решения.
Java 8
Думал, что стоит обновить, чтобы показать, как эффективно это делать с Streams:
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);
}
Поскольку Streams лениво оценивается, он будет читать строки до и включая end
(плюс любая внутренняя буферизация, которую он выбирает).
Ответ 3
Здесь a Groovy решение. К сожалению, это будет читать каждую строку файла после start
def start = 4
def end = 48
new File("input.txt").eachLine(start) {lineNo, line ->
if (lineNo <= end) {
// Process the line
}
}
Ответ 4
Groovy имеет возможность начать с некоторой специальной строки. Вот два цитаты из docs в файле
Object eachLine(int firstLine, Closure closure)
Object eachLine(String charset, int firstLine, Closure closure)
Ответ 5
Это должно сделать это. Я считаю, что это не читает ни одной строки после "конца".
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
}
}
}
Ответ 6
В Groovy вы можете использовать Category
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"
}
В этом решении вы читаете каждую строку из файла последовательно, но не сохраняете их в памяти.
Ответ 7
Вам нужно перебирать строки с начала, чтобы перейти в исходное положение, но вы можете использовать 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
}
}
Ответ 8
Спасибо за все ваши намеки. Из того, что вы написали, я вымотал свой кусок кода, который, кажется, работает. Не элегантный, но он служит своей цели: -)
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++
}
}
Ответ 9
Здесь другое решение Java, использующее LineIterator и FileUtils from 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;
}