Java 逐行读取文本文件的几种方式以及效率对比

作者: Seven-Steven

原文链接: https://blog.diqigan.cn/posts/java-read-file-by-line.html

前言

上周负责的模块中需要逐行读取文件内容, 写完之后对程序执行效率不太满意, 索性上网查了一下 Java 逐行读取文件内容的各种方法, 并且简单地比对了一下执行效率. 在此记录, 希望能够帮到有需要的人.

注意: 本文比对的项目为 逐行读取文本内容, 并不能代表其他方式的文件读取效率优劣!!!

文末有完整代码.

先放结果

1000000 行文本读取结果比对:

BufferedReader 耗时: 49ms
Scanner 耗时: 653ms
Apache Commons IO 耗时: 44ms
InputStreamReader 耗时: 191ms
FileInputStream 耗时: 3171ms
BufferedInputStream 耗时: 70ms
FileUtils 耗时: 46ms
Files 耗时: 99ms

24488656 行文本读取结果比对:

BufferedReader 耗时: 989ms
Scanner 耗时: 11899ms
Apache Commons IO 耗时: 568ms
InputStreamReader 耗时: 3377ms
FileInputStream 耗时: 78903ms
BufferedInputStream 耗时: 1480ms
FileUtils 耗时: 16569ms
Files 耗时: 25162ms

可见, 当文件较小时:

  • ApacheCommonsIO 流 表现最佳;
  • FileUtils, BufferedReader 居其二;
  • BufferedInputStream, Files 随其后;
  • InputStreamReader, Scanner, FileInputStream 略慢.

当文件较大时, Apache Commons IO 流, BufferedReader 依然出色, Files, FileUtils 速度开始变慢.

简要分析

使用到的工具类包括:

  • java.io.BufferedReader
  • java.util.Scanner
  • org.apache.commons.io.FileUtils
  • java.io.InputStreamReader
  • java.io.FileInputStream
  • java.io.BufferedInputStream
  • com.google.common.io.Files

其中:

Apache Commons IO 流BufferedReader 使用到了缓冲区, 所以在不消耗大量内存的情况下提高了处理速度;

FileUtilsFiles 是先把文件内容全部读入内存, 然后在进行操作, 是典型的空间换时间案例. 这种方法可能会大量消耗内存, 建议酌情使用;

其他几个工具类本来就不擅长逐行读取, 效率底下也是情理之中.

建议

在逐行读取文本内容的需求下, 建议使用 Apache Commons IO 流, 或者 BufferedReader, 既不会过多地占用内存, 也保证了优异的处理速度.

参考文献:

附录-源代码:

import com.google.common.io.Files;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.LineIterator;

import java.io.*;
import java.util.List;
import java.util.Random;
import java.util.Scanner;

/**
 * @Description: 逐行读取文件性能对比
 * @Author: Seven-Steven
 * @Date: 19-1-25
 **/
public class ReadByLineFromFileTest {

  public static void main(String[] args) {
    ReadByLineFromFileTest test = new ReadByLineFromFileTest();
    String filePath = "./testFile.txt";
    File file = new File(filePath);
    if (!file.exists()) {
      // 随机写入 1000000 行内容
      test.writeRandom(filePath, 1000000);
    }

    long before, after, time;
    // 使用 BufferedReader 逐行读取文件
    before = System.currentTimeMillis();
    test.bufferedReader(filePath);
    after = System.currentTimeMillis();
    time = after - before;
    System.out.println("BufferedReader 耗时: " + time + "ms");

    // 使用 Scanner 逐行读取文件
    before = System.currentTimeMillis();
    test.scanner(filePath);
    after = System.currentTimeMillis();
    time = after - before;
    System.out.println("Scanner 耗时: " + time + "ms");

    // 使用 Apache Commons IO 流逐行读取文件
    before = System.currentTimeMillis();
    test.apacheCommonsIo(filePath);
    after = System.currentTimeMillis();
    time = after - before;
    System.out.println("Apache Commons IO 耗时: " + time + "ms");

    // 使用 InputStreamReader 逐字符读取文件
    before = System.currentTimeMillis();
    test.inputStreamReader(filePath);
    after = System.currentTimeMillis();
    time = after - before;
    System.out.println("InputStreamReader 耗时: " + time + "ms");

    // 使用 FileInputStream 逐字符读取文件
    before = System.currentTimeMillis();
    test.fileInputStream(filePath);
    after = System.currentTimeMillis();
    time = after - before;
    System.out.println("FileInputStream 耗时: " + time + "ms");


    // 使用 BufferedInputStream 逐字符读取文件
    before = System.currentTimeMillis();
    test.bufferedInputStream(filePath);
    after = System.currentTimeMillis();
    time = after - before;
    System.out.println("BufferedInputStream 耗时: " + time + "ms");

    // 使用 FileUtils 一次性读取文件所有行
    before = System.currentTimeMillis();
    test.fileUtils(filePath);
    after = System.currentTimeMillis();
    time = after - before;
    System.out.println("FileUtils 耗时: " + time + "ms");

    // 使用 Files 一次性读取文件所有行
    before = System.currentTimeMillis();
    test.files(filePath);
    after = System.currentTimeMillis();
    time = after - before;
    System.out.println("Files 耗时: " + time + "ms");
  }

  /**
   * @Description: 使用 Apache Commons IO 流逐行读取文件
   * Maven 依赖:
   *         <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
   *         <dependency>
   *             <groupId>commons-io</groupId>
   *             <artifactId>commons-io</artifactId>
   *             <version>2.6</version>
   *         </dependency>
   * @Param: [filePath] 文件路径
   * @Author: Seven-Steven
   * @Date: 19-1-24
   **/
  public void apacheCommonsIo(String filePath) {
    File file = new File(filePath);
    if (!file.exists()) {
      return;
    }

    try {
      LineIterator iterator = FileUtils.lineIterator(file, "UTf-8");
      while (iterator.hasNext()) {
        String line = iterator.nextLine();
        // TODO
        // System.out.println(line);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * @Description: 使用 Scanner 类逐行读取
   * @Param: [filePath] 文件路径
   * @Author: Seven-Steven
   * @Date: 19-1-24
   **/
  public void scanner(String filePath) {
    File file = new File(filePath);
    if (!file.exists()) {
      return;
    }

    FileInputStream fileInputStream = null;
    Scanner scanner = null;
    try {
      fileInputStream = new FileInputStream(file);
      scanner = new Scanner(fileInputStream, "UTF-8");

      while (scanner.hasNextLine()) {
        // TODO things
        String line = scanner.nextLine();
        // System.out.println(line);
      }
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } finally {
      if (fileInputStream != null) {
        try {
          fileInputStream.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }

      if (scanner != null) {
        scanner.close();
      }
    }
  }

  /**
   * @Description: 使用 Files 一次性读取所有行
   * Maven 依赖:
   *         <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
   *         <dependency>
   *             <groupId>com.google.guava</groupId>
   *             <artifactId>guava</artifactId>
   *             <version>r05</version>
   *         </dependency>
   * @Param: [filePath] 文件路径
   * @Author: Seven-Steven
   * @Date: 19-1-24
   **/
  public void files(String filePath) {
    File file = new File(filePath);
    if (!file.exists()) {
      return;
    }

    try {
      List<String> fileLines = Files.readLines(file, Charsets.toCharset("UTF-8"));
      for (String str : fileLines) {
        // TODO things
        // System.out.println(str);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * @Description: 使用 FileUtils 一次性将文件所有行读入内存
   * Maven 依赖:
   *         <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
   *         <dependency>
   *             <groupId>commons-io</groupId>
   *             <artifactId>commons-io</artifactId>
   *             <version>2.6</version>
   *         </dependency>
   * @Param: [filePath] 文件路径
   * @Author: Seven-Steven
   * @Date: 19-1-24
   **/
  public void fileUtils(String filePath) {
    File file = new File(filePath);
    if (!file.exists()) {
      return;
    }

    try {
      List<String> fileLines = FileUtils.readLines(file, Charsets.UTF_8);
      for (String str : fileLines) {
        // TODO
        // System.out.println(str);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public void bufferedInputStream(String filePath) {
    File file = new File(filePath);
    if (!file.exists()) {
      return;
    }

    FileInputStream fileInputStream = null;
    BufferedInputStream bufferedInputStream = null;
    try {
      fileInputStream = new FileInputStream(file);
      bufferedInputStream = new BufferedInputStream(fileInputStream);

      int temp;
      char character;
      String line = "";
      while ((temp = bufferedInputStream.read()) != -1) {
        character = (char) temp;
        if (character != '\n') {
          line += character;
        } else {
          // TODO
          // System.out.println(line);
          line = "";
        }
      }
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (fileInputStream != null) {
        try {
          fileInputStream.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }

      if (bufferedInputStream != null) {
        try {
          bufferedInputStream.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }

  /**
   * @Description: 使用 FileInputStream 逐字符读取文件
   * @Param: [filePath] 文件路径
   * @Author: Seven-Steven
   * @Date: 19-1-23
   **/
  public void fileInputStream(String filePath) {
    File file = new File(filePath);
    if (!file.exists()) {
      return;
    }

    FileInputStream fileInputStream = null;
    try {
      fileInputStream = new FileInputStream(file);
      int temp;
      char character;
      String line = "";
      while ((temp = fileInputStream.read()) != -1) {
        character = (char) temp;
        if (character != '\n') {
          line += character;
        } else {
          // TODO
          // System.out.println(line);
          line = "";
        }
      }
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (fileInputStream != null) {
        try {
          fileInputStream.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }

  /**
   * @Description: 使用 InputStreamReader 逐行读取文件
   * @Param: [filePath] 文件路径
   * @Author: Seven-Steven
   * @Date: 19-1-23
   **/
  public void inputStreamReader(String filePath) {
    File file = new File(filePath);
    if (!file.exists()) {
      return;
    }

    FileInputStream fileInputStream = null;
    InputStreamReader inputStreamReader = null;
    try {
      fileInputStream = new FileInputStream(file);
      inputStreamReader = new InputStreamReader(fileInputStream);
      int temp;
      char character;
      String line = "";
      while ((temp = inputStreamReader.read()) != -1) {
        character = (char) temp;
        if (character != '\n') {
          line += character;
        } else {
          // TODO
          // System.out.println(line);
          line = "";
        }
      }
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (fileInputStream != null) {
        try {
          fileInputStream.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }

      if (inputStreamReader != null) {
        try {
          inputStreamReader.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }

  /**
   * @Description: 使用 BufferedReader 逐行读取文件内容
   * @Param: [filePath] 文件路径
   * @Author: Seven-Steven
   * @Date: 19-1-23
   **/
  public void bufferedReader(String filePath) {
    File file = new File(filePath);
    if (!file.exists()) {
      return;
    }

    FileReader fileReader = null;
    BufferedReader bufferedReader = null;
    try {
      fileReader = new FileReader(file);
      bufferedReader = new BufferedReader(fileReader);

      String line = "";
      while ((line = bufferedReader.readLine()) != null) {
        // TODO things
        // System.out.println(line);
      }
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (fileReader != null) {
        try {
          fileReader.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }

      if (bufferedReader != null) {
        try {
          bufferedReader.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }

  /**
   * @Description: 随机往文件中写入 totalLines 行内容
   * @Param: [filePath, totalLines] 文件路径, 内容行数
   * @Author: Seven-Steven
   * @Date: 19-1-23
   **/
  public void writeRandom(String filePath, int totalLines) {
    RandomAccessFile file = null;
    Random random = new Random();
    try {
      file = new RandomAccessFile(filePath, "rw");
      long length = file.length();
      for (int i = 0; i < totalLines; i++) {
        file.seek(length);
        int number = random.nextInt(1000000);
        String line = number + "\n";
        file.writeBytes(line);
        length += line.length();
      }
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (file != null) {
        try {
          file.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容