IO笔记
一、文件
1.1 概念
文件是保存数据的地方
文件流:文件在程序中是以流的形式来操作的

1.2 常用操作
创建文件对象相关构造器和方法:
new File(String pathName); // 根据路径创建一个File对象
new File(File parent, String child); // 根据父目录文件(文件的路径)+子路径创建
new File(String parent, String child); // 根据父目录+子路径创建
file.createNewFile(); // 创建新文件
// 方式一
public void create01() {
String filePath = "e:\\new1.txt";
File file = new File(filePath);
try {
file.createNewFile();
System.out.println("文件创建成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
// 方式二
// e:\new1.txt
public void create02() {
File parentFile = new File("e:\\");
String fileName = "new2.txt";
File file = new File(parentFile, fileName);
try {
file.createNewFile();
System.out.println("文件创建成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
// 方式三
public void create03() {
String parentPath = "e:\\";
String fileName = "new3.txt";
File file = new File(parentPath, fileName);
try {
file.createNewFile();
System.out.println("文件创建成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
获取文件的相关信息
getName, getAbsolutePath, getParent, length, exists, isFile, isDirectory
// 获取文件的信息
public void info() {
// 先创建文件对象
File file = new File("e:\\new1.txt");
// 调用响应的方法,得到信息
String fileName = file.getName(); // 文件名
String absolutePath = file.getAbsolutePath(); // 绝对路径
String parent = file.getParent(); // 文件父级目录
long length = file.length(); // 文件大小(字节)
boolean exists = file.exists(); // 文件是否存在
boolean file1 = file.isFile(); // 是不是一个文件
boolean directory = file.isDirectory(); // 是不是一个目录
}
目录操作和文件删除
mkdir() // 创建一级目录
mkdirs // 创建多级目录
delete() // 删除空目录或文件
// 判断 e:\\new1.txt 文件是否存在,如果存在就删除
public void m1() {
String filePath = "e:\\new1.txt";
File file = new File(filePath);
if (file.exists()) {
if (file.delete()) {
System.out.println("删除成功!");
} else {
System.out.println("删除失败!");
}
} else {
System.out.println("该文件不存在!");
}
}
// 判断 e:\\demo 目录是否存在,如果存在就删除
public void m2() {
String filePath = "e:\\demo";
File file = new File(filePath);
if (file.exists()) {
if (file.delete()) {
System.out.println("删除成功!");
} else {
System.out.println("删除失败!");
}
} else {
System.out.println("该目录不存在!");
}
}
// 判断 e:\\demo\\a\\b\\c 目录是否存在,如果存在就提示已经存在,否则就创建
public void m3() {
String directoryPath = "e:\\demo\\a\\b\\c";
File file = new File(directoryPath);
if (file.exists()) {
System.out.println("目录存在!");
} else {
if (file.mkdirs()) {
System.out.println("创建成功!");
} else {
System.out.println("创建失败!");
}
}
}
二、IO流原理及流的分类




三、节点流和处理流
处理流就是对节点流的包装!!(装饰器模式)




// System 类的 public final static InputStream in = null
// System.in 编译类型:InputStream
// System.in 运行类型:BufferedInputStream
System.out.println(System.in.getClass());
// System 类的 public final static PrintStream out = null
// System.out 编译类型:PrintStream
// System.out 运行类型:PrintStream
System.out.println(System.out.getClass());
打印流只有输出流,没有输入流

四、输入流
4.1 InputStream


4.1.1 FileInputStream

/**
* 单个字节的读取,效率低
* -> 使用 read(byte[] b) 优化
*/
public void readFile01() {
String filePath = "e:\\hello.txt";
int readData = 0;
FileInputStream fileInputStream = null;
try {
// 创建 FileInputStream 对象,用于读取文件
fileInputStream = new FileInputStream(filePath);
// 从该输入流读取一个字节数据
// 如果返回 -1,表示读取完毕
while ((readData = fileInputStream.read()) != -1) {
System.out.print((char) readData); // 转成char显示
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭文件流,释放资源
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 使用 read(byte[] b) 优化
*/
public void readFile02() {
String filePath = "e:\\hello.txt";
// 字节数组
byte[] buf = new byte[8]; // 一次读取8个字节
int readLen = 0;
FileInputStream fileInputStream = null;
try {
// 创建 FileInputStream 对象,用于读取文件
fileInputStream = new FileInputStream(filePath);
// 从该输入流读取一个字节数据
// 如果返回 -1,表示读取完毕
// 如果读取正常,返回实际读取的字节数
while ((readLen = fileInputStream.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen)); // 转成char显示
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭文件流,释放资源
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.1.2 BufferedInputStream
处理流

处理流属性中,封装了Reader对象,即封装了一个节点流,可实现多种数据类型。该节点流可以是任意的,只要是Reader的子类就行。

4.1.3 ObjectInputStream
序列化


public static void main(String[] args) throws IOException, ClassNotFoundException {
// 指定反序列化的文件
String filePath = "e:\\data.txt";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
// 读取
// 1.读取(反序列化)的顺序,需要和保存数据(序列化)的顺序一致
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
Object dog = ois.readObject();
System.out.println(dog);
// 关闭流
ois.close();
}

4.2 Reader
4.2.1 FileReader


4.2.2 BufferedReader
处理流

关闭流时,只需关闭包装流即可,会自动关闭节点流,源码如下:

public static void main(String[] args) throws Exception {
String filePath = "e:\\a.java";
// 创建bufferedReader
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
// 读取
String line;
// 说明:
// 1.bufferedReader.readLine(); 按行读取
// 2.当返回空时,表示读取完毕
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
// 关闭流, 只需要关闭 bufferedReader就行了,底层会自动关闭节点流
bufferedReader.close();
}
4.2.3 InputStreamReader
转换流:将字节流 包装为 字符流

字节流可以指定编码方式,因此可以先用字节流,然后转成字符流。


public static void main(String[] args) throws IOException {
String filePath = "e:\\a.txt";
// 1.FileInputStream 转成 InputStreamReader
InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
// 2.把 InputStreamReader 传入 BufferedReader
BufferedReader br = new BufferedReader(isr);
// 3.读取
String s = br.readLine();
System.out.println("读取内容:" + s);
// 4.关闭外层流
br.close();
}
五、输出流
5.1 OutputStream
5.1.1 FileOutputStream

/**
* 使用 FileOutputStream 将数据写入文件中
* 如果文件不存在,则创建文件
*/
public void writeFile() {
// 创建 FileOutputStream 对象
String filePath = "e:\\a.txt";
FileOutputStream fileOutputStream = null;
try {
// 得到输出流对象
// 说明:
// 1.new FileOutputStream(filePath) 当写入内容时,会覆盖原先内容
// 2.new FileOutputStream(filePath, true) 追加文件内容
fileOutputStream = new FileOutputStream(filePath);
// 写入一个字节
fileOutputStream.write('a');
// 写入字符串
String str = "hello,world";
// str.getBytes() 可以把字符串===> 字节数组
fileOutputStream.write(str.getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
5.1.2 BufferedOutputStream
处理流

5.1.3 ObjectOutputStream
反序列化

public static void main(String[] args) throws IOException {
// 序列化后,保存的文件格式,不是指定的后缀格式,而是按照他的格式保存
String filePath = "e:\\data.txt";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
// 序列化数据到 e:\data.txt
oos.write(100); // int -> Integer(实现了 Serializable)
oos.writeBoolean(true); // boolean -> Boolean
oos.writeChar('a'); // char -> Character
oos.writeDouble(9.5); // double -> Double
oos.writeUTF("孔祥宇"); // String
// 保存一个dog对象
oos.writeObject(new Dog("旺财", 10));
oos.close();
System.out.println("数据保存完毕(序列化形式)");
}
5.1.4 PrintStream

public static void main(String[] args) throws IOException {
PrintStream out = System.out;
// 在默认情况下,PrintStream 输出位置是 标准输出,即显示器
/*
public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
}
*/
out.print("hello, john");
// 因为 print底层使用的是 write(),所以我们可以直接调用 write进行输出
out.write("孔祥宇, 你好".getBytes());
out.close();
// 我们可以去修改打印流的输出位置
// 1.修改为到 e:\\a.txt
// 2."hello, 孔祥宇" 就会输出到 e:\a.txt
// 3.public static void setOut(PrintStream out) {
// checkIO();
// setOut0(out); // native方法,修改了out
// }
System.setOut(new PrintStream("e:\\a.txt"));
System.out.println("hello, 孔祥宇");
}
5.2 Writer
5.2.1 FileWriter


注意:如果最终没有关闭(close)或者刷新(flush),写入不到指定文件!
close() 和 flush() 方法中关键的写操作

5.2.2 BufferedWriter
处理流

public static void main(String[] args) throws IOException {
String filePath = "e:\\ok.txt";
// 创建BufferedWriter对象
// new FileWriter(filePath, true) 追加方式写入
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
bufferedWriter.write("hello, 孔祥宇");
// 插入一个换行
bufferedWriter.newLine();
// 关闭流
bufferedWriter.close();
}
5.2.3 OutputStreamWriter
实现将OutputStream(字节流) 包装成 Writer(字符流)

public static void main(String[] args) throws IOException {
String filePath = "e:\\a.txt";
String charset = "gbk";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charset);
osw.write("hi, 孔祥宇");
// 关闭流
osw.close();
System.out.println("按照 " + charset + " 保存文件成功!");
}
5.2.4 PrintWriter

PrintWriter 需要close() 或者 flush() 才能写入
public static void main(String[] args) throws IOException {
// PrintWriter writer = new PrintWriter(System.out);
PrintWriter writer = new PrintWriter(new FileWriter("e:\\a.txt"));
writer.print("hello, world");
// PrintWriter 需要close() 或者 flush() 才能写入
writer.close();
}
六、Properties类
传统方法读取 xxx.properties 文件
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("src\\mysql.properties"));
String line = "";
while ((line = br.readLine()) != null) {
String[] split = line.split("=");
System.out.println(split[0] + " 值是:" + split[1]);
}
br.close();
}
使用 Properties 类


public static void main(String[] args) throws IOException {
// 使用Properties类读取mysql.properties文件
// 1.创建Properties对象
Properties properties = new Properties();
// 2.加载指定配置文件
properties.load(new FileReader("src\\mysql.properties"));
// 3.把k-v显示到控制台
properties.list(System.out);
// 4.根据key 获取对应的值
String user = properties.getProperty("user");
String pwd = properties.getProperty("pwd");
System.out.println("用户名=" + user);
System.out.println("密码=" + pwd);
}
使用 Properties类添加key-value到新文件 mysql2.properties
public static void main(String[] args) throws IOException {
// 使用Properties类 创建配置文件,修改配置文件
Properties properties = new Properties();
// 创建
// 如果不存在key,就是创建,否则就是修改
properties.setProperty("charset", "utf8");
properties.setProperty("user", "汤姆"); // 中文保存的 unicode编码
properties.setProperty("pwd", "abc111");
// 将k-v存储到文件中
properties.store(new FileOutputStream("src\\mysql2.properties"), null);
System.out.println("保存配置文件成功!");
}
使用 Properties类完成对 mysql2.properties 的读取,并修改某个key-value
// 如果不存在key,就是创建,否则就是修改
properties.setProperty("charset", "utf8");
7、IO模型
I/O描述了计算机系统与外部设备之间的通信过程。
当应用程序发起I/O调用后,会经历两个步骤:
- 内核等待I/O设备准备数据
- 内核将数据从内核空间拷贝到用户空间
常见的I/O模型:
- BIO:同步阻塞
- NIO:同步非阻塞
- AIO:异步非阻塞
BIO:应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。
NIO:应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的这段时间,线程依然是阻塞的。一直进行 read 调用轮询数据十分耗费资源。出现IO多路复用模型。线程会首先发起 select 调用,查询数据是否准备好,等内核准备好数据,用户再发起 read 调用。
所以,non blocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有:
- 等待数据准备就绪 (Waiting for the data to be ready) 「非阻塞」
- 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process) 「阻塞」
AIO:基于事件和回调机制,应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
因此对异步IO模型来说:
- 等待数据准备就绪 (Waiting for the data to be ready) 「非阻塞」
- 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process) 「非阻塞」
select、poll 和 epoll 区别
select:
- 时间复杂度O(n),当有I/O事件时,无差别轮询所有流,确定具体哪个流
poll:
- 时间复杂度O(n),依次查询每个文件对象的设备状态
- 基于链表存储,因此没有最大连接数限制
epoll:
- 时间复杂度O(1),不用轮询,基于事件,会通知我们哪个流发生了怎样的I/O事件通知我们

浙公网安备 33010602011771号