一、java io的发展历程
tips:
1、阻塞与非阻塞的概念:阻塞是当前线程在当前任务没有完成前,是不能去执行其他任务的,只能等到它的就绪条件达成后,才能继续执行当前任务。非阻塞是指一个线程不管当前任务是否已经结束,直接返回,相应的操作在后台继续处理
2、同步与异步:同步是一种可靠的有序运行的机制,当进行同步操作时,后续的任务等待当前调用的返回,才会进行下一步。异步则是其他任务不需要等待当前调用返回,通常依靠事件,回调等机制来实现任务间的执行顺序
1、bio阶段(排队打饭模式)
传统的Java io包:基于流模型实现 ,具备file抽象、输入输出的功能,交互方式是同步、阻塞的方式,在读取输入流或者写入输出流时,在读写操作完成前,线程会一直阻塞在那里,它们之间的调用是可靠的线性关系。
优点:代码简单直观,易于实现
缺点:io效率不高和扩展性存在局限性
2、nio阶段(点单等待被叫)
Java1.4中引入了nio框架:提供了buffer、channel、selector等新的抽象,可以构建多路复用的、同步非阻塞io程序,同时提供更接近操作系统底层的高性能数据操作方式。
nio的主要组成部分:
buffer:高效的数据容器,除了布尔类型,所有的原始数据类型都有相应的buffer实现
channel:类似在linux操作系统上看到的文件描述符,是nio中用来支持批量式io操作的一种抽象。file和socket通常被认为是比较高级层次的抽象,而channel是更加贴近操作系统底层的抽象,这也使得nio得以充分利用现代操作系统的底层机制,获得特定场景的优化,例如dma(direct memory access),不同层次之间的抽象是相互关联的,我们可以通过socket获取channel反之亦然
selector:nio实现多路复用的基础,提供了一种高效的机制,可以检测到注册在selector上的多个channel中是否有channel处于就绪状态 ,进而实现了单线程对多个channel的高效管理,selector同样是基于底层操作系统机制,不同模式不同版本都存在区别,linux上依赖epoll,windows上nio2依赖于iocp
charset:根据unicode字符串定义,nio也提供了相应的解码器等
nio多路复用小栗子:
public class NIOServer extends Thread {
public void run() {
try (Selector selector = Selector.open();
ServerSocketChannel serverSocket = ServerSocketChannel.open();) {// 创建Selector和Channel
serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8888));
serverSocket.configureBlocking(false);
// 注册到Selector,并说明关注点
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();// 阻塞等待就绪的Channel,这是关键点之一
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectedKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
// 生产系统中一般会额外进行就绪状态检查
sayHelloWorld((ServerSocketChannel) key.channel());
iter.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void sayHelloWorld(ServerSocketChannel server) throws IOException {
try (SocketChannel client = server.accept();) { client.write(Charset.defaultCharset().encode("Hello world!"));
}
}
public static void main(String[] args) throws IOException {
DemoServer server = new DemoServer();
server.start();
try (Socket client = new Socket(InetAddress.getLocalHost(), server.getPort())) {
BufferedReader bufferedReader = new BufferedReader(new
InputStreamReader(client.getInputStream()));
bufferedReader.lines().forEach(s -> System.out.println(s));
}
}
}
主要步骤:
1、通过Selector.open()创建一个Selector,类似调度员的角色
2、然后创建一个ServerSocketChannel,并且向Selector注册,通过指定SelectionKey.OP_ACCEPT,告诉调度员,它关注的是新的请求(我们必须明确配置非阻塞模式,阻塞模式下是不允许注册操作的,会抛出IllegalBlockingModeException异常)
3、Selector阻塞在select操作,当有Channel发生接入请求,就会被唤醒
4、在sayHelloWorld中,通过SocketChannel和buffer进行数据操作