IO与NIO相关知识浅析

传统IO流

IO分类及常用IO

1) 按照流的流向的不同:输入流 输出流 (站位于程序的角度)
2) 按照流中的数据单位的不同:字节流 字符流 (纯文本文件使用字符流 ,除此之外使用字节流)
3) 按照流的角色的不同:节点流 处理流 (流直接作用于文件上是节点流(4个),除此之外都是处理流)

分类 字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 InputStrem OutputStream Reader Writer
实现流 FilterInputStrem FilterOutputStream FilterReader FilterWriter
文件流(节点流) FileInputStream FileOutputStream FileReader FileWriter
数组流 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
管道流 PipInputStream PipOutputStream PipedReader PipedWriter
字符串流 StringReader StringWriter
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
转换流 InputStreamReader OutputStreamWriter
对象流 ObjectInputStream ObjectOutputStream
打印流 PrintStream PrintWriter
推回输入流 PushbackInputStream PushbackReader
数据流 DataInputStream DataOutputStream

常用IO示例

/**
 *@author yutao
* @desc 主要IO流示例
 * 节点流只有 FileInputStream   FileOutputStream   FileReader  FileWriter
 * 其他都是处理流
 * 字符流只能处理纯文本文件(.txt等), .doc是不能处理的
 */

public class IOTest {
    /**
     * 文件流(节点流):
     *          FileInputStream   FileOutputStream
     *          FileReader        FileWriter    (仅只能处理纯文本文件)
     * @desc 文件复制,FileOutputStream和FileWriter构造方法可以指定boolean值是否为追加内容,默认为false;
     */
    @Test
    public void testFile() {
        //字节流
        File file = new File("C:\\Users\\yutao\\Desktop\\person.txt");
        String file1 = "C:\\Users\\yutao\\Desktop\\person1.txt";
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            fileInputStream = new FileInputStream(file);
            fileOutputStream = new FileOutputStream(file1);
            //逐字节读写
            int b = 0;
            while ((b = fileInputStream.read()) != -1) {
                fileOutputStream.write((byte)b);
            }
            //使用缓冲区读写,减少读写的切换
            byte[] bytes = new byte[1024];
            int len = 0;
            while ((len = fileInputStream.read(bytes)) != -1) {
                fileOutputStream.write(bytes,0,len);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        //字符流
        File file2 = new File("C:\\Users\\yutao\\Desktop\\person.txt");
        File file3 = new File("C:\\Users\\yutao\\Desktop\\person2.txt");
        FileReader fileReader = null;
        FileWriter fileWriter = null;
        try {
            fileReader = new FileReader(file2);
            fileWriter = new FileWriter(file3);
            //逐字符读写
           /* int ch = 0;
            while ((ch = fileReader.read()) != -1) {
                fileWriter.write((char)ch);
            }*/
            //字符缓冲区读写
            char[] chars = new char[1024];
            int length = 0;
            while ((length = fileReader.read(chars)) != -1) {
                fileWriter.write(chars,0,length);
                fileWriter.flush();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileReader != null) {
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fileWriter != null) {
                try {
                    fileWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 缓冲流(处理流):
     *          BufferedInputStream     BufferedOutputStream
     *          BufferedReader          BufferedWriter
     * @desc 本身维持着一个数组
     */
    @Test
    public void testBuffer() throws IOException {
        File file = new File("C:\\Users\\yutao\\Desktop\\watch.png");
        String file1 = "C:\\Users\\yutao\\Desktop\\watch1.png";
        //字节流
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        BufferedInputStream bufferedInputStream = null;
        BufferedOutputStream bufferedOutputStream = null;
        try {
            fileInputStream = new FileInputStream(file);
            fileOutputStream = new FileOutputStream(file1);
            bufferedInputStream = new BufferedInputStream(fileInputStream);
            bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
            byte[] bytes = new byte[1000];
            int len = 0;
            while ((len = bufferedInputStream.read(bytes)) != -1) {
                bufferedOutputStream.write(bytes,0,len);
                bufferedOutputStream.flush();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bufferedOutputStream != null) {
                bufferedOutputStream.close();
            }
            if (bufferedInputStream != null) {
                bufferedInputStream.close();
            }
            if (fileOutputStream != null) {
                fileOutputStream.close();
            }
            if (fileInputStream != null) {
                fileInputStream.close();
            }
        }

        //字符流
        File file2 = new File("C:\\Users\\yutao\\Desktop\\person.txt");
        File file3 = new File("C:\\Users\\yutao\\Desktop\\person1.txt");
        FileReader fileReader = null;
        FileWriter fileWriter = null;
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;
        try {
            fileReader = new FileReader(file2);
            fileWriter = new FileWriter(file3);
            bufferedReader = new BufferedReader(fileReader);
            bufferedWriter = new BufferedWriter(fileWriter);
            //逐字符读写
           /* int ch = 0;
            while ((ch = bufferedReader.read()) != -1) {
                bufferedWriter.write((char)ch);
            }*/
            //字符缓冲区读写
            /*char[] chars = new char[1024];
            int length = 0;
            while ((length = bufferedReader.read(chars)) != -1) {
                bufferedWriter.write(chars,0,length);
            }*/
            //逐行读写
            String str = "";
            while ((str = bufferedReader.readLine()) != null) {
                bufferedWriter.write(str);
                bufferedWriter.newLine();
                bufferedWriter.flush();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bufferedReader != null) {
                bufferedReader.close();
            }
            if (bufferedWriter != null) {
                bufferedWriter.close();
            }
            if (fileReader != null) {
                fileReader.close();
            }
            if (fileWriter != null) {
                fileWriter.close();
            }
        }
    }

    /**
     * 对象流(处理流):
     *          ObjectInputStream   ObjectOutputStream
     * @desc 用于序列化与反序列化 只有实现了Serializable接口才可以被序列化,static和transient修饰的变量不会被序列化
     */
    public void testObject() throws IOException {
        //将对象与字节数组间序列化与反序列化
        ByteArrayInputStream byteArrayInputStream = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        ObjectInputStream ois = null;
        ObjectOutputStream oos = null;
        Person person = new Person("linux",23);
        try {
            //序列化
            byteArrayOutputStream = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(byteArrayOutputStream);
            oos.writeObject(person);
            byte[] b = byteArrayOutputStream.toByteArray();
            //反序列化
            byteArrayInputStream = new ByteArrayInputStream(b);
            ois = new ObjectInputStream(byteArrayInputStream);
            Person p = (Person)ois.readObject();
            System.out.println(p.toString());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            if (ois != null) {
                ois.close();
            }
            if (oos != null) {
                oos.close();
            }
            if (byteArrayInputStream != null) {
                byteArrayInputStream.close();
            }
            if (byteArrayOutputStream != null) {
                byteArrayOutputStream.close();
            }
        }
    }
    /**
     * 转换流(处理流):
     *          InputStreamReader   :实现字节流到字符流的转换,提高操作的效率(前提是,数据是文本文件)
     *          OutputStreamWriter  :实现字符流到字节流的转换
     * @desc 实现字节流与字符流之间的转换,
     */
    @Test
    public void testByteToChar() throws IOException {
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        InputStreamReader inputStreamReader = null;
        OutputStreamWriter outputStreamWriter = null;
        try {
            fileInputStream = new FileInputStream("C:\\Users\\yutao\\Desktop\\person.txt");
            fileOutputStream = new FileOutputStream("C:\\Users\\yutao\\Desktop\\person1.txt");
            inputStreamReader = new InputStreamReader(fileInputStream,"utf-8");
            outputStreamWriter = new OutputStreamWriter(fileOutputStream,"utf-8");
            char[] chars = new char[1000];
            int len = 0;
            while ((len = inputStreamReader.read(chars)) != -1) {
                outputStreamWriter.write(chars,0,len);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (outputStreamWriter != null) {
                outputStreamWriter.close();
            }
            if (inputStreamReader != null) {
                inputStreamReader.close();
            }
            if (fileOutputStream != null) {
                fileOutputStream.close();
            }
            if (fileInputStream != null) {
                fileInputStream.close();
            }
        }
    }

    /**
     * 打印流
     *          PrintStream      PrintWriter
     */
    @Test
    public void testPrint() throws IOException {
        //打印字节流(标准输出流)
        FileOutputStream fileOutputStream = null;
        PrintStream printStream = null;
        try {
            fileOutputStream = new FileOutputStream(new File("C:\\Users\\yutao\\Desktop\\person4.txt"));
            printStream = new PrintStream(fileOutputStream);
            //修改标准输出流
            System.setOut(printStream);
            System.out.println("我是一只小小鸟");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (printStream != null) {
                printStream.close();
            }
            if (fileOutputStream != null) {
                fileOutputStream.close();
            }
        }

        //打印字符流
        FileOutputStream fileOutputStream1 = null;
        PrintWriter printWriter = null;
        try {
            fileOutputStream1 = new FileOutputStream("C:\\Users\\yutao\\Desktop\\person4.txt");
            printWriter = new PrintWriter(fileOutputStream1);
            //支持int long char boolean float double String char[] Object
            printWriter.print(1);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (printWriter != null) {
                printWriter.close();
            }
            if (fileOutputStream1 != null) {
                fileOutputStream1.close();
            }
        }
    }
    /**
     * 数据流
     *          DataInputStream     DataOutputStream
     *@desc 支持基本的数据类型
     */
    @Test
    public void testData() throws IOException {
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        DataInputStream dataInputStream = null;
        DataOutputStream dataOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream("C:\\Users\\yutao\\Desktop\\person5.txt");
            dataOutputStream = new DataOutputStream(fileOutputStream);
            fileInputStream = new FileInputStream("C:\\Users\\yutao\\Desktop\\person5.txt");
            dataInputStream = new DataInputStream(fileInputStream);

            //支持基本数据类型的write,byte[] boolean String short int long float double char char[]
            //并且支持使用UTF-8编码String
            byte[] bytes = {1,2,5,7,6};
            dataOutputStream.write(3);
            dataOutputStream.write(bytes);
            dataOutputStream.write(bytes,1,3);
            dataOutputStream.writeBoolean(true);
            dataOutputStream.writeByte(7);
            dataOutputStream.writeBytes("张三");
            dataOutputStream.writeChars("lisi");
            dataOutputStream.writeChar('c');
            dataOutputStream.writeDouble(23.1);
            dataOutputStream.writeFloat(2.3f);
            dataOutputStream.writeShort((short)2);
            dataOutputStream.writeInt(4);
            dataOutputStream.writeLong(2L);
            dataOutputStream.writeUTF("呵呵呵");
            dataOutputStream.flush();

            int len = 0;
            byte[] b = new byte[1024];
            while ((len = dataInputStream.read(b)) != -1) {
                System.out.println(new String(b, 0, len));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (dataOutputStream != null) {
                dataOutputStream.close();
            }
            if (fileOutputStream != null) {
                fileOutputStream.close();
            }
            if (dataInputStream != null) {
                dataInputStream.close();
            }
            if (fileInputStream != null) {
                fileInputStream.close();
            }
        }
    }
}

本地中文文档复制可能会出现乱码,这是因为Windows中txt文件默认编码是ANSI,java中不支持该编码,可以更改本地文件的编码格式。上述的示例也很自然的让我们想到了Decrotator(装饰器)模式

IO工作机制

磁盘IO工作机制

Java IO对磁盘的读写都是针对文件而言的,通过File类来表示:

File file = new File("D:/temp/temp.txt")

但是File并不表示一个真实的存在磁盘上的文件,它只是通过文件描述符返回某一路径的虚拟对象,只有当我们试图使用FileInputStream对象去操作文件时才会真正去创建一个关联真实存在的磁盘文件的文件描述符FileDescriptor,FileDescriptor是真正指向了一个打开的文件。
这里写图片描述

Socket工作机制

Socket位于应用层和传输层之间,是应用程序的网络编程接口(API),Java对Socket的支持有以下二种:

  1. 基于TCP的Socket:提供给应用层可靠的流式数据服务,使用TCP的Socket应用程序协议:BGP,HTTP,FTP,TELNET等。优点:有连接,可靠性的数据传输。
  2. 基于UDP的Socket:适用于数据传输可靠性要求不高的场合。基于UDP的Socket应用程序协议:RIP,SNMP,L2TP等。

这里写图片描述

客户端:客户端对服务端发起连接请求,只有当三次握手成功后Socket的构造函数才会成功返回Socket实例,互联网是一种尽力而为(best-effort)的网络,客户端的起始消息或服务器端的回复消息都可能在传输过程中丢失。出于这个原因,TCP 协议实现将以递增的时间间隔重复发送几次握手消息。如果TCP客户端在一段时间后还没有收到服务器的返回消息,则发生超时并放弃连接。这种情况下,构造函数将抛出IOException 异常。

Socket socket = new Socket("127.0.0.1",10000);

服务端:当有客户端请求到来时,将为这个链接创建一个套接字数据结构,包括请求客户端的地址和端口号。该数据结构将被关联到ServerSocket实例的一个未连接列表里。此时连接并没有成功建立,处于三次握手阶段,Socket构造函数并未成功返回。当三次握手成功后,会将Socket实例对应的数据结构从未完成列表移到完成列表中

IO的阻塞与非阻塞、同步与异步

IO操作其实分成了两个步骤:发起IO请求和实际的IO操作。
《Unix网络编程卷》中的理论:
IO操作中涉及的2个主要对象为程序进程、系统内核。以读操作为例,当一个IO读操作发生时,通常经历两个步骤:

  1. 等待数据准备
  2. 将数据从系统内核拷贝到操作进程中(由内核态到用户态)

例如,在Socket通讯中,步骤1会等到网络数据包到达,到达后会拷贝到系统内核的缓冲区;步骤2会将数据包从内核缓冲区拷贝到程序进程的缓冲区中。

阻塞(blocking)与非阻塞(non-blocking)IO

阻塞IO和非阻塞IO的区别在于第一步,发起IO请求是否会被阻塞。比如在socket读操作时需要准备数据,阻塞IO会一直等待准备数据完成或得知暂时不可操作时一直等待;而非阻塞IO会在发出IO请求后立即得到回应,即使数据包没有准备好,也会返回一个错误标识,使得操作进程不会阻塞在那里。操作进程可以通过多次请求的方式确认数据准备好了,返回成功的标识。

同步(synchronous)与异步(asynchronous)IO

同步IO和异步IO的区别就在于第二个步骤是否阻塞,如果实际的IO读写阻塞请求进程,那么就是同步IO;当一个异步过程调用发出后,调用者不能立刻得到结果,操作系统在处理完IO操作后,通过状态、通知和回调函数来通知调用者。

两种方式的组合

组合毫无疑问是四种:同步阻塞、同步非阻塞、异步阻塞、异步非阻塞。

同步阻塞IO

public class testSyncBlock {
    /**
     * 同步阻塞IO
     * 单线程:阻塞整个进程,同步阻塞最常用的一种用法,使用也是最简单的,但是 I/O 性能一般很差,CPU 大部分在空闲状态
     */
    @Test
    public void testSyncBlocking() throws IOException {
        ServerSocket serverSocket = new ServerSocket(10000);
        Socket socket = null;
        while (true) {
            try {
                socket = serverSocket.accept();
                System.out.println("socket连接:" + socket.getRemoteSocketAddress().toString());
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                while (true) {
                    String readLine = in.readLine();        //同步阻塞IO
                    System.out.println("收到消息" + readLine);
                    if ("end".equals(readLine)) {
                        break;
                    }
                    //客户端断开连接
                    socket.sendUrgentData(0xFF);
                }
            } catch (SocketException se) {
                System.out.println("客户端断开连接");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                System.out.println("socket关闭:" + socket.getRemoteSocketAddress().toString());
                socket.close();
            }
        }
    }
}

上述示例是单线程的,当二个客户端请求连接时,只有第一个客户端断开连接后,第二个客户端才会连接上。可以使用SocketTest或Hercules等工具测试多客户端连接。无论是使用长连接还是短连接进程都会在数据写入OutputStream或InputStream读取时阻塞,这对于大规模的访问量是不能接受的,通常解决这个问题的方法是使用多线程技术,每个客户端都会创建一个线程,出现阻塞时也只会是一个线程阻塞,出现阻塞时只是一个线程阻塞而不会影响其它线程工作。使用线程池可以减少系统线程的开销。

public class testSyncBlock {
   /**
    * 多线程Socket通讯
    **/
    @Test
    public void testMultiThreadSocket() throws IOException, InterruptedException {
        ServerSocket serverSocket = new ServerSocket(11000);
        Thread thread = new Thread(new Acceptor(serverSocket));
        thread.start();
        Scanner scanner = new Scanner(System.in);
        scanner.next();
    }
    class Acceptor implements Runnable {
        private ServerSocket serverSocket;
        public Acceptor(ServerSocket serverSocket) {
            this.serverSocket = serverSocket;
        }

        @Override
        public void run() {
            while (true) {
                Socket socket = null;
                try {
                    socket = this.serverSocket.accept();    //线程阻塞
                    if (socket != null) {
                        System.out.println("socket连接:" + socket.getRemoteSocketAddress().toString());
                        Thread thread = new Thread(new Proccesor(socket));
                        thread.start();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    class Proccesor implements Runnable {
        private Socket socket = null;
        public Proccesor(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try {
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                while (true) {
                    String readLine = in.readLine();
                    System.out.println("收到消息:" + readLine);
                    if ("end".equals(readLine)) {
                        break;
                    }
                    //客户端断开连接
                    socket.sendUrgentData(0xFF);
                }
            } catch (SocketException s) {
                System.out.println("客户端断开连接");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                System.out.println("socket关闭:" + socket.getRemoteSocketAddress().toString());
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

上述连接若是长连接,对于几百万的高并发是及其消耗资源的,同时多个线程访问服务端的某些竞争资源也需要进行同步操作。

Java NIO

传统的Java IO是面向流的I/O,流I/O一次处理一个字节,即使传统的Java IO中也提出了缓冲流(BufferedInputStream等),但是移进/移出的操作是由程序员来包装的,它本质是对数据结构化和积累达到处理时的方便,并不是一种提高I/O效率的措施。
Java NIO是面向缓冲区的I/O,缓冲区作为传输数据的基本单位块,对缓冲区的移进/移出是由底层操作系统来实现的,进程对底层操作系统发出I/O操作,操作系统会根据要求将数据缓冲区填满或排干。

IO NIO
面向流(Stream Oriented 面向缓冲区(BUffer Oriented
阻塞IO(Blocking IO) 非阻塞IO(Non Blocking IO)
选择器(Selectors

Buffer And Channel

Java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取
用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。

Buffer(缓冲区)

Buffer持有一个数组,可以保存多个相同类型的数据,主要有:

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

都有着相似的方法进行数据的管理,唯一区别在于管理的数据类型不同而已。

public abstract class BufferAPI {
    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;      //标记: 标记一个索引。调用mark()方法将会设定mark = position,调用reset()方法将设定position = mark。
    private int position = 0;   //位置: 缓冲区下一个读或写的元素索引
    private int limit;          //上界: 缓冲区第一个不能被读或写的元素索引,也就是数据的上限位置,这个位置以后即便有数据,也是不能够访问的
    private int capacity;       //容量: 能够容纳数据元素的最大数量,在缓冲区创建时指定并且不能更改

    public final int capacity();                        //返回capacity值
    public final int position();                        //返回position值
    public final Buffer position(int newPosition);      //设置新的position值
    public final int limit();                           //返回limit值
    public final Buffer limit(int newLimit);            //设置新的limit值
    public final Buffer mark();                         //标记位置 mark = position
    public final Buffer reset();                        //返回标记位置 position = mark
    public final Buffer clear();                        //重置缓冲区的属性到新建时的状态,不会清除数据
    public final Buffer flip();                         //缓冲区翻转,用于读和写的切换
    public final Buffer rewind();                       //重置缓冲区position和mark属性,可用于重复读
    public final int remaining();                       //返回缓冲区可读或写的元素数量
    public final boolean hasRemaining();                //缓冲区是否还有可读或写的元素
    public abstract boolean isReadOnly();               //缓冲区是否是只读的
    public abstract boolean isDirect();                 //缓冲区是否是直接缓冲区
}

Buffer的存取

@Test
public void  testBuffer{
        //获取Byte非直接缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //写数据
        String str = "abcde";
        buffer.put(str.getBytes());
        //切换读写状态
        buffer.flip();
        //读取数据
        byte[] bytes = new byte[buffer.limit()];
        buffer.get(bytes,0,2);
        //标记
        buffer.mark();
        //恢复到mark位置
        buffer.reset();
        //判断缓冲区中剩余的数据个数
        if (buffer.hasRemaining()) {
            System.out.println("剩余:"+buffer.remaining());
        }
        buffer.put((byte)1);
        //可重复读
        buffer.rewind();
        //清空缓冲区,缓冲区中数据依然存在,但是处于“被遗忘状态”
        buffer.clear();
    }

直接缓冲区与非直接缓冲区

普通IO处理流程:
这里写图片描述
由于DMA不能直接访问用户空间(用户缓冲区),普通IO操作需要将数据来回地在用户缓冲区和内核缓冲区移动,这在一定程度上影响了IO的速度,针对这个问题Java提出NIO中的内存映射文件的解决方案。

非直接缓冲区

这里写图片描述

直接缓冲区

通过allocateDirect方法可以获取直接缓冲区
这里写图片描述
主要特点:

  1. 对文件的操作不需要再发read 或者 write 系统调用了。
  2. 当用户进程访问“内存映射文件”地址时,自动产生缺页错误,然后由底层的OS负责将磁盘上的数据送到内存。

Channel

这里写图片描述
java.nio.channels.Channel接口主要实现类:

  • FileChannel:用于读取、写入、映射和操作文件的通道
  • SocketChannel:通过 TCP 读写网络中的数据
  • ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来
    的连接都会创建一个 SocketChannel
  • DatagramChannel:通过 UDP 读写网络中的数据通道

获取Channel方式:

  1. Java针对支持通道的类提供了getChannel()方法,有
    本地IO:FileInputStream/FileOutputStream、RandomAccessFile
    网络IO:Socket、ServerSocket、DatagramSocket
  2. 在 JDK 1.7 中的NIO2针对各个通道提供了静态方法open()
  3. 在 JDK 1.7 中的NIO2的Files工具类的newByteChannel()

NIO与NIO2

NIO与NIO2的使用

public class TestBlockingNIO {

    //客户端
    @Test
    public void client() throws IOException{
        //1. 获取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));

        FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);

        //2. 分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);

        //3. 读取本地文件,并发送到服务端
        while(inChannel.read(buf) != -1){
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }

        //4. 关闭通道
        inChannel.close();
        sChannel.close();
    }

    //服务端
    @Test
    public void server() throws IOException{
        //1. 获取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();

        FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

        //2. 绑定连接
        ssChannel.bind(new InetSocketAddress(9898));

        //3. 获取客户端连接的通道
        SocketChannel sChannel = ssChannel.accept();

        //4. 分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        System.out.println(sChannel.getRemoteAddress());
        //5. 接收客户端的数据,并保存到本地
        while(sChannel.read(buf) != -1){
            buf.flip();
            outChannel.write(buf);
            buf.clear();
        }
        System.out.println("完成传输");
        //6. 关闭通道
        sChannel.close();
        outChannel.close();
        ssChannel.close();
    }
    //客户端
    @Test
    public void client1() throws IOException{
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));

        FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);

        ByteBuffer buf = ByteBuffer.allocate(1024);

        while(inChannel.read(buf) != -1){
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }

        sChannel.shutdownOutput();

        //接收服务端的反馈
        int len = 0;
        while((len = sChannel.read(buf)) != -1){
            buf.flip();
            System.out.println(new String(buf.array(), 0, len));
            buf.clear();
        }

        inChannel.close();
        sChannel.close();
    }

    //服务端
    @Test
    public void server1() throws IOException, InterruptedException {
        ServerSocketChannel ssChannel = ServerSocketChannel.open();

        FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

        ssChannel.bind(new InetSocketAddress(9898));

        SocketChannel sChannel = ssChannel.accept();

        ByteBuffer buf = ByteBuffer.allocate(1024);

        while(sChannel.read(buf) != -1){
            buf.flip();
            outChannel.write(buf);
            buf.clear();
        }

        //发送反馈给客户端
        buf.put("服务端接收数据成功".getBytes());
        buf.flip();
        sChannel.write(buf);
        sChannel.shutdownOutput();
        Thread.sleep(10000);
        sChannel.close();
        outChannel.close();
        ssChannel.close();
    }
}

选择器(Selector)

选择器(Selector) 是 SelectableChannle 对象的多路复用器,Selector 可以同时监控多个 SelectableChannel 的 IO 状况,也就是说,利用 Selector可使一个单独的线程管理多个 Channel。Selector 是非阻塞 IO 的核心。

//创建一个Socket套接字
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9898);
//获取SocketChannel
SocketChannel Channel = socket.getChannel();
//创建选择器
Selector selector = Selector.open();
//将SocketChannel切换到非阻塞模式
channel.configureBlocking(false);
//向Selector注册Channel
SelectionKey key = channel.register(selector,SelectionKey.OP_READ);
//注册多个事件
int interestSet = SelectionKey.OP_READ|SelectionKey.OP_WRITE;
channel.register((selector,interestSet);

当调用 register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数来指定,可以监听的事件类型(可使用SelectionKey 的四个常量表示):

  • 读 : SelectionKey.OP_READ (1)
  • 写 : SelectionKey.OP_WRITE (4)
  • 连接 : SelectionKey.OP_CONNECT (8)
  • 接收 : SelectionKey.OP_ACCEPT (16)

NIO的阻塞与非阻塞都是针对网络通信而言

/*
 * 一、使用 NIO 完成网络通信的三个核心:
 * 
 * 1. 通道(Channel):负责连接
 *      
 *     java.nio.channels.Channel 接口:
 *          |--SelectableChannel
 *              |--SocketChannel
 *              |--ServerSocketChannel
 *              |--DatagramChannel
 * 
 *              |--Pipe.SinkChannel
 *              |--Pipe.SourceChannel
 * 
 * 2. 缓冲区(Buffer):负责数据的存取
 * 
 * 3. 选择器(Selector):是 SelectableChannel 的多路复用器。用于监控 SelectableChannel 的 IO 状况
 * 
 */
public class TestNonBlockingNIO {

    //客户端
    @Test
    public void client() throws IOException{
        //1. 获取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));

        //2. 切换非阻塞模式
        sChannel.configureBlocking(false);

        //3. 分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);

        //4. 发送数据给服务端
        Scanner scan = new Scanner(System.in);

        while(scan.hasNext()){
            String str = scan.next();
            buf.put((new Date().toString() + "\n" + str).getBytes());
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }

        //5. 关闭通道
        sChannel.close();
    }

    //服务端
    @Test
    public void server() throws IOException{
        //1. 获取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();

        //2. 切换非阻塞模式
        ssChannel.configureBlocking(false);

        //3. 绑定连接
        ssChannel.bind(new InetSocketAddress(9898));

        //4. 获取选择器
        Selector selector = Selector.open();

        //5. 将通道注册到选择器上, 并且指定“监听接收事件”
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);

        //6. 轮询式的获取选择器上已经“准备就绪”的事件
        while(selector.select() > 0){

            //7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();

            while(it.hasNext()){
                //8. 获取准备“就绪”的是事件
                SelectionKey sk = it.next();

                //9. 判断具体是什么事件准备就绪
                if(sk.isAcceptable()){
                    //10. 若“接收就绪”,获取客户端连接
                    SocketChannel sChannel = ssChannel.accept();

                    //11. 切换非阻塞模式
                    sChannel.configureBlocking(false);

                    //12. 将该通道注册到选择器上
                    sChannel.register(selector, SelectionKey.OP_READ);
                }else if(sk.isReadable()){
                    //13. 获取当前选择器上“读就绪”状态的通道
                    SocketChannel sChannel = (SocketChannel) sk.channel();

                    //14. 读取数据
                    ByteBuffer buf = ByteBuffer.allocate(1024);

                    int len = 0;
                    while((len = sChannel.read(buf)) > 0 ){
                        buf.flip();
                        System.out.println(new String(buf.array(), 0, len));
                        buf.clear();
                    }
                }

                //15. 取消选择键 SelectionKey
                it.remove();
            }
        }
    }
}

NIO.2 – Path、Paths、Files

随着 JDK 7 的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为 NIO.2。因为 NIO 提供的一些功能,NIO已经成为文件处理中越来越重要的部分。

Path与Paths

  • java.nio.file.Path 接口代表一个平台无关的平台路径,描述了目录结构中文件的位置。
  • Paths 提供的 get() 方法用来获取 Path 对象:
    • Path get(String first, String … more) : 用于将多个字符串串连成路径。
  • Path 常用方法:
    • boolean endsWith(String path) : 判断是否以 path 路径结束
    • boolean startsWith(String path) : 判断是否以 path 路径开始
    • boolean isAbsolute() : 判断是否是绝对路径
    • Path getFileName() : 返回与调用 Path 对象关联的文件名
    • Path getName(int idx) : 返回的指定索引位置 idx 的路径名称
    • int getNameCount() : 返回Path 根目录后面元素的数量
    • Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
    • Path getRoot() :返回调用 Path 对象的根路径
    • Path resolve(Path p) :将相对路径解析为绝对路径
    • Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
    • String toString() : 返回调用 Path 对象的字符串表示形式

Files

  • java.nio.file.Files 用于操作文件或目录的工具类。
  • Files常用方法:

    • Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
    • Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
    • Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
    • void delete(Path path) : 删除一个文件
    • Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
    • long size(Path path) : 返回 path 指定文件的大小
  • Files常用方法:用于判断

    • boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
    • boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
    • boolean isExecutable(Path path) : 判断是否是可执行文件
    • boolean isHidden(Path path) : 判断是否是隐藏文件
    • boolean isReadable(Path path) : 判断文件是否可读
    • boolean isWritable(Path path) : 判断文件是否可写
    • boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在
  • Files常用方法:用于操作内容
    • SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,how 指定打开方式。
    • DirectoryStream newDirectoryStream(Path path) : 打开 path 指定的目录
    • InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象

自动资源管理

Java 7 增加了一个新特性,该特性提供了另外一种管理资源的方式,这种方式能自动关闭文件。这个特性有时被称为自动资源管理(Automatic Resource Management, ARM), 该特性以 try 语句的扩展版为基础。自动资源管理主要用于,当不再需要文件(或其他资源)时,可以防止无意中忘记释放它们。
try(需要关闭的资源声明){
//可能发生异常的语句
}catch(异常类型 变量名){
//异常的处理语句
}
……
finally{
//一定执行的语句
}

 @Test
 public void test(){
        try(FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
            FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE)){

            ByteBuffer buf = ByteBuffer.allocate(1024);
            inChannel.read(buf);

        }catch(IOException e){

        }
    }

猜你喜欢

转载自blog.csdn.net/yutao_struggle/article/details/80092923
今日推荐