Java io流

一.java io流 概况

    按照技术体系分为BIO、NIO、AIO

   

    每种技术体系都有以下分类

    1. 流内容角度分类:

     

1.字节流---> 输入或者输出都是字节。
2.字符流---> 输入或者输出都是字符,相比字节流,其实是多做一步,自动把字符转换成字节或者把字节转换为字符(涉及到字符集或者操作系统默认字符集) ;实际应用中,这个只能用于字符操作,不能用于二进制数据(比如视频、音频、图片啥的)。

     2.流向维度分类:

1.输入流---> 数据从外部设备到内存
2.输出流---> 数据从内存到外部设备

   

二.BIO

   1.流种类图

      

 

 

   2.说明

    

     2.1字节流   

1.文件操作字节流
FileInputStream
FileOutputStream

2.带缓冲的字节流(装饰器设计模式,增加其他流的能力)
BufferedInputStream
BufferedOutputStream

3.直接操作java基本数据类型的字节流(装饰器设计模式,增加其他流的能力)
DataInputStream  ---读取字节流,根据UTF编码解析成字符串,再解析成对应对象
DataOutputStream --- 先把输出对象转换成字符串,再使用UTF8编码转换为字节数组,进行输出

4.可以回写的输入字节流,可以把读取的数据再塞回流中,再流缓冲的最前端(装饰器设计模式,增加其他流的能力)
PushbakInputStream

5.自动转换成字符串、再自动转换成字节的输出字节流(比如直接打印一个浮点数字,转换成字符串,再根据字符编码(用户指定,否则用系统字符集)转换成字节数组,再输出) (装饰器设计模式,增加其他流的能力)
PrintStream  --- System.out 就是这个类的对象

6.序列化、反序列化字节流
ObjectInputStream  --- 从文件中读取流,转换成对象返回
ObjectOutputStream --- 把对象转换成字节数组,写入到文件

7.管道操作字节流,用于线程间通讯,向管道写数据,其他线程从管道读数据
PipedInputStream
PipedOutputStream

8.合并多个输入字节流,读取这个合并流的时候,按照顺序读出所有流的所有数据(先读stream1,读完了,再读流2,依次类推)
SequenceInputStream

9.从字符串创建一个输入字节流,读流时,其实是由字符串提供内容
StringBufferInputStream

10.字节数组操作字节流,从字节数组创建流
ByteArrayInputStream --- 读取时,实际是从字节数组中读数据
ByteArrayOutputStream --- 写入时,实际是写入字节数组

 

      2.2字符流

1.文件读写字符流
FileReader --- 文件读入字符流
FileWriter --- 文件写入字符流

2.带缓存区的字符流(装饰器)
BufferedReader
BufferedWriter

3.字符串相关字符流
StringReader --- 从一个String读取字符
StringWriter --- 以StringBuffer当成输出地

4.管道操作字符流
PipedReader
PipedWriter

5.字符数组字符流---字符数组作为源
CharArrayReader
CharArrayWriter

6.打印输出字符流
PrintWriter --- 这个类功能和PrintStream功能一致,但是这个PrintStream生成字节数组时用的是操作系统默认字符集,PrintWriter可以指定字符集,兼容性更好

 

 

 三.NIO

 

 

Java NIO 由以下几个核心部分组成:

  • Channels
  • Buffers
  • Selectors

虽然Java NIO 中除此之外还有很多类和组件,但在我看来,Channel,Buffer 和 Selector 构成了核心的API。其它组件,如Pipe和FileLock,只不过是与三个核心组件共同使用的工具类。因此,在概述中我将集中在这三个组件上。其它组件会在单独的章节中讲到。

 

Channel 和 Buffer

基本上,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流,但是流是单向的,Channel是双向的; 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。这里有个图示:

Channel和Buffer有好几种类型。下面是JAVA NIO中的一些主要Channel的实现:

  • FileChannel   --- 文件操作,不能设置为非阻塞模式
  • DatagramChannel --- UDP操作,可以设置为非阻塞模式
  • SocketChannel  --- TCP客户端操作,可以设置为非阻塞模式
  • ServerSocketChannel ---TCP服务端操作,可以设置为非阻塞模式

正如你所看到的,这些通道涵盖了UDP 和 TCP 网络IO,以及文件IO。

与这些类一起的有一些有趣的接口,但为简单起见,我尽量在概述中不提到它们。本教程其它章节与它们相关的地方我会进行解释。

 

以下是Java NIO里关键的Buffer实现:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

这些Buffer覆盖了你能通过IO发送的基本数据类型:byte, short, int, long, float, double 和 char。

Java NIO 还有个 MappedByteBuffer,用于表示内存映射文件, 我也不打算在概述中说明。

     Buffer4个属性:

1.capacity
作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。
2.position
当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity.
当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。
3.limit
在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。
当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position
4.mark
Mark标记,调用mark()来设置mark=position,再调用reset()可以让position恢复到标记的位置

     Buffer常用方法:

 

allocate(int capacity):从堆空间中分配一个容量大小为capacity的byte数组作为缓冲区的byte数据存储器。

allocateDirect(int capacity):不使用JVM堆而是通过操作系统来创建内存块用作缓冲区,它与当前操作系统能够更好的耦合,因此能进一步提高I/O操作速度。但是分配直接缓冲区的系统开销很大,因此只有在缓冲区较大并长期存在,或者需要经常重用时,才使用这种缓冲区

limit(), limit(10)等:其中读取和设置这4个属性的方法的命名和jQuery中的val(),val(10)类似,一个负责get,一个负责set

mark(): 把Buffer中的position赋值给mark属性。之后可以通过调用Buffer.reset()方法恢复到这个position。
reset():把position设置成mark的值,相当于之前做过一个标记,现在要退回到之前标记的地方 clear():position
= 0;limit = capacity;mark = -1; 有点初始化的味道,但是并不影响底层byte数组的内容 flip(): limit = position;position = 0;mark = -1; 翻转,也就是让flip之后的position到limit这块区域变成之前的0到position这块,翻转就是将一个处于存数据状态的缓冲区变为一个处于准备取数据的状态 rewind():把position设为0,mark设为-1,不改变limit的值 remaining():return limit - position;返回limit和position之间相对位置差 hasRemaining():return position < limit返回是否还有未读内容 compact():把从position到limit中的内容移到0到limit-position的区域内,position和limit的取值也分别变成limit-position、capacity。如果先将positon设置到limit,再compact,那么相当于clear() get() :相对读,从position位置读取一个byte,并将position+1,为下次读写作准备 get(int index):绝对读,读取byteBuffer底层的bytes中下标为index的byte,不改变position get(byte[] dst, int offset, int length):从position位置开始相对读,读length个byte,并写入dst下标从offset到offset+length的区域 put(byte b):相对写,向position的位置写入一个byte,并将postion+1,为下次读写作准备 put(int index, byte b):绝对写,向byteBuffer底层的bytes中下标为index的位置插入byte b,不改变position put(ByteBuffer src):用相对写,把src中可读的部分(也就是position到limit)写入此byteBuffer put(byte[] src, int offset, int length):从src数组中的offset到offset+length区域读取数据并使用相对写写入此byteBuffer

 

 

Selector

Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。

这是在一个单线程中使用一个Selector处理3个Channel的图示:

要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。

 

NIO TCP SERVER示例代码:

public class Reactor implements Runnable {
    public int id = 100001;
    public int bufferSize = 2048;
    @Override
    public void run() {
        // TODO Auto-generated method stub
        init();
    }

    public void init() {
        try {
            // 创建通道和选择器
            ServerSocketChannel socketChannel = ServerSocketChannel.open();
            Selector selector = Selector.open();
            InetSocketAddress inetSocketAddress = new InetSocketAddress(
                    InetAddress.getLocalHost(), 4700);
            socketChannel.socket().bind(inetSocketAddress);
            // 设置通道非阻塞 绑定选择器
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_ACCEPT).attach(
                    id++);
            System.out.println("Server started .... port:4700");
            listener(selector);

        } catch (Exception e) {
            // TODO: handle exception
        }
    }

    public void listener(Selector in_selector) {
        try {
            while (true) {
                Thread.sleep(1*1000);
                in_selector.select(); // 阻塞 直到有就绪事件为止
                Set<SelectionKey> readySelectionKey = in_selector
                        .selectedKeys();
                Iterator<SelectionKey> it = readySelectionKey.iterator();
                while (it.hasNext()) {
                    SelectionKey selectionKey = it.next();
                    // 判断是哪个事件
                    if (selectionKey.isAcceptable()) {// 客户请求连接
                        System.out.println(selectionKey.attachment()
                                + " - 接受请求事件");
                        // 获取通道 接受连接,
                        // 设置非阻塞模式(必须),同时需要注册 读写数据的事件,这样有消息触发时才能捕获
                        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey
                                .channel();
                        serverSocketChannel
                                .accept()
                                .configureBlocking(false)
                                .register(
                                        in_selector,
                                        SelectionKey.OP_READ
                                                | SelectionKey.OP_WRITE).attach(id++);
                        System.out
                                .println(selectionKey.attachment() + " - 已连接");

                        // 下面这种写法是有问题的 不应该在serverSocketChannel上面注册
                        /*
                         * serverSocketChannel.configureBlocking(false);
                         * serverSocketChannel.register(in_selector,
                         * SelectionKey.OP_READ);
                         * serverSocketChannel.register(in_selector,
                         * SelectionKey.OP_WRITE);
                         */
                    }
                    if (selectionKey.isReadable()) {// 读数据
                        System.out.println(selectionKey.attachment()
                                + " - 读数据事件");
                        SocketChannel clientChannel=(SocketChannel)selectionKey.channel();
                        ByteBuffer receiveBuf = ByteBuffer.allocate(bufferSize);
                        clientChannel.read(receiveBuf);
                        System.out.println(selectionKey.attachment()
                                + " - 读取数据:" + getString(receiveBuf));
                    }
                    if (selectionKey.isWritable()) {// 写数据
                        System.out.println(selectionKey.attachment()
                                + " - 写数据事件");
                        SocketChannel clientChannel = (SocketChannel) selectionKey.channel();
                        ByteBuffer sendBuf = ByteBuffer.allocate(bufferSize);
                        String sendText = "hello\n";
                        sendBuf.put(sendText.getBytes());
                        sendBuf.flip();        //写完数据后调用此方法
                        clientChannel.write(sendBuf);
                    }
                    if (selectionKey.isConnectable()) {
                        System.out.println(selectionKey.attachment()
                                + " - 连接事件");
                    }
                    // 必须removed 否则会继续存在,下一次循环还会进来,
                    // 注意removed 的位置,针对一个.next() remove一次
                    it.remove(); 
                }
            }
        } catch (Exception e) {
            // TODO: handle exception
            System.out.println("Error - " + e.getMessage());
            e.printStackTrace();
        }

    }
    /**
     * ByteBuffer 转换 String
     * @param buffer
     * @return
     */
    public static String getString(ByteBuffer buffer)
    {
        String string = "";
        try
        {
            for(int i = 0; i<buffer.position();i++){
                string += (char)buffer.get(i);
            }
            return string;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
            return "";
        }
    }
}
View Code

 

 

 

 

四.AIO

   

AIO最大的一个特性就是异步能力,这种能力对socket与文件I/O都起作用。AIO其实是一种在读写操作结束之前允许进行其他操作的I/O处理。AIO是对JDK1.4中提出的同步非阻塞I/O(NIO)的进一步增强。

增加的新的类如下:

  • AsynchronousChannel:支持异步通道,包括服务端AsynchronousServerSocketChannel和普通AsynchronousSocketChannel等实现。
  • CompletionHandler:用户处理器。定义了一个用户处理就绪事件的接口,由用户自己实现,异步io的数据就绪后回调该处理器消费或处理数据。
  • AsynchronousChannelGroup:一个用于资源共享的异步通道集合。处理IO事件和分配给CompletionHandler

另外,主要在java.nio.channels包下增加了下面四个异步通道:

  • AsynchronousSocketChannel
  • AsynchronousServerSocketChannel
  • AsynchronousFileChannel
  • AsynchronousDatagramChannel

AIO的实施需充分调用OS参与,IO需要操作系统支持、并发也同样需要操作系统的支持,所以性能方面不同操作系统差异会比较明显。因此在实际中AIO使用不是很广泛。

 

   

本文摘自网络,如有侵权,请联系我!!! 

   

 

posted @ 2022-02-25 11:56  高压锅里的大萝卜  阅读(154)  评论(0编辑  收藏  举报