代码之家  ›  专栏  ›  技术社区  ›  Robert Strauch

groovy:从文件中读取一系列行

  •  13
  • Robert Strauch  · 技术社区  · 14 年前

    我有一个文本文件,其中有相当大的数据量,大约有200万行。使用以下代码片段浏览文件很容易,但这不是我需要的;-)

    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 年前
        1
  •  3
  •   Yevgeniy Brikman    14 年前

    我不相信有任何“神奇”的方法可以跳过文件中的任意“行”。行仅由换行符定义,因此如果不实际读取文件,就无法知道它们将在哪里。我相信你有两个选择:

    1. 按照马克·彼得的回答,用 BufferedReader 一次读取一行文件,直到达到所需行。这显然会很慢。
    2. 算出有多少 字节 (而不是行)您的下一次读取需要从文件中的该点开始,并使用类似的 RandomAccessFile . 能否有效地知道正确的字节数取决于您的应用程序。例如,如果您按顺序读取文件,一次读取一个片段,则只需记录您离开的位置。如果所有行的长度都是固定的l字节,那么到达n行只是寻找n*l位置的问题。如果这是一个经常重复的操作,一些预处理可能会有所帮助:例如,读取整个文件一次,并在内存散列图中记录每行的起始位置。下一次您需要转到第n行时,只需查找它在哈希图中的位置并直接搜索到该点。
        2
  •  8
  •   Mark Peters    8 年前

    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 因为每行可以任意长,所以需要读取所有数据,所以可以有效地使用第行。这并不排除 更好的 但是解决方案。

    爪哇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 (加上它选择做的任何内部缓冲)。

        3
  •  4
  •   Dónal    14 年前

    这是一个绝妙的解决方案。不幸的是,这将读取文件的每一行 start

    def start = 4
    def end = 48
    
    new File("input.txt").eachLine(start) {lineNo, line ->
    
        if (lineNo <= end) {
            // Process the line
        }
    }
    
        4
  •  4
  •   Gangnus    9 年前

    groovy现在有可能从一些特殊的行开始。这是两篇引文 docs on File

    Object eachLine(int firstLine, Closure closure) 
    
    Object eachLine(String charset, int firstLine, Closure closure) 
    
        5
  •  2
  •   Vinay    14 年前

    这应该可以做到。我相信这句话在“结束”后没有读到。

    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
  •  2
  •   Jarek Przygódzki    11 年前

    在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
  •  1
  •   dogbane    14 年前

    您必须从一开始迭代这些行才能到达起始位置,但是您可以使用 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
  •  1
  •   Robert Strauch    14 年前

    谢谢你的提示。根据你所写的,我拼凑出了自己的一段代码,这段代码似乎有效。不优雅,但符合其目的

    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
  •  0
  •   Sean Patrick Floyd    14 年前

    这是另一个使用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;
    }