一、继承体系
二、概念
1)定义
1、流的定义
流:数据在数据源(文件)和程序(内存)之间经历的路程
输入流:数据从数据源(文件)到程序(内存)的路径
输出流:数据从程序(内存)到数据源(文件)的路径
Java中流分为两种流
字节流:可以用于读写二进制文件以及任何类型文件(以字节为单位去读取,byte)
字符流:可以用于读写文本文件,不能操作二进制文件(用记事本可以打开的文件就叫文本文件,以字符为单位去读取,char)
2、框架图
3、判断输入流还是输出流
以内存为参照,如果数据是向内存流动(内存占用增大),则是输入流;如果数据是向内存流出(内存占用减小),则是输出流
2)字符流与字节流的区别与转换流
区别:
· 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位。
· 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。
转换流:
转换流的特点:
1. 其是字符流和字节流之间的桥梁
2. 可对读取到的字节数据经过指定编码转换成字符
3. 可对读取到的字符数据经过指定编码转换成字节
何时使用转换流?
1. 当字节和字符之间有转换动作时;
2. 流操作的数据需要编码或解码时。
具体的对象体现:
1. InputStreamReader:字节到字符的桥梁
2. OutputStreamWriter:字符到字节的桥梁
3)相关类与接口
相关类:File、RandomAccessFile、InputStream、OutputStream、Reader、Writer
接口:Serializable
File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。
RandomAccessFile类
该对象特点:
1. 该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。
2. 该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)
注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。 可以用于多线程下载或多个线程同时写数据到文件。
三、对象序列化
所有分布式尝尝需要跨平台、跨网络,因此要求所有传的参数、返回值都必须实现序列化
1)定义
序列化:把Java对象转换为字节序列的过程
反序列化:把字节序列转换为Java对象的过程
2)用途
1、把对象的字节序列永久地保存到硬盘上,通常存放在一个文件仲(持久化对象)
2、在网络上传送对象的字节序列(网络传输对象)
3)实现接口
序列化:ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中
反序列化:ObjectInputStream代表对象输入流,它的readObject()方法从一个输出流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
四、NIO
1)Channel
Channel和传统IO中的Stream很相似。虽然很相似,但是有很大的区别,主要区别为:通道是双向的,通过一个Channel既可以进行读,也可以进行写;而Stream只能进行单向操作,通过一个Stream只能进行读或者写;
以下是常用的几种通道:
· FileChannel 可以从文件读或者向文件写入数据;无法设置为非阻塞模式
· SocketChanel 以TCP来向网络连接的两端读写数据;
· ServerSocketChannel 能够监听客户端发起的TCP连接,并为每个TCP连接创建一个新的SocketChannel来进行数据读写;
· DatagramChannel 以UDP协议来向网络连接的两端读写数据。
2)Buffer
是一个容器,是一个连续数组。
上面的图描述了从一个客户端向服务端发送数据,然后服务端接收数据的过程。客户端发送数据时,必须先将数据存入Buffer中,然后将Buffer中的内容写入通道。服务端这边接收数据必须通过Channel将数据读入到Buffer中,然后再从Buffer中取出数据来处理。
在NIO中,Buffer是一个顶层父类,它是一个抽象类,常用的Buffer的子类有:
· ByteBuffer
· IntBuffer
· CharBuffer
· LongBuffer
· DoubleBuffer
· FloatBuffer
· ShortBuffer
如果是对于文件读写,上面几种Buffer都可能会用到。但是对于网络读写来说,用的最多的是ByteBuffer。
缓冲区内部细节
状态变量
可以用三个值指定缓冲区在任意时刻的状态:
- position 跟踪从缓冲区中写了、获取了多少数据
- limit 表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
- capacity表明可以储存在缓冲区中的最大数据容量。指定了底层数组的大小 ― 或者至少是指定了准许我们使用的底层数组的容量。
position 总是小于或者等于 limit。
limit 决不能大于 capacity。
总结:position<limit<capacity
访问方法:
flip
现在我们要将数据写到输出通道中。在这之前,我们必须调用 flip() 方法。这个方法做两件非常重要的事:
- 它将 limit 设置为当前 position。
- 它将 position 设置为 0。
clear
最后一步是调用缓冲区的 clear() 方法。这个方法重设缓冲区以便接收更多的字节。 Clear 做两种非常重要的事情:
- 它将 limit 设置为与 capacity 相同。
- 它设置 position 为 0。
3)Selector
Selector的作用就是用来轮询(检测)每个注册的Channel,一旦发现Channel有注册的时间发生,便获取事件然后进行处理(一个线程吹多个Channel,因为多线程存在线程上下文切换,因此能减少系统开销)
4) Scatter&Gather
分散(scatter)从Channel中读取是指在读操作时将读取的数据写入多个buffer中。因此,Channel将从Channel中读取的数据“分散(scatter)”到多个Buffer中。
聚集(gather)写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,因此,Channel 将多个Buffer中的数据“聚集(gather)”后发送到Channel。
scatter/ gather经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息
ScatteringReads(不适用于动态消息)
ByteBuffer header = ByteBuffer.allocate(128); ByteBuffer body =ByteBuffer.allocate(1024); ByteBuffer[] bufferArray = { header, body }; channel.read(bufferArray);
Gathering Writes(适合于动态消息)
ByteBuffer header = ByteBuffer.allocate(128); ByteBuffer body = ByteBuffer.allocate(1024); //write data into buffers ByteBuffer[] bufferArray = { header, body }; channel.write(bufferArray);
5) 通道之间的数据传输
在Java NIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据从一个channel(译者注:channel中文常译作通道)传输到另外一个channel。
transferFrom()
FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw"); FileChannel fromChannel = fromFile.getChannel(); RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw"); FileChannel toChannel = toFile.getChannel(); long position = 0; long count = fromChannel.size(); toChannel.transferFrom(position, count, fromChannel);
transferTo()
transferTo()方法将数据从FileChannel传输到其他的channel中。
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw"); FileChannel fromChannel = fromFile.getChannel(); RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw"); FileChannel toChannel = toFile.getChannel(); long position = 0; long count = fromChannel.size(); fromChannel.transferTo(position, count, toChannel);
6) NIO与IO的主要区别与选择
主要区别:
1、 面向流与面向缓冲
2、 阻塞与非阻塞IO
3、 选择器(Selectors)
选择IO或NIO考虑的几个方面:
1、 API调用
2、 数据处理(流或者块(但是必须要读完才能操作))
3、 用来处理数据的线程数
7) FileChannel、SocketChannel&ServerSocketChannel用法
FileChannel:
打开FileChannel
在使用FileChannel之前,必须先打开它。但是,我们无法直接打开一个FileChannel,需要通过使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例。
从FileChannel读取数据
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
向FileChannel写数据
String newData = "New String to write to file..." + System.currentTimeMillis(); ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while(buf.hasRemaining()) { channel.write(buf); }
关闭FileChannel
channel.close();
FileChannel的position方法
long pos = channel.position(); channel.position(pos +123);
如果将位置设置在文件结束符之后,然后试图从文件通道中读取数据,读方法将返回-1 —— 文件结束标志
FileChannel的size方法
long fileSize = channel.size();
FileChannel的truncate方法
channel.truncate(1024);
SocketChannel:
Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建SocketChannel:
1. 打开一个SocketChannel并连接到互联网上的某台服务器。
2. 一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。
打开 SocketChannel
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
从 SocketChannel 读取数据
ByteBuffer buf = ByteBuffer.allocate(48); int bytesRead = socketChannel.read(buf);
该方法将数据从SocketChannel 读到Buffer中。read()方法返回的int值表示读了多少字节进Buffer里。如果返回的是-1,表示已经读到了流的末尾(连接关闭了)。
写入 SocketChannel
String newData = "New String to write to file..." + System.currentTimeMillis(); ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while(buf.hasRemaining()) { channel.write(buf); }
关闭 SocketChannel
socketChannel.close();
非阻塞模式
可以设置 SocketChannel 为非阻塞模式(non-blockingmode).设置之后,就可以在异步模式下调用connect(), read() 和write()了。
connect()
如果SocketChannel在非阻塞模式下,此时调用connect(),该方法可能在连接建立之前就返回了。为了确定连接是否建立,可以调用finishConnect()的方法。像这样:
socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80)); while(! socketChannel.finishConnect() ){ //wait, or do something else... }
write()
非阻塞模式下,write()方法在尚未写出任何内容时可能就返回了。所以需要在循环中调用write()。前面已经有例子了,这里就不赘述了。
read()
非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。所以需要关注它的int返回值,它会告诉你读取了多少字节。
非阻塞模式与选择器
非阻塞模式与选择器搭配会工作的更好,通过将一或多个SocketChannel注册到Selector,可以询问选择器哪个通道已经准备好了读取,写入等。
ServerSocketChannel:
NIO进行服务端开发的一般步骤:
- 创建一个ServerSocketChannel,并配置它为非阻塞模式;
- 绑定监听,配置相关的TCP参数,比如backlog大小;
- 创建一个独立的I/O进程,用于轮询多路复用器Selector
- 创建Selector,将之前创建的ServerSocketChannel注册到Selector上,监听SelectionKey.ACCEPT事件
- 启动I/O线程,在一个循环体中执行Selecttor.selector()方法,轮询就绪的Channel
- 当轮询到就绪的Channel的时候,就需要对它的状态进行判断,如果是OP_ACCEPT状态,说明在这个时候,有客户端接入了,需要调用ServerSocketChannel.accept()方法接收新的客户端;
- 设置新接入的客户端链路SocketChannel为非阻塞模式,并配置一些TCP参数
- 将SocketChannel注册到Selector,监听OP_READ事件
- 如果轮询到了OP_READ事件,则说明在SocketChannel中有新的就绪数据包,这时候需要创建ByteBuffer读取数据包;
- 如果轮询到channel中的事件为OP_WRITE,说明还有数据包没有发送完成,需要继续进行发送
下面以一个简单的Demo来说明NIO的使用方法:
package study170301; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; importjava.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class NioServer { public void run() { new Thread(new MultipexerTimeServer(8080)).start(); // 启动服务器 } public static void main(String[] args) { new NioServer().run(); } class MultipexerTimeServer implements Runnable { private Selector selector; private ServerSocketChannel serverChannel; private volatile boolean stop; public MultipexerTimeServer(int port) { try { serverChannel =ServerSocketChannel.open(); selector = Selector.open(); serverChannel.configureBlocking(false); serverChannel.register(selector, SelectionKey.OP_ACCEPT); // 注册连接事件 serverChannel.socket().bind(newInetSocketAddress(port), 1024); System.out.printf("Serveris started in %s:\n", port); } catch (IOException e) { e.printStackTrace(); } } public void stop() { this.stop = true; } private void handleEvent(SelectionKey key) { // 只有在key合法的情况下,才对相应的事件做处理 if (key.isValid()) { // 处理客户端的连接事件 if (key.isAcceptable()) { ServerSocketChannel ssc =(ServerSocketChannel) key.channel(); try { SocketChannel sc =ssc.accept(); sc.configureBlocking(false); sc.register(selector,SelectionKey.OP_READ); // 注册可读事件 } catch (IOException e) { e.printStackTrace(); } } // 处理读事件 if (key.isReadable()) { SocketChannel socketChannel =(SocketChannel) key.channel(); ByteBuffer readBuffer =ByteBuffer.allocate(1024); // 创建一个1024的空间 try { int bytes =socketChannel.read(readBuffer); if (bytes > 0) { readBuffer.flip();// 从缓存中出数据 byte[] bt = newbyte[readBuffer.remaining()]; // 创建一个byte,用来装数据 readBuffer.get(bt); System.out.println(new String(bt)); // 输出从客户端读到的数据 } } catch (IOException e) { e.printStackTrace(); } } } else { System.out.println("key isnot valid..."); } } @Override public void run() { while (!stop) { try { selector.select(1000); Set<SelectionKey>selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> it = selectedKeys.iterator(); SelectionKey key = null; while (it.hasNext()) { key = it.next(); it.remove(); // 在这里对key进行处理 handleEvent(key); } } catch (IOException e) { e.printStackTrace(); } // 设置轮询扫描的时间间隔 } } } }