一、Java中的IO的分类
- 磁盘的操作: File
- 字节的操作:InputStream 和 OutputStream
- 字符的操作:Reader 和 Writer
- 对象的操作:Serializable
- 网络的操作:Socket,如服务端通过输入流读取客户端发送的请求信息 和 客户端通过输出流向服务端请求信息。
- 非阻塞IO:NIO
Java中IO流的类结构图:
二、磁盘的操作
File类用于表示文件和目录的信息,但是它不表示文件的内容。
递归地列出一个目录下地所有文件:
// 递归 打印一个目录下面的所有文件 java7开始,可用Paths代替File
public static void listAllFiles(File dir) {
if(dir == null || !dir.exists()) return;
if(dir.isFile()) {
System.out.println(dir.getName());
return ;
}
for(File file: dir.listFiles()) {
listAllFiles(file);
}
}
三、字节的操作
读取数据到字节数组中:public int read(byte b[]) throws IOException.最常用方法
实现文件复制操作
每次读取一个字节,实现文件复制
// 每次读取一个字节
public static void one_byte(File src, File dist) throws IOException {
FileInputStream fileInputStream = new FileInputStream(src);
FileOutputStream fileOutputStream = new FileOutputStream(dist);
int byte_num = 0;
// read() 返回的是实际读取的个数
// 返回 -1 表示读到文件尾部
while ((byte_num = fileInputStream.read()) != -1) {
fileOutputStream.write(byte_num);
}
fileInputStream.close();
fileOutputStream.close();
}
一次性读取多个字节, 实现文件复制
// 一次读写多个字节
public static void more_byte(File src, File dist) throws IOException {
FileInputStream fileInputStream = new FileInputStream(src);
FileOutputStream fileOutputStream = new FileOutputStream(dist);
// 每次读取 50byte
byte[] bytes = new byte[50];
int byte_num = 0;
// read() 最多读取 bytes.length 个字节
// 返回的是实际读取的字节数
// 在这里,每次 读取/写入 50byte
while ((byte_num = fileInputStream.read(bytes, 0, bytes.length - 1)) != -1) {
fileOutputStream.write(bytes);
}
fileInputStream.close();
fileOutputStream.close();
}
实例化具有缓冲功能的字节流对象时,只需要在FileInputStream对象上再套一层BufferedInputStream对象即可。
FileInputStream fileInputStream = new FileInputStream( filepath );
BufferedInputStream buffer = new BufferedInputStream( fileInputStream );
四、字符的操作
编码与解码
编码:将字符转化为字节
解码:将字节转化为字符
Java中的编码方式:
GBK, 中文字符2字节,英文字符1字节
UTF-8, 中文字符3字节,英文字符1字节
UTF-16eb, 中文字符和英文字符均占2字节
String的编码方式
String 可以看成是一个字符序列,可以指定一个编码方式将它编码为字节序列,亦可指定解码方式,将字节序列解码为String
String str = new String("周五");
byte[] bytes = str.getBytes("GBK");
String re = new String(bytes, "GBK");
System.out.print(re);
字符操作 Reader 和 Writer
- InputStreamReader 实现从字节流解码解码成字符流
- OutputStreamWriter 实现从字符流编码成字节流
使用FileInputStream 和 InputStreamReader 读取文件内容
// 读取文件内容
public static void get_content(File file) throws IOException {
// 使用 FileInputStream 将文件读取到字节流
FileInputStream fileInputStream = new FileInputStream(file);
// 使用 InputStreamReader 从字节流转化为字符流
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "GBK");
// 使用缓存 BufferReader
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line = null;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
// 装饰者模式是的BufferedReader 组合了一个 Reader 对象
// 在调用 BufferedRead 的close() 方法时会去调用 Reader 的 close() 方法
bufferedReader.close();
inputStreamReader.close();
}
五、对象的序列化和反序列化
序列化:所谓序列化,就是将一个对象转化成字节序列,用于存储和运输。 注意,不会对静态变量进行序列化,静态变量属于类的状态。
反序列化:反序列化,自然就是将字节序列转化为原对象
序列化: ObjectOutputStream.writeObject()
反序列化:ObjectInputStream.readObject()
序列化类的实现,Serializable接口
Serializable接口只是一个标准,没有任何方法需要实现。
序列化和反序列化的使用示例:
private static A a = new A(666, "Hello World!");
private static String objectFile = "obj.txt";
// 对象的序列化 Serializable
public static void ojb_to_byte() throws IOException{
FileOutputStream fileOutputStream = new FileOutputStream(objectFile);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(a);
// 将对象转化为字节序 写入文件
objectOutputStream.close();
}
// 对象反序列化
public static void byte_to_obj() throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream(objectFile);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
// 使用ObjectInputStream 从文件读取字节序,再转化为对象
A a2 = (A) objectInputStream.readObject();
objectInputStream.close();
System.out.println(a2.str);
}
// 一个类
private static class A implements Serializable {
private int x;
private String str;
A(int x, String str) {
this.x = x;
this.str = str;
}
}
补充:transient 关键字, transient可以使一些属性不被序列化。
六、通道和缓冲
1. 通道
通道Channel是对原IO包中的流的模拟,可以通过它读取和写入数据。
通道与流:流只能在一个方向上移动,而通道是双向的,可以读、写或者同时用于读写。
通道的类型:
- FileChannel: 从文件中读写数据
- DatagramChannel: 通过UDP读写网络中的数据
- SocketChannel: 通过TCP读写网络中的数据
- ServerSocketChannel: 可以监听新进来的TCP连接,对每个新连接都创建一个ServerSocketChannel
2. 缓冲区
发送给一个通道的所有数据均要首先放到缓冲区,同样,从通道中读取任何数据也要先读到缓冲区中。
不会直接对通道进行读写数据,而是先经过缓冲区。
Java中7类缓冲区:
ByteBuffer、ShortBuffer、IntBuffer、CharBuffer、FloatBuffer、DoubleBuffer、LongBuffer
缓冲区实质上是一个数组。
缓冲区的状态变量:
- capacity : 最大容量
- position: 当前已经读写的字节数
- limit: 还可以读写的字节数
// 1. 创建容量大小为 10 的ByteBuffer
ByteBuffer bf = ByteBuffer.allocate(10);
此时position = 0, limit = capacity = 10。
// 2. 往缓冲区中 put() 5个字节
bf.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'o');
此时,position = 5, limit = capacity = 10;
// 3.调用 flip() 方法,切换为读就绪状态
bf.flip();
在缓冲区的数据写到通道之前,需要调用flip() 方法,该方法将limit 设置为当前position,并将position设置为0
// 4. 从缓冲区中读取两个元素
从缓冲区中读取2个字节到输出缓冲中,此时position = 2
// 5. 最后调用clear() 方法清空缓冲区,此时 position 和 limit 都被设置为初始值。
七、非阻塞IO
NIO也叫做非阻塞IO,NIO在网络通信中的非阻塞特性应用广泛。
IO与NIO最重要的区别是数据打包和传送的方式,IO以流的方式处理数据,而NIO是以块的形式处理数据。
面向流的IO一次处理一个字节数据,而面向块的NIO一次处理一个数据块。
1. 阻塞IO通信模型
阻塞I/O在调用InputStream.read()方法时是阻塞的,它会一直等到数据到来时(或超时)才会返回;同样,在调用ServerSocket.accept()方法时,也会一直阻塞到有客户端连接才会返回,每个客户端连接过来后,服务端都会启动一个线程去处理该客户端的请求。阻塞I/O的通信模型示意图如下:
阻塞IO的缺点:
- 客户端过多时,会创建大量处理线程。占用栈空间和CPU
- 阻塞可能带来频繁上下文切换,且大部分上下文切换可能是无意义的。
2. Java NIO原理及通信模型
- 由一个专门的线程处理所有的IO事件,并负责并发。
- 事件驱动机制:事件到的时候触发,而不是同步去监视事件。
- 线程通讯:线程之间通过 wait, notify 等方式通讯。保证上下文切换都是有意义的,减少无意义切换。
工作原理图:
进行通信时,客户端和服务端各自维护一个管道的对象,即选择器Selector,而Selector通过轮询的方式去监听多个通道Channel上的事件,从而让一个线程处理多个事件。
通过配置监听的通道Channel为非阻塞,当Channel上的IO事件还未到达时,就不会进入阻塞状态,而是继续轮询其他Channel,找到IO事件已经到达的Channel执行。
只有Channel才能配置非阻塞,而FileChannel不能。
参考博客:
https://blog.youkuaiyun.com/shimiso/article/details/24990499