I/O的方式通常分为几种,同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。
基于不同的IO抽象模型和交互方式,可以对IO进行简单区分:
* 传统的IO基于流模型实现,交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。也称为BIO。
* 在java1.4中引入了NIO框架,提供了Channel、Selector、Buffer等新的抽象,可以构建多路复用的、同步非阻塞IO。
* 在Java7中,NIO有了进一步的改进,也就是NIO2,引入了异步非阻塞IO方式,也叫AIO(Asynchronous IO)。异步IO操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。
BIO
在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端会线程会等待请求结束后才继续执行。
IO流的分类
根据操作对象可分为:
磁盘操作:File
字节操作:InputStream 和OutputStream
字符操作:Reader 和Writer
对象操作:Serializable
网络操作:Socket
根据处理数据类型的不同分为:字符流和字节流,字节流一次读入或读出是8位二进制,字符流一次读入或读出是16位二进制。
根据数据流向不同分为:输入流和输出流,输入流只能进行读操作,输出流只能进行写操作。
结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。
按照流是否直接与特定的地方(如磁盘、内存、设备等)相连,分为节点流和处理流两类:
1)节点流:直接与数据源相连,读入或读出。
常用节点流:
父 类:InputStream 、OutputStream、 Reader、Writer
文 件:FileInputStream 、FileOutputStrean 、FileReader 、FileWriter 文件进行处理的节点流
数 组:ByteArrayInputStream、ByteArrayOutputStream、 CharArrayReader 、CharArrayWriter对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)
字符串:StringReader、 StringWriter 对字符串进行处理的节点流
管 道:PipedInputStream 、PipedOutputStream、PipedReader 、PipedWriter对管道进行处理的节点流
2)处理流:直接使用节点流,读写不方便,为了更快的读写文件,才有了处理流。处理流和节点流一块使用,在节点流的基础上,再套接一层,套接在节点流上的就是处理流。
常用处理流:
缓冲流:BufferedInputStrean 、BufferedOutputStream、 BufferedReader、BufferedWriter增加缓冲功能,避免频繁读写硬盘。可修饰所有从节点流父类继承的对应节点流。
转换流:InputStreamReader 、OutputStreamReader实现字节流和字符流之间的转换。
数据流:DataInputStream 、DataOutputStream等-提供将基础数据类型写入到文件中,或者读取出来。
什么时候使用转换流?
1)源或者目的对应的设备是字节流,但是操作的却是文本数据,可以使用转换作为桥梁。提高对文本操作的便捷。
2)一旦操作文本涉及到具体的指定编码表时,必须使用转换流。
IO流的交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,他们之间的调用是可靠的线性顺序。
BIO常见操作示例:
1)磁盘操作
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);
}
}
从 Java7 开始,可以使用 Paths 和 Files 代替 File。
2)字节操作
实现文件复制
public static void copyFile(String src, String dist) throwsIOException {
FileInputStream in = newFileInputStream(src);
FileOutputStream out =new FileOutputStream(dist);
byte[] buffer = newbyte[20 * 1024];
int cnt;
// read()最多读取 buffer.length 个字节
//返回的是实际读取的个数
//返回 -1 的时候表示读到 eof,即文件尾
while ((cnt =in.read(buffer, 0, buffer.length)) != -1) {
out.write(buffer, 0,cnt);
}
in.close();
out.close();
}
装饰者模式
Java I/O 使用了装饰者模式来实现。以 InputStream 为例,
InputStream 是抽象组件;
FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作;
FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。
实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。
FileInputStream fileInputStream = new FileInputStream(filePath);
BufferedInputStream bufferedInputStream = newBufferedInputStream(fileInputStream);
DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。
3)字符操作
编码与解码
编码就是把字符转换为字节,而解码是把字节重新组合成字符。
如果编码和解码过程使用不同的编码方式那么就出现了乱码。
GBK 编码中,中文字符占 2 个字节,英文字符占 1 个字节;
UTF-8 编码中,中文字符占 3 个字节,英文字符占 1 个字节;
UTF-16be 编码中,中文字符和英文字符都占 2 个字节。
UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF-16le,le 指的是 Little Endian,也就是小端。
Java 的内存编码使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。
String 的编码方式
String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。
String str1 = "中文";
byte[] bytes = str1.getBytes("UTF-8");
String str2 = new String(bytes, "UTF-8");
System.out.println(str2);
在调用无参数 getBytes() 方法时,默认的编码方式不是 UTF-16be。双字节编码的好处是可以使用一个 char 存储中文和英文,而将 String 转为 bytes[] 字节数组就不再需要这个好处,因此也就不再需要双字节编码。getBytes() 的默认编码方式与平台有关,一般为 UTF-8。
byte[] bytes = str1.getBytes();
Reader 与Writer
不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
InputStreamReader实现从字节流解码成字符流;
OutputStreamWriter实现字符流编码成为字节流。
实现逐行输出文本文件的内容
public static void readFileContent(String filePath) throwsIOException {
FileReader fileReader =new FileReader(filePath);
BufferedReaderbufferedReader = new BufferedReader(fileReader);
String line;
while ((line =bufferedReader.readLine()) != null) {
System.out.println(line);
}
//装饰者模式使得 BufferedReader 组合了一个 Reader 对象
//在调用 BufferedReader 的 close() 方法时会去调用 Reader 的 close() 方法
//因此只要一个 close() 调用即可
bufferedReader.close();
}
4)对象操作
序列化
序列化就是将一个对象转换成字节序列,方便存储和传输。
序列化:ObjectOutputStream.writeObject()
反序列化:ObjectInputStream.readObject()
不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。
Serializable
序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。
public static void main(String[] args) throws IOException,ClassNotFoundException {
A a1 = new A(123,"abc");
String objectFile = "file/a1";
ObjectOutputStreamobjectOutputStream = new ObjectOutputStream(new FileOutputStream(objectFile));
objectOutputStream.writeObject(a1);
objectOutputStream.close();
ObjectInputStreamobjectInputStream = new ObjectInputStream(new FileInputStream(objectFile));
A a2 = (A)objectInputStream.readObject();
objectInputStream.close();
System.out.println(a2);
}
private static class A implements Serializable {
private int x;
private String y;
A(int x, String y) {
this.x = x;
this.y = y;
}
@Override
public String toString(){
return "x =" + x + " " + "y =" + y;
}
}
transient
transient 关键字可以使一些属性不会被序列化。
ArrayList 中存储数据的数组 elementData 是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。
private transient Object[] elementData;
5)网络操作
Java中的网络支持:
InetAddress:用于表示网络上的硬件资源,即IP地址;
URL:统一资源定位符;
Sockets:使用TCP协议实现网络通信;
Datagram:使用UDP协议实现网络通信。
InetAddress
没有公有的构造函数,只能通过静态方法来创建实例。
InetAddress.getByName(String host);
InetAddress.getByAddress(byte[] address);
URL
可以直接从 URL 中读取字节流数据。
public static void main(String[] args) throws IOException {
URL url = newURL("http://www.baidu.com");
/*字节流*/
InputStream is =url.openStream();
/*字符流*/
InputStreamReader isr =new InputStreamReader(is, "utf-8");
/*提供缓存功能*/
BufferedReader br = newBufferedReader(isr);
String line;
while ((line =br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
Sockets
ServerSocket:服务器端类
Socket:客户端类
服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。
Datagram
DatagramSocket:通信类
DatagramPacket:数据包类