Java的IO、NIO和Okio

2,292 阅读4分钟

IO

I/O 是什么?

  • 程序内部和外部进行数据交互的过程,就叫输入输出。
    • 程序内部是谁?内存
    • 程序外部是谁?
      • 一般来说是两类:本地文件和网络。
      • 也有别的情况,比如你和别的程序做交互,和你交互的程序也属于 外部,但一般来说,就是文件和网络这么两种。
    • 从文件里或者从网络上读数据到内存里,就叫输入;从内存里写到文件 里或者发送到网络上,就叫输出
  • Java I/O 作用只有一个:和外界做数据交互

用法

  • 使用流,例如 FileInputStream / FileOutputStream
  • 可以用 Reader 和 Writer 来对字符进行读写
  • 流的外面还可以套别的流,层层嵌套都可以
  • BufferedXXXX 可以给流加上缓冲。对于输入流,是每次多读一些放在内存 里面,下次再去数据就不用再和外部做交互(即不必做 IO 操作);对于输 出流,是把数据先在内存里面攒一下,攒够一波了再往外部去写。 通过缓存的方式减少和和外部的交互,从而可以提高效率
  • 文件的关闭:close()
  • 需要用到的写过的数据,flush() 一下可以保证数据真正写到外部去(读数据 没有这样的担忧)
  • 这个就是 Java 的 I/O,它的原理就是内存和外界的交互
  • Java I/O 涉及的类非常多,但你用到哪个再去关注它就行了,不要背类 的继承关系图

FileOutputStream写操作

    //写
    private static void writeIO() {
        OutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream("text.txt");
            outputStream.write('a');
            outputStream.write('b');
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

FileInputStream读操作

    //字符读取
    private static void readIO() {
        InputStream inputStream = null;
        try{
            inputStream = new FileInputStream("text.txt");
            char content = (char) inputStream.read();
            char content2 = (char) inputStream.read();
            System.out.println(content);//a
            System.out.println(content2);//b
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

InputStreamReader字符串读取操作

   //字符串读取
    private static void stringIO() {
        //这样写流将自动关闭
        try( InputStream inputStream = new FileInputStream("text.txt");
             Reader reader = new InputStreamReader(inputStream);
             BufferedReader bufferedReader = new BufferedReader(reader)){
            //            FileReader reader = new FileReader("text.txt");
            System.out.println(bufferedReader.readLine());//ab
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

BufferedOutputStream 读操作

    private static void bosIO() {
        try( OutputStream os = new FileOutputStream("text.txt");
             BufferedOutputStream bos = new BufferedOutputStream(os)){
            bos.write('c');
            bos.write('d');
            //如果bos不在try的括号内,需要手动冲一下
            //             bos.flush();//或 bos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

文件拷贝,InputStream读OutputStream写

    //文件拷贝
    public static void fileCopy(){
        try (InputStream is = new FileInputStream("text.txt");
             OutputStream os = new FileOutputStream("text_copy.txt")){
            //笨方法 一个一个地写入
            //            os.write(is.read());
            byte[] data = new byte[1024];
            int readCount ;
            while ((readCount = is.read(data))!=-1){
                os.write(data,0,readCount);
            }

            //性能优化,BufferedXXX给流加上缓冲,减少交互频率
            //            InputStream is2 = new BufferedInputStream(new FileInputStream("text.txt"));
            //            OutputStream os2 = new BufferedOutputStream(new FileOutputStream("text_copy.txt"));

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

客户端请求SocketIO

    //socket请求方式
    public static void socketIO(){
        try {
            Socket socket = new Socket("hencoder.com",80);
            OutputStream os = socket.getOutputStream();//发送请求报文
            InputStream is = socket.getInputStream();//接收响应报文
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os));
            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
            writer.write("GET / HTTP/1.1\n"+
                    "Host: www.example.com\n\n");
            writer.flush();
            String message;
            while ((message = reader.readLine())!=null){
                System.out.println(message);//整个http的响应报文
            }
            System.out.println(reader.readLine());//HTTP/1.1 200 OK
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

服务端serverSocketIO

//serverSocket
    public static void serverSocketIO(){
        try (ServerSocket serverSocket = new ServerSocket(100);
             Socket socket = serverSocket.accept();
             OutputStream os = socket.getOutputStream();//发送请求报文
             InputStream is = socket.getInputStream();//接收响应报文
             BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os));
             BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        ){
            writer.write("HTTP/1.1 200 OK\n" +
                    "Date: Mon, 23 May 2005 22:38:34 GMT\n" +
                    "Content-Type: text/html; charset=UTF-8\n" +
                    "Content-Length: 138\n" +
                    "Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT\n" +
                    "Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)\n" +
                    "ETag: \"3f80f-1b6-3e1cb03b\"\n" +
                    "Accept-Ranges: bytes\n" +
                    "Connection: close\n" +
                    "\n" +
                    "<html>\n" +
                    "  <head>\n" +
                    "    <title>An Example Page</title>\n" +
                    "  </head>\n" +
                    "  <body>\n" +
                    "    <p>Hello World, this is a very simple HTML document.</p>\n" +
                    "  </body>\n" +
                    "</html>\n\n");
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

NIO

NIO 和 IO 的区别有几点:

  1. 传统IO用的是插管道的方式,用的是Stream;
    • NIO用的也是插管道的方式,用的是 Channel。
    • NIO 的 Channel 是双向的
  2. NIO也用到buffer
    • 它的 Buffer 可以被操作
    • 它强制使用 Buffer
    • 它的 buffer 不好用
  3. NIO有非阻塞式的支持
    • 只是支持非阻塞式,而不是全是非阻塞式。默认是阻塞式的
    • 而且就算是非阻塞式,也只是网络交互支持,文件交互是不支持的

使用

  • NIO 的 Buffer 模型:
  • 用 NIO 来读文件的写法:
    • 使用 fine.getChannel() 获取到 Channel
    • 然后创建一个 Buffer
    • 再用 channel.read(buffer),把文件内容读进去
    • 读完以后,用 flip() 翻⻚
    • 开始使用 Buffer
    • 使用完之后记得 clear() 一下
public class NIOMain {
    public static void main(String[] args) {
//        nio1();//NIO
        nio2();//非阻塞式NIO
    }

    //阻塞式
    private static void nio1() {
        try {
            RandomAccessFile file = new RandomAccessFile("text.txt","r");
            FileChannel channel = file.getChannel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            channel.read(byteBuffer);
            //limit 和 position 可以用flip一句代码解决
//            byteBuffer.limit(byteBuffer.position());
//            byteBuffer.position(0);
            byteBuffer.flip();
            System.out.println(Charset.defaultCharset().decode(byteBuffer));
            byteBuffer.clear();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void nio2() {
        try {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(99));
            //非阻塞 start -> 以下代码为非阻塞配置代码
            serverSocketChannel.configureBlocking(false);//非阻塞
            Selector selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (true){
                selector.select();//阻塞
                for (SelectionKey key:selector.selectedKeys()) {
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    while (socketChannel.read(byteBuffer)!=-1){
                        byteBuffer.flip();
                        socketChannel.write(byteBuffer);
                        byteBuffer.clear();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}

OKIO

特点:

  • 它也是基于插管的,而且是单向的,输入源叫 Source,输出目标叫 Sink
  • 支持 Buffer
    • 像 NIO 一样,可以对 Buffer 进行操作
    • 但不强制使用 Buffer
public class OKIOMain {
    public static void main(String[] args) {
        try (BufferedSource source = Okio.buffer(Okio.source(new File("text.txt")))){
            System.out.println(source.readUtf8Line());//ab
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        //不带buffer的
        //try (Source source = Okio.source(new File("text.txt"))){
        //    Buffer buffer = new Buffer();
        //    source.read(buffer,1024);
        //    System.out.println(buffer.readUtf8Line());//ab
        //} catch (FileNotFoundException e) {
        //    e.printStackTrace();
        //} catch (IOException e) {
        //    e.printStackTrace();
        //}
    }
}