File
类可用于表示 文件 和 目录 的信息,但不表示文件的内容。
InputStream Java标准库中提供的一个最基本的 输入流
,是一个抽象类,而不是接口。
FileInputStream
是InputStream的一个子类。FileInputStream就是从文件流中读取数据。
outputStream Java标准库中提供的一个最基本的 输出流
,也是一个抽象类。
FileOutputStream
可以从文件获取输出流。
public class CopyDemo {
public static void main(String[] args) throws IOException {
// 创建字节输入流对象
FileInputStream in = new FileInputStream("a.txt");
// 创建字节输出流对象
FileOutputStream out = new FileOutputStream("b.txt");
// 创建byte对象数组
byte[] by = new byte[1024];
// read() 最多读取 by.length 个字节
// 返回的是实际读取的个数
// 返回 -1 的时候表示读到 eof,即文件尾
int len = 0;
while ((len = in.read(by)) != -1) {
out.write(by, 0, len);
}
// 释放资源
in.close();
out.close();
}
}
Java I/O 使用 装饰者模式
来实现,以 InputStream 为例:
//传递的是一个对象
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("a.txt"));
编码就是将字符转换为字节,而解码就是将字节重新组合成字符。
如果编码和解码过程使用不同的编码方法那么就会出现乱码。
GBK
编码中,中文字符占两个字节,英文字符占一个字节UTF-8
编码中,中文字符占三个字节,英文字符占一个字节UTF-16be
编码中,中文和英文字符都占两个字节UTF-16be 中,be 指的是Big Endian ,大端
Java的内存编码使用 双字节编码 UTF-16be,这不是指Java 只支持这一种类型的编码格式,而是说char 这种类型使用 UTF-16be 进行编码。
char 类型占16位,两个字节,Java使用这种双字节编码 是为了让一个中文 或者英文字符都能使用一个 char来存储。
/*
* String 可以看成一个字符序列,
* 可以指定一个编码方式将它编码为字节序列,
* 也可以指定一个编码方式将一个字节序列解码为 String。
*/
public class Test {
public static void main(String[] args) {
//String -- byte[]
String str1 = "无敌敏";
byte[] by = str1.getBytes();
//byte[] -- String
String str2 = new String(by);
System.out.println(str2);
}
}
不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。
但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
InputStreamReader
:实现从字节流解码成字符流。OutputStreamWriter
:实现从字符流编码成字节流。将一个文本文件的内容读入到另一个文件中
public static void main(String[] args) throws IOException {
//封装数据源
BufferedReader in = new BufferedReader(new FileReader("a.txt"));
//封装目的地
BufferedWriter out = new BufferedWriter(new FileWriter("b.txt"));
String line = null;
//readLine() 一次读取一行数据
while((line=in.readLine()) != null) {
out.write(line);
// newLine() 写入一个行分隔符
out.newLine();
out.flush();
}
//关闭
in.close();
out.close();
}
序列化是指把一个 Java对象 变成二进制内容(字节序列),本质上就是一个byte[] 数组。
方便存储和传输 :序列化后可以把 byte[] 保存到文件中,或者把 byte[] 通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了。
序列化
:ObjectOutputStream.writeObject()反序列化
:ObjectInputStream.readObject()不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。
一个Java对象要能序列化,必须实现一个特殊的 java.io.Serializable
接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。(我们把这样的空接口称为“标记接口”)
private class A implements Serializable{
}
序列化和反序列化实例
public static void main(String[] args) throws IOException, ClassNotFoundException {
A a1 = new A(123, "abc");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("a.txt"));
objectOutputStream.writeObject(a1);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("a.txt"));
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;
Java 中的网络 支持:
InetAddress
:用于表示网络上的硬件资源,即 IP地址
URL
:统一资源定位符Sockets
(套接字):使用 TCP 网络协议实现网络通信Datagram
(数据报文):使用 UDP 网络协议实现网络通信同步 和 异步
同步
发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。
例如,普通B/S模式(同步):提交请求 -> 等待服务器才处理 -> 处理完毕返回
这期间客户端浏览器不能干任何事情。
异步
当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。
例如,Ajax请求(异步):请求通过事件触发 -> 服务器处理(此时浏览器仍然可以做其他事情)-> 处理完毕。
同步和异步的重点在于多个任务或事件发生时,一个事件或任务的执行是否会导致整个流程的暂时等待。
一般来说,可以通过 多线程的方式 来实现异步,但二者并不能画等号,异步只是宏观上的一个模式,而采用多线程来实现异步只是一种手段,通过多进程的方式也能实现异步。
阻塞 和 非阻塞
阻塞
当某个任务 或 事件在执行过程中,它发出一个请求操作,但是由于该请求操作需要的条件不满足,那么就会一直等待,直到条件满足。
阻塞调用是指,调用结果返回前,当前线程会被挂起(线程进入非可执行状态,在此状态下,CPU不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果后才会返回。
非阻塞
当某个任务 或 事件在执行过程中,它发出一个请求操作,如果该请求操作需要的条件不满足,会立即返回一个标志信息告知条件不满足,不会一直在那等待。
阻塞与非阻塞的关键在于,当发出一个请求操作时,如果条件不满足,是会一直等待,还是返回一个标志信息。
阻塞IO 和 非阻塞IO
当用户线程发起一个IO请求操作,内核会去查看要读取的数据是否就绪
对于阻塞IO来说,如果数据没有就绪,则会一直在此等待,直到数据就绪;
对于非阻塞IO来说,如果数据没有就绪,则会返回一个标志信息,告知用户线程当前数据没有就绪。
当数据就绪之后,便将数据拷贝到用户线程,这样就完成了一个完成的IO读请求操作。
一个完整的 IO读请求操作包括两个阶段:
阻塞IO 与 非阻塞IO 的区别在于第一阶段,如果数据没有就绪,在查看数据是否就绪的过程中,是一直等待,还是直接返回一个标志信息。
同步IO 和 异步IO
同步IO
如果一个线程请求进行IO操作,在IO操作完成之前,该线程会被阻塞。
异步IO
如果一个线程请求进行IO操作,IO操作不会导致请求线程被阻塞。
同步IO和异步IO模型是针对 用户线程
和 内核
的交互来说的,同步IO 和 异步IO的关键区别在于:数据拷贝阶段,是由用户线程完成还是内核完成。(异步IO的两个阶段都是由内核自动完成,所以说,异步IO需要操作系统的底层支持)
在读写中会发生的一种阻塞现象。
当用户线程发出IO请求之后,内核会去查看数据是否就绪
,如果没有就绪,就会等待数据就绪,此时用户线程会处于等待状态,用户线程交出CPU。当数据就绪之后,内核将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除阻塞状态。
当用户线程发起一个read
操作之后,并不需要等待,而是马上就能得到一个结果。如果结果是 error,就代表数据还没有准备好,于是,它可以再次发送read操作。一旦内核中的操作准备好了,并且又再次收到了用户线程的请求,则会马上将数据拷贝到用户线程中,然后返回。
在非阻塞IO模型中,用户线程需要不断地去询问内核数据是否就绪,也就是说,非阻塞IO不会交出CPU,而是一直占用。
在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。
在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。
异步IO模型才是最理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。
在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用IO函数进行具体的读写。
前面四种IO模型实际上都属于同步IO,只有最后一种是真正的异步IO,因为无论是多路复用IO还是信号驱动模型,IO操作的第2个阶段都会引起用户线程阻塞,也就是内核进行数据拷贝的过程都会让用户线程阻塞。
BIO里用户最关心“我要读”,NIO里用户最关心”我可以读了”,在AIO模型里用户更需要关注的是“读完了”。
Java NIO:浅析I/O模型
CYC