Java 之旅

中级05 - File与IO

2019-08-28  本文已影响0人  晓风残月1994

IO就是输入输出。IO 的本质就是字节流。

一切文件的本质就是一段字节流,不管是文本文件(txt/代码/HTML等)。由每个程序负责解释文件中的字节流,赋予具体含义。

一、IO

1. InputStream/OutputStream

是一种抽象的输入/输出操作,无论是文件写入字节流;从网络读取字节流,或其他什么地方读取字节流。
如果对文件系统不是十分熟悉的话,永远使用绝对路径,防止踩坑。

从输入流中读取字节:

FileInputStream("C:\\Users\\Administrator\\Projects\\tmp\\read-write-files\\src\\main\\java\\com\\github\\hcsp\\io\\Main.java");

        while(true) {
            int b = is.read();
            if (b == -1) {
                break;
            }
//            System.out.print((char)b);
        }

向输出流中写入字节:

OutputStream os = new FileOutputStream("C:\\Users\\Administrator\\Projects\\tmp\\read-write-files\\src\\main\\java\\com\\github\\hcsp\\io\\output.txt");
        os.write('/');
        os.write('/');
        os.write('o');
        os.write('k');

fork 一个子进程(命令行程序),并从标准输入中读取字节:

        ProcessBuilder pb = new ProcessBuilder("ls");
        Process process = pb.start();
        while(true) {
            int b = process.getInputStream().read();
            if (b == -1) {
                break;
            }
            System.out.print((char)b);
        }

        File home = new File("C:\\Users\\Administrator\\Desktop");
        System.out.println(home.isAbsolute());
        home.isFile();
        home.isAbsolute();
        home.isHidden();
        home.exists();
        System.out.println(home);
    }
}

2. FIle

别误会!File 并不代表一个“文件”,它只代表一个抽象的“文件路径”:
文件或者文件夹。
File API 很多,需要掌握的是解决问题的方法,不需要背 API:

File home = new File("C:\\Users\\Administrator\\Desktop");
System.out.println(home.isAbsolute());
home.isFile();
home.isAbsolute();
home.isHidden();
home.exists();
System.out.println(home);

3. BufferedReader/BufferedWriter读写文本文件

为了提高字符流读写效率,引入了缓冲机制,对字符批量读写。
BufferedReader 和 BufferedWriter 各拥有 8192 个字符缓冲区。
读取文本文件时,会先把读入的字符数据放入缓冲区,然后从缓冲区中一次性读取到程序中。
写入字符数据时,先存储至缓冲区,然后从缓冲区中一次性写出到目的地。
所以可以使用 java.io.BufferedReader/java.io.BufferedWriter 代替之前的 InputStream/OutputStream。
(Java Examples- BufferedReader and BufferedWriter)

二、NIO

NIO 是新的IO,来自Java7,是非阻塞的(Non-blocking) IO。
NIO 的 Path 就是旧版本的 FIle。
旧的 IO 是基于流的,优点是抽象良好,但缺点就是慢,一切操作只能一个字节一个字节。
NIO 是基于块的,读写多个块可以不按照顺序。
关于 NIO 只需要学习最常用的一个 Files 工具类,重要的方法有 readAllLines/write等,其他的以后用到再搜。

1. 缓冲

假如缓冲区大小是 1MB,则一次性攒够一百万个字节,然后一次性写入,避免了 CPU 频繁的等待硬盘寻址的时间。

2. 并发(多线程)

即同时向多个块读写数据

三、不要重复发明轮子(生产场景中)

public static String readFile(File  file) throws IOException {
    return FileUtils.readFileToString(file, Charset.defaultCharset());
}

public static String inputStreamToString(InputSream is) {
    return IOUtils.toString(is, Charset.defaultCharset());
}

四、IO实战

1. 使用多种方法读写文件

package com.github.hcsp.io;

import org.apache.commons.io.FileUtils;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class FileAccessor {

    /**
     * 使用最原始的 FileInputStream,一个字符一个字符地读取
     *
     * @param file 待读取的文件
     * @return 将文件按行分割后的 List<String>
     * @throws IOException 如果读取时报错,比如没有权限,文件不存在等
     */
    public static List<String> readFile1(File file) throws IOException {
        List<String> result = new ArrayList<>();
        InputStream is = new FileInputStream(file);
        String line = "";
        while (true) {
            int b = is.read();
            if (b == -1) {
                break;
            }
            if (b != '\n') {
                line = String.join("", line, Character.toString((char) b));
            } else {
                result.add(line);
                line = "";
            }
        }
        return result;
    }

    /**
     * 使用 BufferedReader 一行一行地读取
     *
     * @param file 待读取的文件
     * @return 将文件按行分割后的 List<String>
     * @throws IOException 如果读取时报错,比如没有权限,文件不存在等
     */
    public static List<String> readFile2(File file) throws IOException {
        List<String> result = new ArrayList<>();
        FileReader fr = new FileReader(file);
        BufferedReader br = new BufferedReader(fr);
        while (true) {
            String line = br.readLine();
            if (line == null) {
                break;
            }
            result.add(line);
        }
        br.close();
        return result;
    }

    /**
     * 使用第三方库 Apache Commons IO 的 FileUtils 读取
     *
     * @param file 待读取的文件
     * @return 将文件按行分割后的 List<String>
     * @throws IOException 如果读取时报错,比如没有权限,文件不存在等
     */
    public static List<String> readFile3(File file) throws IOException {
        return FileUtils.readLines(file);
    }

    /**
     * 使用 Java 7+ 引入的 Files.write() 方法F
     *
     * @param file 待读取的文件
     * @return 将文件按行分割后的 List<String>
     * @throws IOException 如果读取时报错,比如没有权限,文件不存在等
     */
    public static List<String> readFile4(File file) throws IOException {
        return Files.readAllLines(file.toPath());
    }

    /**
     * 使用最原始的 FileOutputStream,一行一行地写入
     *
     * @param lines 待写入的数据
     * @param file  用来接收写入操作的文件
     * @throws IOException 如果读取时报错,比如没有权限,文件不存在等
     */
    public static void writeLinesToFile1(List<String> lines, File file) throws IOException {
        OutputStream os = new FileOutputStream(file);
        for (String line : lines) {
            os.write(line.getBytes());
            os.write('\n');
        }
    }

    /**
     * 使用 BufferedWriter 一行一行地写入
     *
     * @param lines 待写入的数据
     * @param file  用来接收写入操作的文件
     * @throws IOException 如果读取时报错,比如没有权限,文件不存在等
     */
    public static void writeLinesToFile2(List<String> lines, File file) throws IOException {
        FileWriter fw = new FileWriter(file);
        BufferedWriter bw = new BufferedWriter(fw);
        for (String line : lines) {
            bw.write(line);
            bw.newLine();
        }
        bw.close();
    }

    /**
     * 使用第三方库 Apache Commons IO 的 FileUtils 写入
     *
     * @param lines 待写入的数据
     * @param file  用来接收写入操作的文件
     * @throws IOException 如果读取时报错,比如没有权限,文件不存在等
     */
    public static void writeLinesToFile3(List<String> lines, File file) throws IOException {
        FileUtils.writeLines(file, lines);
    }

    /**
     * 使用 Java 7+ 引入的 Files.write() 方法
     *
     * @param lines 待写入的数据
     * @param file  用来接收写入操作的文件
     * @throws IOException 如果读取时报错,比如没有权限,文件不存在等
     */
    public static void writeLinesToFile4(List<String> lines, File file) throws IOException {
        Files.write(file.toPath(), lines);
    }

    public static void main(String[] args) throws IOException {
        File projectDir = new File(System.getProperty("basedir", System.getProperty("user.dir")));
        File testFile = new File(projectDir, "target/test.txt");
        List<String> lines = Arrays.asList("AAA", "BBB", "CCC");
        writeLinesToFile1(lines, testFile);
        writeLinesToFile2(lines, testFile);
        writeLinesToFile3(lines, testFile);
        writeLinesToFile4(lines, testFile);

        System.out.println(readFile1(testFile));
        System.out.println(readFile2(testFile));
        System.out.println(readFile3(testFile));
        System.out.println(readFile4(testFile));
    }
}

2. 给定一个Github仓库名读取前n个Pull request并保存至csvFile指定的文件中(未使用SDK)

package com.github.hcsp.io;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;

public class Crawler {

    private static ArrayList<GitHubPullRequest> PULL_REQUESTS = new ArrayList<>();
    private static int numberLimitOfPR;

    /**
     * 给定一个仓库名,例如"golang/go",或者"gradle/gradle",读取前n个Pull request并保存至csvFile指定的文件中,格式如下:
     * number,author,title
     * 12345,blindpirate,这是一个标题
     * 12345,FrankFang,这是第二个标题
     *
     * @param repo    仓库名
     * @param n       前 n 个 Pull request
     * @param csvFile 用来存放 Pull request 信息的指定 csv 文件路径
     * @throws IOException 当写入出错时
     */
    public static void savePullRequestsToCSV(String repo, int n, File csvFile) throws IOException {

        numberLimitOfPR = n;
        int page = 1;
        getSpecifiedPageOfPullRequestsAndStoreWithinLimit(repo, page);
        while (true) {
            if (PULL_REQUESTS.size() < n) {
                getSpecifiedPageOfPullRequestsAndStoreWithinLimit(repo, ++page);
            } else {
                break;
            }
        }
        FileWriter fw = new FileWriter(csvFile);
        BufferedWriter bw = new BufferedWriter(fw);
        bw.write("number,author,title");
        bw.newLine();
        for (GitHubPullRequest pr : PULL_REQUESTS) {
            bw.write(pr.toString());
            bw.newLine();
        }
        bw.close();
        System.out.println(PULL_REQUESTS.size());
    }

    static class GitHubPullRequest {
        // Pull request的编号
        int number;
        // Pull request的标题
        String title;
        // Pull request的作者的GitHub id
        String author;

        GitHubPullRequest(int number, String title, String author) {
            this.number = number;
            this.title = title;
            this.author = author;
        }

        @Override
        public String toString() {
            return number + "," + author + "," + title;
        }
    }

    /**
     * 给定一个仓库名,例如"golang/go",或者"gradle/gradle",获取指定页的 Pull request 信息
     * 然后存够指定数量的 GitHubPullRequest 对象到 PULL_REQUESTS 中
     *
     * @param repo 仓库名
     * @param page 当前 Pull request 的页数
     * @throws IOException 当获取出错时
     */
    public static void getSpecifiedPageOfPullRequestsAndStoreWithinLimit(String repo, int page) throws IOException {
        Document doc = Jsoup.connect("https://github.com/" + repo + "/pulls" + "?page=" + page).get();
        ArrayList<Element> issues = doc.select(".js-issue-row");
        for (Element element : issues) {
            GitHubPullRequest pr = new GitHubPullRequest(
                    Integer.parseInt(element.attr("id").substring(6)),
                    element.select(".js-navigation-open").get(0).text(),
                    element.select(".muted-link").get(0).text()
            );
            if (PULL_REQUESTS.size() < numberLimitOfPR) {
                PULL_REQUESTS.add(pr);
            }
        }
    }

    public static void main(String[] args) throws IOException {
        savePullRequestsToCSV("golang/go", 40, new File("./temp.csv"));
    }

}

参考:
CPU:这个世界太慢了

上一篇 下一篇

猜你喜欢

热点阅读