3.4. 文件与 I\O 流

3.4.1. Java 中 IO 流分为几种?

  • 按照流的流向分,可以分为输入流和输出流;
  • 按照操作单元划分,可以划分为字节流和字符流;
  • 按照流的角色划分为节点流和处理流。

Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

按操作方式分类结构图:

IO-操作方式分类

按操作对象分类结构图:

IO-操作对象分类

3.4.1.1. 既然有了字节流,为什么还要有字符流?

问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?

回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。

3.4.1.2. BIO,NIO,AIO 有什么区别?
  • BIO (Blocking I/O): 同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机 1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。

bio实现文件读写

/**
    * BIO模式
    * FileReader逐个字符读取文件,FileReader extends InputStreamReader
    * 读取文件中内容到字符数组中
    * 如何确定字符数组长度:
    * FileReader不能自定义编码读取
    * 此方法也可以用于读取二进制文件,只不过读取出来有很多乱码
    * @param fileName
    * @return
    * @throws IOException
    */
public static char[] readByOneCharWithDefaultEncoding(String fileName) throws IOException{
    File file = new File(fileName);
    FileReader fileReader = new FileReader(file); // 不能自定义编码,内部默认采用系统的编码
    System.out.println("当前采用编码: " + fileReader.getEncoding()); 
    char[] charcters = new char[1024];
    int result = fileReader.read();  // 逐个字符读取,不能按行读取
    int i = 0;
    while(result != -1 && i < 1024){
        char temp = (char)result;
        charcters[i] = temp;
        i++;
        result = fileReader.read();
    }
    fileReader.close();
    return charcters;
}

bio服务器

//BIO服务端源码
public final class ServerNormal {
    //默认的端口号
    private static int DEFAULT_PORT = 12345;
    //单例的ServerSocket
    private static ServerSocket server;
    //根据传入参数设置监听端口,如果没有参数调用以下方法并使用默认值
    public static void start() throws IOException{
        //使用默认值
        start(DEFAULT_PORT);
    }
    //这个方法不会被大量并发访问,不太需要考虑效率,直接进行方法同步就行了
    public synchronized static void start(int port) throws IOException{
        if(server != null) return;
        try{
            //通过构造函数创建ServerSocket
            //如果端口合法且空闲,服务端就监听成功
            server = new ServerSocket(port);
            System.out.println("服务器已启动,端口号:" + port);
            //通过无线循环监听客户端连接
            //如果没有客户端接入,将阻塞在accept操作上。
            while(true){
                Socket socket = server.accept();
                //当有新的客户端接入时,会执行下面的代码
                //然后创建一个新的线程处理这条Socket链路
                new Thread(new ServerHandler(socket)).start();
            }
        }finally{
            //一些必要的清理工作
            if(server != null){
                System.out.println("服务器已关闭。");
                server.close();
                server = null;
            }
        }
    }
}

bio的服务处理handler

/**
 * 客户端线程
 */
public class ServerHandler implements Runnable{
    private Socket socket;
    public ServerHandler(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        BufferedReader in = null;
        PrintWriter out = null;
        try{
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(),true);
            String expression;
            String result;
            while(true){
                //通过BufferedReader读取一行
                //如果已经读到输入流尾部,返回null,退出循环
                //如果得到非空值,就尝试计算结果并返回
                if((expression = in.readLine())==null) break;
                System.out.println("服务器收到消息:" + expression);
                try{
                    result = Calculator.cal(expression).toString();
                }catch(Exception e){
                    result = "计算错误:" + e.getMessage();
                }
                out.println(result);
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            //一些必要的清理工作
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                in = null;
            }
            if(out != null){
                out.close();
                out = null;
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                socket = null;
            }
        }
    }
}

bio客户端

/**
 * 阻塞式I/O创建的客户端
 */
public class Client {
    //默认的端口号
    private static int DEFAULT_SERVER_PORT = 12345;
    private static String DEFAULT_SERVER_IP = "127.0.0.1";
    public static void send(String expression){
        send(DEFAULT_SERVER_PORT,expression);
    }
    public static void send(int port,String expression){
        System.out.println("算术表达式为:" + expression);
        Socket socket = null;
        BufferedReader in = null;
        PrintWriter out = null;
        try{
            socket = new Socket(DEFAULT_SERVER_IP,port);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(),true);
            out.println(expression);
            System.out.println("___结果为:" + in.readLine());
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            //一下必要的清理工作
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                in = null;
            }
            if(out != null){
                out.close();
                out = null;
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                socket = null;
            }
        }
    }
}
  • NIO (Non-blocking/New I/O): NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 SocketServerSocket 相对应的 SocketChannelServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发

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

    • Channels
    • Buffers:与channel可以相互存取数据
    • Selectors:允许单线程处理多个 Channel

    常用的channel

    • FileChannel 从文件中读写数据。
    • DatagramChannel 能通过 UDP 读写网络中的数据。
    • SocketChannel 能通过 TCP 读写网络中的数据。
    • ServerSocketChannel 可以监听TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个 SocketChannel。
    • DatagramChannel是一个能收发UDP包的通道。因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。

    nio实现文件读写

    nio实现简单服务器

    nio实现客户端

  • AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO 操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。

results matching ""

    No results matching ""