java——IO与NIO

简介: java——IO与NIO

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对象,指定了输入文件和输出文件。然后使用FileInputStream和FileOutputStream来创建字节流,对文件进行读写操作。为了提高效率,我们还使用了缓冲流BufferedInputStream和BufferedOutputStream,将其包装在字节流之上。

接下来,在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<SelectionKey> 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的学习曲线相对较陡峭,需要掌握一定的底层知识和技能。


/

相关文章
|
3月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
9天前
|
消息中间件 缓存 Java
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
零拷贝技术 Zero-Copy 是指计算机执行操作时,可以直接从源(如文件或网络套接字)将数据传输到目标缓冲区, 而不需要 CPU 先将数据从某处内存复制到另一个特定区域,从而减少上下文切换以及 CPU 的拷贝时间。
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
|
1月前
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
67 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
1月前
|
Java
让星星⭐月亮告诉你,Java NIO之Buffer详解 属性capacity/position/limit/mark 方法put(X)/get()/flip()/compact()/clear()
这段代码演示了Java NIO中`ByteBuffer`的基本操作,包括分配、写入、翻转、读取、压缩和清空缓冲区。通过示例展示了`position`、`limit`和`mark`属性的变化过程,帮助理解缓冲区的工作原理。
25 2
|
2月前
|
存储 网络协议 Java
Java NIO 开发
本文介绍了Java NIO(New IO)及其主要组件,包括Channel、Buffer和Selector,并对比了NIO与传统IO的优势。文章详细讲解了FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel及Pipe.SinkChannel和Pipe.SourceChannel等Channel实现类,并提供了示例代码。通过这些示例,读者可以了解如何使用不同类型的通道进行数据读写操作。
Java NIO 开发
|
1月前
|
Java 数据处理 开发者
揭秘Java IO流:字节流与字符流的神秘面纱!
揭秘Java IO流:字节流与字符流的神秘面纱!
33 1
|
1月前
|
自然语言处理 Java 数据处理
Java IO流全解析:字节流和字符流的区别与联系!
Java IO流全解析:字节流和字符流的区别与联系!
72 1
|
2月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
26天前
|
Java
Java 中 IO 流的分类详解
【10月更文挑战第10天】不同类型的 IO 流具有不同的特点和适用场景,我们可以根据具体的需求选择合适的流来进行数据的输入和输出操作。在实际应用中,还可以通过组合使用多种流来实现更复杂的功能。
43 0
|
2月前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。