一、简介
Java对数据的操作是通过流的方式,通过IO流来处理设备之间的数据传输,上传和下载
二、说明
Java 的 IO 操作有以下几类
- 字节IO:InputStream 和 OutputStream
- 字符IO:Reader 和 Writer
- 对象IO:Serializable
- 磁盘IO:File
- 网络IO:Socket
- 非阻塞IO:NIO
三、使用
字节IO
采用装饰者模式实现
- InputStream:字节输入流,抽象类
- OutputStream:字节输出流,抽象类
- FileInputStream:文件输入流
- BufferedInputStream:字节缓冲输入流
示例
public class ByteIO {
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(Paths.get("E:", "test.txt").toString()));
byte[] bs = new byte[1024];
int len = 0;
while ((len = bis.read(bs)) != -1) {
System.out.print(new String(bs, 0, len));
}
bis.close();
}
}
字符IO
编码是把字符转换为字节,解码是把字节重新组合成字符。
不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。
但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
- Reader:读取字符流
- Writer:写入字符流
- InputStreamReader:实现从字节流解码成字符流;
- BufferedReader:字符缓冲流
示例
public class CharIO {
public static void main(String[] args) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new FileReader(Paths.get("E:", "test.txt").toString()));
String line;
while (true) {
if (!((line = bufferedReader.readLine()) != null)) break;
System.out.println(line);
}
bufferedReader.close();
}
}
对象IO
- 序列化是将一个对象转换成字节序列,方便存储和传输
- 它不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。
- transient 关键字可以使一些属性不会被序列化
- ObjectInputStream:对象输入流
- ObjectOutputStream:对象输出流
示例
public class ObjectIO {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person1 = new Person("赵士杰", "男", 23);
Path path = Paths.get("D:", "test.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(path.toFile()));
objectOutputStream.writeObject(person1);
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(path.toFile()));
System.out.println(objectInputStream.readObject());
objectOutputStream.close();
objectInputStream.close();
}
private static class Person implements Serializable {
private String name;
private String gender;
private transient Integer age;
Person(String name, String gender, Integer age) {
this.name = name;
this.gender = gender;
this.age = age;
}
@Override
public String toString() {
return "name = " + name + " gender = " + gender + " age = " + age;
}
}
}
磁盘IO
- File:用来表示文件和目录的信息,不表示文件的内容
- Path:用来取代File,可以File相互转换
- Paths:提供创建Path的功能
- Files:提供大量处理文件的功能,如复制、读取、写入等
示例
public class FileIO {
public static void main(String[] args) {
Path source = Paths.get("D:", "test.txt");
Path target = Paths.get("E:", "test.txt");
System.out.println(copyFile(source, target));
}
/**
* 复制文件
*/
public static Path copyFile(Path source, Path target) {
try {
System.out.println("source路径:" + source);
//复制文件
return Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
return source;
}
/**
* 列出path下所有文件路径
*/
public static void listAllFiles(Path path) {
if (Files.notExists(path)) {
return;
}
if (Files.isDirectory(path)) {
try {
Files.list(path).forEach(MyFile::listAllFiles);
} catch (IOException e) {
e.printStackTrace();
}
} else {
System.out.println(path);
System.out.println(path.getFileName() + " isReadable: " + Files.isReadable(path)
+ " isWritable: " + Files.isWritable(path)
+ " isExecutable: " + Files.isExecutable(path)
);
}
}
}
网络IO
- InetAddress:网络上的硬件资源,即 IP 地址
- URL:统一资源定位符
- Sockets:使用 TCP 协议实现网络通信
- Datagram:使用 UDP 协议实现网络通信
示例
public class NetworkIO {
public static void main(String[] args) throws IOException {
URL url = new URL("http://www.baidu.com");
InputStream is = url.openStream();
InputStreamReader isr = new InputStreamReader(is, "utf-8");
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
}
非阻塞IO
工作原理
- 1、获取用于连接IO设备的通道Channel以及用于容纳数据的缓冲区Buffer
- 2、通过选择器Selector监控多个Channel的IO状况
- 3、操作缓冲区来对数据进行处理
相比于传统IO
-
NIO
- 面向缓冲区
- 双向
- 非阻塞
- 多路复用
- 支持字符集编解码、锁
-
IO
- 面向流
- 单向
- 阻塞
通道
Channel 是对流的模拟,负责管道的连接和数据的传输,可通过它读取和写入数据。
流只能在一个方向上移动,而通道是双向的,可以用于读、写或者同时用于读写。
- FileChannel:从文件中读写数据
- DatagramChannel:通过 UDP 读写网络中数据
- SocketChannel:通过 TCP 读写网络中数据,用于客户端
- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel,用于服务端
缓冲区
向 Channel 中写数据或从 Channel 中读数据都会先经过 Buffer,
缓冲区提供了对数据的结构化访问,而且可以跟踪系统的读/写进程。
- capacity:最大容量
- position:当前已经读写的字节数
- limit:还可读写的字节数
- mark:记录当前position的位置,可重置到mark
- put():写数据到 Buffer
- flip():切换到读模式
- get():读数据到 Buffer
- allocate(int capacity):非直接缓冲区,在JVM中分配
- allocateDirect(int capacity):非直接缓冲区,在物理内存分配
选择器
Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程可以处理多个事件
-
工作流程
- 1、创建选择器
- 2、将通道注册到选择器
- 3、监听事件
- 4、获取到达的事件
- 5、循环获取
-
监听事件类型
- 读取操作:SelectionKey.OP_READ,数值为1
- 写入操作:SelectionKey.OP_WRITE,数值为4
- socket连接操作:SelectionKey.OP_CONNECT,数值为8
- socket接受操作:SelectionKey.OP_ACCEPT,数值为16
非阻塞示例
//服务端
public class NIOServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
ServerSocket serverSocket = ssChannel.socket();
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
serverSocket.bind(address);
while (true) {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel();
//服务器会为每个新连接创建一个 SocketChannel
SocketChannel sChannel = ssChannel1.accept();
sChannel.configureBlocking(false);
//从客户端读取数据
sChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel sChannel = (SocketChannel) key.channel();
System.out.println(readDataFromSocketChannel(sChannel));
sChannel.close();
}
keyIterator.remove();
}
}
}
private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
StringBuilder data = new StringBuilder();
while (true) {
buffer.clear();
int n = sChannel.read(buffer);
if (n == -1) {
break;
}
buffer.flip();
int limit = buffer.limit();
char[] dst = new char[limit];
for (int i = 0; i < limit; i++) {
dst[i] = (char) buffer.get(i);
}
data.append(dst);
buffer.clear();
}
return data.toString();
}
}
//客户端
public class NIOClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8888);
OutputStream out = socket.getOutputStream();
String s = "hello world";
out.write(s.getBytes());
out.close();
}
}