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. 并发(多线程)
即同时向多个块读写数据
三、不要重复发明轮子(生产场景中)
- FileUtils
- IOUtils
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:这个世界太慢了