java——IO与NIO

2023年 8月 16日 55.4k 0

@[TOC]

Java中的IO(输入输出)是用于在程序中读取和写入数据的一种机制。Java提供了两种不同的IO模型:传统的IO模型和NIO(New IO)模型。

1. 传统IO模型

在传统的IO模型中,输入和输出是通过字节流或字符流进行处理的。字节流是以8位字节为单位读写数据,而字符流则是以16位字符为单位读写数据。常见的字节流包括InputStream和OutputStream,而常见的字符流包括Reader和Writer。

另外,在传统的IO模型中,还有缓冲流,它们可以提高IO的效率。BufferedInputStream和BufferedOutputStream是字节流的缓冲流,而BufferedReader和BufferedWriter则是字符流的缓冲流。

传统IO模型的优点在于它简单易用,而缺点则是它的效率较低,特别是在处理大量数据时。

以下是一个简单的使用传统IO模型进行文件读写的Java代码示例:

import java.io.*;

public class TraditionalIOExample {

    public static void main(String[] args) {
        File inputFile = new File("input.txt");
        File outputFile = new File("output.txt");

        try {
            // 使用字节流读取文件
            FileInputStream fis = new FileInputStream(inputFile);
            // 使用字节流写入文件
            FileOutputStream fos = new FileOutputStream(outputFile);

            // 使用缓冲流提高效率
            BufferedInputStream bis = new BufferedInputStream(fis);
            BufferedOutputStream bos = new BufferedOutputStream(fos);

            // 读取数据并写入文件
            int len;
            byte[] buffer = new byte[1024];
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }

            // 关闭流
            bos.close();
            bis.close();
            fos.close();
            fis.close();

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

在这个例子中,我们首先创建了一个File对象,指定了输入文件和输出文件。然后使用FileInputStreamFileOutputStream来创建字节流,对文件进行读写操作。为了提高效率,我们还使用了缓冲流BufferedInputStreamBufferedOutputStream,将其包装在字节流之上。

接下来,在while循环中,我们不断从输入文件中读取数据,并将其写入到输出文件中。最后,我们关闭所有的流以释放资源。如果在读写过程中发生异常,我们就在catch块中捕获并打印异常信息。

这是一个简单的使用传统IO模型进行文件读写的例子,但实际上,传统IO模型还可以用于网络编程、对象序列化等方面。
在这里插入图片描述

字节流

字节流是Java IO中的一种流,它以字节为单位进行读写操作,用于处理二进制数据,如图像、音频等。字节流主要有两种类型:InputStream和OutputStream,分别用于从输入流中读取字节和向输出流中写入字节。

InputStream是所有字节输入流的超类,它定义了读取字节的基本方法,如read()、read(byte[] b)、read(byte[] b, int off, int len)等。其中,read()方法每次读取一个字节,返回一个整数表示实际读取的字节数,如果已经读到流的末尾,则返回-1。read(byte[] b)方法会尝试从输入流中读取b.length个字节,并将其存储在字节数组b中,返回值为实际读取的字节数。read(byte[] b, int off, int len)方法与read(byte[] b)类似,只不过指定了起始偏移量off和读取长度len。

OutputStream是所有字节输出流的超类,它定义了写入字节的基本方法,如write(int b)、write(byte[] b)、write(byte[] b, int off, int len)等。其中,write(int b)方法会将指定的字节写入输出流,write(byte[] b)方法会将字节数组b中的所有字节写入输出流,write(byte[] b, int off, int len)方法则只写入数组b中从偏移量off开始的len个字节。

除了基本的读写方法,字节流还提供了一些其他的常用方法。例如,InputStream中的available()方法返回可以从输入流中读取的估计字节数,mark(int readlimit)方法在输入流中标记当前位置,reset()方法将输入流重新定位到最后一次标记的位置,skip(long n)方法跳过指定的字节数等。而OutputStream中也有类似的方法,如flush()方法将输出流缓冲区的内容强制刷新到目标设备上,close()方法关闭流等。

下面是一个简单的示例代码,演示如何使用字节流进行文件读写操作:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ByteStreamDemo {
    public static void main(String[] args) throws IOException {
        // 从文件中读取数据,写入到另一个文件中
        FileInputStream fis = new FileInputStream("source.bin");
        FileOutputStream fos = new FileOutputStream("dest.bin");

        byte[] buffer = new byte[1024];
        int len;

        while ((len = fis.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }

        fis.close();
        fos.close();
    }
}

上述代码通过创建FileInputStream和FileOutputStream来实现文件的读写操作,使用read()方法从源文件中读取字节,并使用write()方法将字节写入目标文件中。注意,在使用完流之后,需要及时调用close()方法关闭流。

字符流

字符流是Java IO中的一种流,它以字符为单位进行读写操作,用于处理文本数据,如文本文件、XML等。Java字符流主要有两种类型:Reader和Writer,分别用于从输入流中读取字符和向输出流中写入字符。

Reader是所有字符输入流的超类,它定义了读取字符的基本方法,如read()、read(char[] cbuf)、read(char[] cbuf, int off, int len)等。其中,read()方法每次读取一个字符,返回一个整数表示实际读取的字符数,如果已经读到末尾,则返回-1;read(char[] cbuf)方法会尝试从输入流中读取cbuf.length个字符,并将其存储在字符数组cbuf中,返回值为实际读取的字符数;read(char[] cbuf, int off, int len)方法与read(char[] cbuf)类似,只不过指定了起始偏移量off和读取长度len。

Writer是所有字符输出流的超类,它定义了写入字符的基本方法,如write(int c)、write(char[] cbuf)、write(String str)等。其中,write(int c)方法会将指定的字符写入输出流;write(char[] cbuf)方法会将字符数组cbuf中的所有字符写入输出流;write(String str)方法会将字符串str中的所有字符写入输出流。

除了基本的读写方法,字符流也提供了一些其他的常用方法。例如,Reader中的skip(long n)方法跳过n个字符,mark(int readAheadLimit)方法在输入流中标记当前位置,reset()方法将输入流重新定位到最后一次标记的位置等;而Writer中的flush()方法将输出流缓冲区的内容强制刷新到目标设备上,close()方法关闭流等。

下面是一个简单的示例代码,演示如何使用字符流进行文件读写操作:

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CharacterStreamDemo {
    public static void main(String[] args) throws IOException {
        // 从文件中读取数据,写入到另一个文件中
        FileReader fr = new FileReader("source.txt");
        FileWriter fw = new FileWriter("dest.txt");

        char[] buffer = new char[1024];
        int len;

        while ((len = fr.read(buffer)) != -1) {
            fw.write(buffer, 0, len);
        }

        fr.close();
        fw.close();
    }
}

上述代码通过创建FileReader和FileWriter来实现文件的读写操作,使用read()方法从源文件中读取字符,并使用write()方法将字符写入目标文件中。注意,在使用完流之后,需要及时调用close()方法关闭流。

2. NIO模型

NIO是Java 1.4引入的新IO模型,它的目标是提高IO的效率,特别是在处理大量数据时。NIO的设计中引入了三个重要的概念:通道、缓冲区和选择器。

通道是NIO中的抽象概念,它类似于传统IO模型中的流。但是,通道可以同时进行读写操作,并且可以使用选择器来实现多路复用,从而提高效率。

缓冲区是NIO中的另一个重要概念,它用于存储读取或写入的数据。与传统IO模型不同的是,NIO中的缓冲区可以直接与通道交互,从而避免了频繁地进行字节或字符的拷贝操作,提高了效率。

选择器是NIO中的另一个重要概念,它可以监听多个通道上的事件并在有事件发生时及时地处理它们。这样,一个线程就可以同时处理多个通道上的IO操作,从而避免了线程阻塞等问题,提高了系统的效率和可扩展性。

下面是一个基于NIO模型的简单示例代码:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NioServer {
    private Selector selector;

    public void init() throws IOException {
        // 创建一个选择器
        selector = Selector.open();

        // 创建一个ServerSocketChannel并绑定到指定端口
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8888));
        serverSocketChannel.configureBlocking(false);

        // 将ServerSocketChannel注册到选择器上,监听ACCEPT事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    public void start() throws IOException {
        while (true) {
            // 阻塞等待就绪的Channel
            selector.select();

            // 获取所有已就绪的Channel
            Iterator keyIterator = selector.selectedKeys().iterator();

            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();

                if (key.isAcceptable()) {
                    // 处理连接请求
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    System.out.println("接受新的连接:" + socketChannel.getRemoteAddress());

                    // 将新连接的SocketChannel注册到选择器上,监听READ事件
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    // 处理读取数据
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int readBytes = socketChannel.read(buffer);

                    if (readBytes > 0) {
                        buffer.flip();
                        byte[] bytes = new byte[buffer.remaining()];
                        buffer.get(bytes);
                        String content = new String(bytes, "UTF-8");
                        System.out.println("接收到客户端消息:" + content);

                        // 回写数据
                        buffer.clear();
                        buffer.put("Hello client!".getBytes());
                        buffer.flip();
                        socketChannel.write(buffer);
                    } else if (readBytes < 0) {
                        // 连接关闭
                        key.cancel();
                        socketChannel.close();
                    }
                }

                // 移除当前已处理的事件
                keyIterator.remove();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        NioServer server = new NioServer();
        server.init();
        server.start();
    }
}

上述代码中,首先创建了一个选择器(Selector)对象。然后创建了一个ServerSocketChannel并将其注册到选择器上,监听ACCEPT事件。接着进入主循环,调用select()方法阻塞等待就绪的Channel,然后使用selectedKeys()方法获取所有已就绪的Channel,遍历每个已就绪的Channel,根据其状态进行相应的处理。如果是新连接请求,就接受连接并将新的SocketChannel注册到选择器上,监听READ事件;如果是数据读取请求,就读取数据并回写数据。

需要注意的是,在NIO模型中,数据的读写操作是通过缓冲区(Buffer)对象完成的,所以需要在代码中使用ByteBuffer等缓冲区对象来处理数据。另外,NIO模型还需要特别注意内存泄漏的问题,因为它很容易发生,需要及时释放资源和取消注册。

总的来说,NIO比传统的IO模型更适合处理大量数据时的IO操作,并且具有更好的效率和可扩展性。但是,NIO的学习曲线相对较陡峭,需要掌握一定的底层知识和技能。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论