架构系列十二(从性能角度看IO选择)

138 阅读5分钟

1.引子

日常开发中,jdk类库给我们提供了io操作相关api,有字节流,字符流;有负责读的输入流,负责写的输出流。且整个io相关的api非常丰富,丰富就意味着实际项目中有一定选择上的困难,尤其对于刚入门的朋友(想想我们刚入门的时候,是不是被各种io类库api绕晕)。

正好前几天有朋友问,项目中有频繁读取和写入文件的需求,如何选择合适的io api?于是顺手写了一个benchmark的测试demo,特意分享出来,期望给有需要的朋友带来一点参考。内容涉及写入文件、读取文件相关的io api对比

  • 写入文件

    • FileOutputStream:字节输出流
    • BufferedOutputStream:字节输出流,提供缓冲区能力,减少系统调用,性能更好
    • FileWriter:字符输出流
    • BufferedWriter:字符输出流,提供缓冲区能力,减少系统调用,性能更好
  • 读取文件

    • FileInputStream:字节输入流
    • BufferedInputStream:字节输入流,提供缓冲区能力,减少系统调用,性能更好
    • FileReader:字符输入流
    • BufferedReader:字符输入流,提供缓冲区能力,减少系统调用,性能更好

案例设计分别读取写入100m的文件,对比执行耗时,实现代码比较简单,下面让我们一起来看一下。

2.案例

2.1.写入文件

2.1.1.公共方法

/**
* 处理文件路径,匹配Linux、Windows操作系统
* @param path
* @return
*/
private static String getRealFilePath(String path) {
  String FILE_SEPARATOR = System.getProperty("file.separator");
  return path.replace("/", FILE_SEPARATOR).replace("\", FILE_SEPARATOR);
}
​
/**
* 根据文件名称,创建File对象
* @param fileName
* @return
*/
private static File getFile(String fileName){
   String DIR = "D:\tmp";
   String filePath = DIR + "\" + fileName;
   filePath = getRealFilePath(filePath);
​
   log.info("写入目录:{},文件完整路径:{}", DIR,filePath);
   File file = new File(filePath);
   if( file.exists()){
       file.delete();
   }
​
    return file;
}

2.1.2.FileOutputStream

/**
* 通过FileOutputStream写入文件
*/
public static void writeFileByFileOutputStream() throws Exception{
        // 统计时间开始
        Long startTime = System.currentTimeMillis();
        log.info("通过FileOutputStream写入文件开始");
​
        // 开始写文件
        File file = getFile("one.txt");
        FileOutputStream out = new FileOutputStream(file);
        try {
            for(int i = 0; i < COUNT; i++){
                out.write(DATA.getBytes());
            }
        } finally {
            out.close();
        }
​
        // 统计时间结束
        Long endTime = System.currentTimeMillis();
        log.info("通过FileOutputStream写入文件结束,写入文件行数:{},共耗时:{}毫秒",COUNT,(endTime - startTime));
}

2.1.3.BufferedOutputStream

/**
* 通过BufferedOutputStream写入文件
*/
public static void writeFileByBufferedOutputStream() throws Exception{
        // 统计时间开始
        Long startTime = System.currentTimeMillis();
        log.info("通过BufferedOutputStream写入文件开始");
​
        // 开始写文件
        File file = getFile("two.txt");
        FileOutputStream out = new FileOutputStream(file);
        BufferedOutputStream buffOut = new BufferedOutputStream(out);
        try {
            for(int i = 0; i < COUNT; i++){
                buffOut.write(DATA.getBytes());
            }
            buffOut.flush();
        } finally {
            buffOut.close();
            out.close();
        }
​
        Long endTime = System.currentTimeMillis();
        log.info("通过BufferedOutputStream写入文件结束,写入文件行数:{},共耗时:{}毫秒",COUNT,(endTime - startTime));
}

2.1.4.FileWriter

/**
* 通过FileWriter写入文件
*/
public static void writeFileByFileWriter()throws Exception{
        // 统计时间开始
        Long startTime = System.currentTimeMillis();
        log.info("通过FileWriter写入文件开始");
​
        // 开始写文件
        File file = getFile("three.txt");
        FileWriter fw = new FileWriter(file);
        try {
            for(int i = 0; i < COUNT; i++){
                fw.write(DATA);
            }
        } finally {
            fw.close();
        }
​
        Long endTime = System.currentTimeMillis();
        log.info("通过FileWriter写入文件结束,写入文件行数:{},共耗时:{}毫秒",COUNT,(endTime - startTime));
​
}

2.1.5.BufferedWriter

/**
* 通过BufferedWriter写入文件
*/
public static void writeFileByBufferedWriter() throws Exception{
        // 统计时间开始
        Long startTime = System.currentTimeMillis();
        log.info("通过BufferedWriter写入文件开始");
​
        // 开始写文件
        File file = getFile("four.txt");
        BufferedWriter bw = new BufferedWriter(new FileWriter(file));
        try {
            for(int i = 0; i < COUNT; i++){
                bw.write(DATA);
            }
            bw.flush();
        } finally {
            bw.close();
        }
​
        Long endTime = System.currentTimeMillis();
        log.info("通过BufferedWriter写入文件结束,写入文件行数:{},共耗时:{}毫秒",COUNT,(endTime - startTime));
​
}

2.1.6.执行测试

 /**
* main
* @param args
*/
public static void main(String[] args) throws Exception {
        // 1.FileOutputStream写入文件
        writeFileByFileOutputStream();
​
        // 2.BufferedOutputStream写入文件
        writeFileByBufferedOutputStream();
​
        // 3.FileWriter写入文件
        writeFileByFileWriter();
​
        // 4.通过BufferedWriter写入文件
        writeFileByBufferedWriter();
​
}
​
通过FileOutputStream写入文件结束,写入文件行数:1000000,共耗时:4765毫秒
通过BufferedOutputStream写入文件结束,写入文件行数:1000000,共耗时:297毫秒
通过FileWriter写入文件结束,写入文件行数:1000000,共耗时:296毫秒
通过BufferedWriter写入文件结束,写入文件行数:1000000,共耗时:250毫秒

从执行结果,我们看到,带buffered缓冲区执行耗时更短,即性能更好,你知道为什么吗?答案我想留给你去思考。

2.2.读取文件

2.2.1.公共方法

/**
* 处理文件路径,匹配Linux、Windows操作系统
* @param path
* @return
*/
private static String getRealFilePath(String path) {
    String FILE_SEPARATOR = System.getProperty("file.separator");
    return path.replace("/", FILE_SEPARATOR).replace("\", FILE_SEPARATOR);
}
​
/**
* 根据文件名称,创建File对象
* @param fileName
* @return
*/
private static File getFile(String fileName){
   String DIR = "D:\tmp";
   String filePath = DIR + "\" + fileName;
   filePath = getRealFilePath(filePath);
​
   log.info("读取目录:{},文件完整路径:{}", DIR,filePath);
   File file = new File(filePath);
​
   return file;
}

2.2.2.FileInputStream

 /**
* 通过FileInputStream读取文件
* @throws Exception
*/
public static void readFileByFileInputStream()throws  Exception{
        // 统计时间开始
        Long startTime = System.currentTimeMillis();
        log.info("通过FileInputStream读取文件开始");
​
        // 开始读文件
        File file = getFile("one.txt");
        FileInputStream in = new FileInputStream(file);
        byte[] bytes = new byte[1024];
        StringBuilder builder =  new StringBuilder();
        try {
            while(in.read(bytes) > 0){
                builder.append(new String(bytes,"UTF-8"));
            }
​
        } finally {
            in.close();
        }
​
        Long endTime = System.currentTimeMillis();
        log.info("通过FileInputStream读取文件结束,共耗时:{}毫秒",(endTime - startTime));
​
}

2.2.3.BufferedInputStream

/**
* 通过BufferedInputStream读取文件
* @throws Exception
*/
public static void readFileByBufferedInputStream() throws Exception{
        // 统计时间开始
        Long startTime = System.currentTimeMillis();
        log.info("通过BufferedInputStream读取文件开始");
​
        // 开始读文件
        File file = getFile("two.txt");
        FileInputStream in = new FileInputStream(file);
        BufferedInputStream bis = new BufferedInputStream(in);
        byte[] bytes = new byte[1024];
        StringBuilder builder =  new StringBuilder();
        try {
            while(bis.read(bytes) > 0){
                builder.append(new String(bytes,"UTF-8"));
            }
​
        } finally {
            in.close();
        }
​
        Long endTime = System.currentTimeMillis();
        log.info("通过BufferedInputStream读取文件结束,共耗时:{}毫秒",(endTime - startTime));
}

2.2.4.FileReader

/**
* 通过FileReader读取文件
* @throws Exception
*/
public static void readFileByFileReader() throws Exception{
        // 统计时间开始
        Long startTime = System.currentTimeMillis();
        log.info("通过FileReader读取文件开始");
​
        // 开始读文件
        File file = getFile("three.txt");
        FileReader reader = new FileReader(file);
        char[] chars = new char[1024];
        StringBuilder builder =  new StringBuilder();
        try {
            while(reader.read(chars) > 0){
                builder.append(new String(chars));
            }
​
        } finally {
            reader.close();
        }
​
        Long endTime = System.currentTimeMillis();
        log.info("通过FileReader读取文件结束,共耗时:{}毫秒",(endTime - startTime));
}

2.2.5.BufferedReader

/**
* 通过BufferedReader读取文件
* @throws Exception
*/
public static void readFileByBufferedReader() throws Exception{
        // 统计时间开始
        Long startTime = System.currentTimeMillis();
        log.info("通过BufferedReader读取文件开始");
​
        // 开始读文件
        File file = getFile("four.txt");
        FileReader reader = new FileReader(file);
        BufferedReader bufferedReader = new BufferedReader(reader);
        StringBuilder builder =  new StringBuilder();
        try {
            String line = bufferedReader.readLine();
            while(line != null){
                builder.append(line);
                line = bufferedReader.readLine();
            }
​
        } finally {
            bufferedReader.close();
            reader.close();
​
        }
​
        Long endTime = System.currentTimeMillis();
        log.info("通过BufferedReader读取文件结束,共耗时:{}毫秒",(endTime - startTime));
}

2.2.6.执行测试

/**
* main
* @param args
*/
public static void main(String[] args) throws Exception{
        // 1.FileInputStream读取文件
        readFileByFileInputStream();
​
        // 2.BufferedInputStream读取文件
        readFileByBufferedInputStream();
​
        // 3.FileReader读取文件
        readFileByFileReader();
​
        // 4.BufferedReader读取文件
        readFileByBufferedReader();
​
}
​
通过FileInputStream读取文件结束,共耗时:766毫秒
通过BufferedInputStream读取文件结束,共耗时:424毫秒
通过FileReader读取文件结束,共耗时:688毫秒
通过BufferedReader读取文件结束,共耗时:375毫秒

从执行结果,我们看到,带buffered缓冲区执行耗时更短,即性能更好,你知道为什么吗?答案我想留给你去思考。