3.网络IO
3.1 初步认识IO
3.1.1 什么是 IO 以及 IO 流的作用
I/O 实际上是input和output,也就是输入和输出。而流其实是一种抽象的概念,它表示的是数据的无结构化传递。
3.1.2 Java 中的 IO 流以及 IO 流的分类
Java中的IO体系:在Java中I/O流操作的类很多,但是核心体系实际上就只有File
、InputStream
、OutputStream
、Reader
、Writer
。
IO流的分类:
字节流:操作的数据单元是8位的字节。
InputStream
、OutputStream
作为抽象基类。字节流能处理所有文件。字符流:操作的数据单元是字符。以
Writer
、Reader
作为抽象基类。字符流只能处理文本文件。
字符流:操作的数据单元是字符。以Writer、Reader作为抽象基类。
3.1.3 IO流实战
public class FileInputDemo {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("d:/test.txt");
int i = 0;
while ((i=fileInputStream.read()) != -1) {
System.out.print((char)(i));
}
}
}
3.2 深入分析Java中的IO流
3.2.1 IO流的数据来源及操作的
IO流的数据源:
硬盘
内存
键盘
网络
public class TestDemo {
public static void main(String[] args) throws IOException {
int i = 0;
// 硬盘
FileInputStream fileInputStream = new FileInputStream("D:/test.txt");
while ((i = fileInputStream.read()) != -1) {
System.out.println((char)i);
}
// 内存
String str = "test data";
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(str.getBytes());
while ((i = byteArrayInputStream.read()) != -1) {
System.out.println((char)i);
}
// 键盘
//Scanner
InputStream inputStream = System.in;
while ((i = inputStream.read()) != -1) {
System.out.print((char) i);
}
// 网络IO
Socket socket = null;
socket.getInputStream();
socket.getOutputStream();
}
}
3.2.2 本地磁盘文件操作之File
3.2.3 基于文件的字节输入输出流实战
public class InputAndOutputDemo {
public static void main(String[] args) throws IOException {
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String path = bufferedReader.readLine();
FileInputStream fileInputStream = new FileInputStream(new File(path));
FileOutputStream fileOutputStream = new FileOutputStream("D:/test.jpg");
int len = 0;
// 缓存 一次写入多个字节,否则一次写入1字节
byte[] buffer = new byte[1024];
while ((len = fileInputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer,0, len);
}
inputStreamReader.close();
bufferedReader.close();
fileInputStream.close();
fileOutputStream.close();
}
}
流使用后一定要及时关闭,也可以使用如下图所示,将流写入到try的括号内,这样try结束后会自动关闭流。前提是流的操作类必须实现Closeable
接口.
3.2.4 深入浅出 read
3.2.5 基于内存的字节输入输出流实战
public class MemroryDemo {
public static void main(String[] args) throws IOException {
String str = "Hello World";
// 从内存中读数据
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(str.getBytes());
// 写入到文件
FileOutputStream fileOutputStream = new FileOutputStream("D:/TEST1.txt");
// 写入到内存中
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int i = 0;
while ((i = byteArrayInputStream.read()) != -1) {
byteArrayOutputStream.write(Character.toUpperCase((char) i));
fileOutputStream.write(Character.toUpperCase((char) i));
}
System.out.println(byteArrayOutputStream.toString());
// 因为内存流并没有操作实际的文件所以不需要关闭
fileOutputStream.close();
}
}
3.2.6 基于缓冲流的输入输出实战
缓冲流简介: 缓冲流是带缓冲区的处理流,它会提供一个缓冲区,缓冲区的作用的主要目的是:避免每次和硬盘打交道,能够提高输入/输出的执行效率。
缓冲流与之前的byte[] buffer = new byte[1024];
一样利用了缓冲区的概念,默认缓冲区为8KB.
public class BufferDemo {
public static void main(String[] args) {
int i = 0;
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("d:/test.txt"));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("d:/test1/txt"));
){
byte[] buffer = new byte[1024];
while ((i=bufferedInputStream.read(buffer)) != -1) {
bufferedOutputStream.write(buffer);
}
bufferedOutputStream.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.2.7 详解 Flush
缓冲区写时,若缓冲区没有写满,不会真正的写磁盘,需要手动flush
。close
也会触发flush
。
3.2.8 基于文件的字符输入输出流实战
public class ReadWriteDemo {
public static void main(String[] args) {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("d:/test.txt"))){
InputStreamReader inputStreamReader = new InputStreamReader(bis, "UTF-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
System.out.println(bufferedReader.readLine());
// 字符输出流
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("d:/test_1.txt"), "UTF-8");
BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
bufferedWriter.write("爱你 媳妇");
bufferedWriter.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.2.9 序列化和反序列化
序列化: 是把对象的状态信息转化为可存储或传输的形式过程,也就是把对象转化为字节序列的过程称为对象的序列化。
反序列化: 是序列化的逆向过程,把字节数组反序列化为对象,把字节序列恢复为对象的过程成为对象的反序列化。
3.3 网络IO
3.3.1 Socket和ServerSocket
3.3.2 网络通信协议分析
网络分层模型:
3.3.3 使用Socket实现RPC通信
什么是 RPC 协议:RPC(Remote Procedure Call) 远程过程调用,是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。一般用来实现部署在不同机器上的系统之间的方法调用,使得程序能够像访问本地系统资源一样,通过网络传输去访问远端系统资源。
3.4 深入分析NIO
3.4.2 深入分析5中IO模型
-
阻塞IO
进程阻塞,影响性能。
-
非阻塞IO
每个socket都创建一个线程进行轮询,会创建多个线程,影响性能。
-
IO复用
select/poll
适用于服务器需要处理多个socket。最大的缺陷是受陷于系统开销。最大1024,随着该值的增大,因为轮询增加,因此导致性能下降,增加网络延迟。(JDK1.5之前使用)epoll
为了解决select/poll的缺陷。对单个进程所打开的连接数没有限制(受限于硬件)
利用每个
fd()
的callback()
函数来实现异步回调,省略了轮询开销。-
mmp (减少内存复制),有点像AIO(伪AIO)
epoll类似与,打电话点餐,然后做其他事。餐馆做好饭后打电话,让我去饭店取。
mmap:内核空间和用户空间映射同一块内存,因此不存在复制。
-
信号驱动IO
类似于epoll.
- 异步IO(AIO)
AIO在发起读请求后,内核在数据准备好后,会发起信号,并把数据发送到用户空间。
与epoll的不同之处在于,AIO是打电话点餐后,餐馆不仅做好饭,还送到我家,再打电话给我让我取。
5种IO模型都是为了提升服务端并处处理连接的数量。
3.4.3 NIO的概述以及应用
NIO 从JDK1.4 提出的,本意是New IO,它的出现为了弥补IO的不足,提供了更高效的方式。
核心组件
- 通道(
Channel
): Java NIO数据来源,可以是网络,也可以是本地磁盘 - 缓冲区(
Buffer
):数据读写的中转区。 - 选择器(
Selectors
):异步IO的核心类,可以实现异步非阻塞IO,一个selectors可以管理多个通道Channel。
IO 和 NIO的区别
3.4.4 NIO 详解 Channel 和 Buffer
Channel
都是对buffer
进行操作的。
Channel 的实现
- FileChannel : 从文件中读写数据。
- DatagramChannel : 通过 UDP 协议读写网络中的数据。
- SocketChannel : 通过 TCP 协议读写网络中的数据。
- ServerSocketChannel : 监听一个 TCP 连接,对于每一个新的客户端连接都会创建一个 SocketChannel 。
什么是Buffer
buffer
是一个对象,它包含了需要写入或者刚读出的数据,最常用的缓冲区类型是 ByteBuffer。
public class NIOReadWriteDemo {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("d:/test.txt");
FileOutputStream fos = new FileOutputStream("d:/test_1.txt")) {
//FileChannel 针对本地文件进行操作
FileChannel fic = fis.getChannel();
FileChannel foc = fos.getChannel();
// 分配缓冲区大小
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
// byteBuffer 与byteBuffer1等价
//byte[] bytes = new byte[30];
//ByteBuffer byteBuffer1 = ByteBuffer.wrap(bytes);
// 写数据到缓冲区
byteBuffer.put("大家好,这是NIOReadWriteDemo\n".getBytes());
// 缓冲区反转,表示从读转到写
byteBuffer.flip();
// 从缓冲区写数据到文件
foc.write(byteBuffer);
//清空缓冲区
byteBuffer.clear();
int len = 0;
// nio读文件 写文件
while ((len = fic.read(byteBuffer)) != -1) {
System.out.println(new String(byteBuffer.array()));
byteBuffer.flip();
foc.write(byteBuffer);
byteBuffer.clear();
}
} catch (Exception e) {
}
}
}
3.4.5 剖析缓冲区的内部细节
Buffer的本质:缓冲区本质上是一块可以写入数据,以及从中读取数据的内存,实际上也是一个byte[]数据,只是在NIO中被封装成了NIO Buffer对象,并提供了一组方法来访问这个内存块,要理解buffer的工作原理,需要知道几个属性:
- capacity
- position
- limit
3.4.7 SocketChannel 和ServerSocketChannel
3.4.8 选择器Selector
什么是Selector:Selector
(选择器,多路复用器)是Java NIO中能够检测一到多个NIO通道,是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。
Selector的服务器实例
public class NIOSelectorServer {
static Selector selector = null;
public static void main(String[] args) {
try {
selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设置连接的非阻塞
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress("localhost", 8080));
// 监听连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 阻塞方法
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
// 每次都删除 避免重复处理
iterator.remove();
if (selectionKey.isAcceptable()) {
handleAccept(selectionKey);
} else if (selectionKey.isReadable()) {
handleRead(selectionKey);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handleAccept(SelectionKey selectionKey) {
ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
try {
SocketChannel socketChannel = channel.accept();
// 设置IO的非阻塞
socketChannel.configureBlocking(false);
if (socketChannel.isConnectionPending()) {
socketChannel.finishConnect();
}
socketChannel.write(ByteBuffer.wrap("Hello, I‘m Hades. This is a Server".getBytes()));
// 注册SocketChannel的读事件
socketChannel.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handleRead(SelectionKey selectionKey) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
socketChannel.read(buffer);
System.out.println("time:" + System.currentTimeMillis() + " Server Recv: " + new String(buffer.array()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
Selector的客户端实例
public class NIOSelectorClient {
static Selector selector = null;
public static void main(String[] args) {
try {
selector = Selector.open();
SocketChannel socketChannel = SocketChannel.open();
// 设置为非阻塞
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("localhost", 8080));
socketChannel.register(selector, SelectionKey.OP_CONNECT);
while (true) {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
if (selectionKey.isConnectable()) {
handleConnect(selectionKey);
} else if (selectionKey.isReadable()) {
handleRead(selectionKey);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handleConnect(SelectionKey selectionKey) {
try {
SocketChannel channel = (SocketChannel) selectionKey.channel();
if (channel.isConnectionPending()) {
channel.finishConnect();
}
channel.write(ByteBuffer.wrap("Hello Server! I'm a Client".getBytes()));
channel.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handleRead(SelectionKey selectionKey) {
try {
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
System.out.println("time:" + System.currentTimeMillis() + " Client Recv:" + new String(buffer.array()));
} catch (Exception e) {
}
}
}