Java IO和NIO

Java中的IO(Input/Output,输入/输出)是处理数据流的一种方式,它允许我们从文件、网络或其他数据源读取数据,或者将数据写入到文件、网络或其他数据源。从Java 1.0开始,Java IO API就是以流的方式来处理输入和输出。

Java IO分为字节流和字符流两种类型。字节流(Byte Stream)是以字节为单位进行读写的,可以用于读写任意类型的数据;而字符流(Character Stream)是以字符为单位进行读写的,主要用于读写字符数据。

字节流和字符流

字节流类主要包括InputStreamOutputStream,而字符流类主要包括ReaderWriter

InputStreamOutputStream

InputStreamOutputStream是抽象类,分别代表输入流和输出流。常用的实现类有FileInputStreamFileOutputStreamByteArrayInputStreamByteArrayOutputStream等。

// 使用InputStream读取文件
try (InputStream input = new FileInputStream("filePath")) {
    int data;
    while ((data = input.read()) != -1) {
        // 处理读取到的字节数据
    }
} catch (IOException e) {
    // 处理异常
}

// 使用OutputStream写入文件
try (OutputStream output = new FileOutputStream("filePath")) {
    byte[] data = "Hello, Java IO".getBytes();
    output.write(data);
} catch (IOException e) {
    // 处理异常
}

ReaderWriter

ReaderWriter也是抽象类,代表字符输入流和字符输出流。常用的实现类有FileReaderFileWriterBufferedReaderBufferedWriter等。

// 使用Reader读取文件
try (Reader reader = new FileReader("filePath")) {
    int data;
    while ((data = reader.read()) != -1) {
        // 处理读取到的字符数据
    }
} catch (IOException e) {
    // 处理异常
}

// 使用Writer写入文件
try (Writer writer = new FileWriter("filePath")) {
    String data = "Hello, Java IO";
    writer.write(data);
} catch (IOException e) {
    // 处理异常
}

File类

File类是Java表示文件和目录路径的标准类,它提供了一系列方法来管理文件系统中的文件和目录。

// 创建File对象
File file = new File("filePath");

// 判断文件是否存在
if (file.exists()) {
    // 文件存在
} else {
    // 文件不存在
}

// 创建新文件
try {
    boolean success = file.createNewFile();
    if (success) {
        // 文件创建成功
    } else {
        // 文件创建失败
    }
} catch (IOException e) {
    // 处理异常
}

// 删除文件或目录
boolean success = file.delete();
if (success) {
    // 文件或目录删除成功
} else {
    // 文件或目录删除失败
}

// 判断是否是目录
boolean isDirectory = file.isDirectory();
// 判断是否是文件
boolean isFile = file.isFile();

// 获取文件名
String fileName = file.getName();
// 获取文件路径
String filePath = file.getAbsolutePath();

InputStream/OutputStream和Reader/Writer接口

在Java中,IO类的基础是byte和char数据类型,而不是对象数据类型。Java使用InputStream、OutputStream、Reader和Writer接口来实现IO操作。

InputStream和OutputStream

InputStream和OutputStream是Java IO中最基本的输入输出抽象,用于读写字节流。InputStream接口中的read()方法可以读取一个字节,返回值为读取到的字节数据,如果已经到达流的末尾,则返回-1;而OutputStream接口中的write()方法用于写入一个字节,参数为一个byte类型的数据。

// 使用InputStream读取文件
try (InputStream input = new FileInputStream("filePath")) {
    int data;
    while ((data = input.read()) != -1) {
        // 处理读取到的字节数据
    }
} catch (IOException e) {
    // 处理异常
}

// 使用OutputStream写入文件
try (OutputStream output = new FileOutputStream("filePath")) {
    byte[] data = "Hello, Java IO".getBytes();
    output.write(data);
} catch (IOException e) {
    // 处理异常
}

Reader和Writer

Reader和Writer是用于读写字符流的Java IO接口,它们可以自动将字节转换为字符。Reader接口中的read()方法可以读取一个字符,返回值为读取到的字符,如果已经到达流的末尾,则返回-1;而Writer接口中的write()方法用于写入一个字符,参数为一个char类型的数据。

// 使用Reader读取文件
try (Reader reader = new FileReader("filePath")) {
    int data;
    while ((data = reader.read()) != -1) {
        // 处理读取到的字符数据
    }
} catch (IOException e) {
    // 处理异常
}

// 使用Writer写入文件
try (Writer writer = new FileWriter("filePath")) {
    String data = "Hello, Java IO";
    writer.write(data);
} catch (IOException e) {
    // 处理异常
}

NIO

在Java 1.4中引入的NIO(New IO)是一组针对Java IO进行改进的API,主要由ChannelBufferSelector三个主要模块组成。它提供了一种基于缓冲区(Buffer)和通道(Channel)的IO方式,与传统IO流(Stream)完全不同,提供更高效的数据操作和更好的可伸缩性。

Channel

Channel 是 NIO中的一个抽象概念,它代表着数据源或数据目标的连接,类似于传统 IO 中的流。 Channel 接口提供了数据连接的独立性,例如可以打开到 TCP 网络套接字的 Channel,读写此套接字中的数据,而不需要考虑底层操作系统传输数据需要的套接字相关 API、传输速度的限制等。

NIO 中最常使用的 Channel 类型有如下几种:

  • FileChannel:用于文件的读取和写入。
  • DatagramChannel:用于UDP协议进行数据读取和写入。
  • SocketChannel:用于TCP协议中的数据读取和写入。
  • ServerSocketChannel:可以监听新进来的TCP连接,对每个新进来的连接都会创建一个 SocketChannel。
// 创建Channel
try (SocketChannel channel = SocketChannel.open()) {
    channel.configureBlocking(false); // 将Channel设置为非阻塞模式
    channel.connect(new InetSocketAddress("example.com", 80));

    // 检查是否连接成功
    if (channel.finishConnect()) {
        // 连接成功,可以进行读写操作
    }
} catch (IOException e) {
    // 处理异常
}

// 使用Channel读取数据
try (RandomAccessFile file = new RandomAccessFile("filePath", "rw")) {
    FileChannel channel = file.getChannel();

    ByteBuffer buffer = ByteBuffer.allocate(1024);
    int bytesRead = channel.read(buffer);
    while (bytesRead != -1) {
        buffer.flip();

        while (buffer.hasRemaining()) {
            System.out.print((char) buffer.get());
        }

        buffer.clear();
        bytesRead = channel.read(buffer);
    }
} catch (IOException e) {
    // 处理异常
}

// 使用Channel写入数据
try (RandomAccessFile file = new RandomAccessFile("filePath", "rw")) {
    FileChannel channel = file.getChannel();

    ByteBuffer buffer = ByteBuffer.allocate(1024);
    buffer.put("Hello, Java NIO".getBytes());
    buffer.flip();

    channel.write(buffer);
} catch (IOException e) {
    // 处理异常
}

Buffer

Buffer 是一个对象,包含一些要写入或者要读出的数据。在 NIO 中,所有数据都是通过 Buffer 对象来处理的。它是一个线性的、有限的数据区域,可以用来存储数据。Buffer 类型包括 ByteBuffer、CharBuffer、DoubleBuffer等等。

  • ByteBuffer:用于读写字节数据。
  • CharBuffer:用于读写字符数据,底层实现是使用的是 char 数组,但是在底层,仍然是通过 ByteBuffer 实现的。
  • DoubleBuffer:用于读写 double 类型数据。
// 创建Buffer
CharBuffer buffer = CharBuffer.allocate(1024);

// 写入数据到Buffer
buffer.put("Hello, Java NIO");

// 切换Buffer为读取模式
buffer.flip();

// 从Buffer中读取数据
while (buffer.hasRemaining()) {
    System.out.print(buffer.get());
}

Selector

Selector 是 NIO 的核心组件之一,用于检测多个 Channel 的事件状态。一个单独的线程可以检查多个 Channel 的状态,从而处理多个 Channel 的网络请求。 Java NIO 的 Selector 实现类似于 UNIX/Linux的 epoll 机制。

// 创建Selector
Selector selector = Selector.open();

// 注册Channel到Selector,并监听感兴趣的事件
channel.register(selector, SelectionKey.OP_READ);

while (true) {
    // 阻塞等待就绪的Channel
    int readyChannels = selector.select();

    if (readyChannels == 0) {
        continue;
    }

    // 获取就绪的Channel的Key,进行读写操作
    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    for (SelectionKey key: selectedKeys) {
        if (key.isReadable()) {
            // 可读事件
        } else if (key.isWritable()) {
            // 可写事件
        }
    }
    // 处理完后,记得清空Key集合
    selectedKeys.clear();
}

什么是序列化?

在Java编程中,序列化(serialization)是指将对象转换为字节流的过程,可以将对象在网络中传输或者保存到磁盘中,使得对象的状态能够被保存和恢复。序列化之后的字节流可以在网络上传输,也可以保存到本地的文件系统中。

为什么需要序列化?

Java序列化技术的出现主要是为了解决对象的存储、传输和分布式计算的问题。通过序列化,可以将对象以字节流的形式进行传输,实现对象在网络中的传输。另外,当涉及到分布式系统、持久化存储等场景时,序列化同样具有重要作用。

如何进行序列化?

在Java中,要使一个对象能够被序列化,需要满足两个条件:

  1. 类必须实现 java.io.Serializable 接口;
  2. 所属类的所有属性必须是可序列化的(基本类型和包装类、String等都可以序列化)。

接下来我们通过一个例子来演示序列化的过程:

import java.io.*;

public class SerializationExample implements Serializable {
   private String name;
   private int age;

   public void setName(String name) {
      this.name = name;
   }

   public void setAge(int age) {
      this.age = age;
   }

   public String getName() {
      return name;
   }

   public int getAge() {
      return age;
   }

   public static void main(String[] args) {
      SerializationExample obj = new SerializationExample();
      obj.setName("John");
      obj.setAge(25);

      try {
         FileOutputStream fileOut = new FileOutputStream("object.ser");
         ObjectOutputStream out = new ObjectOutputStream(fileOut);
         out.writeObject(obj);
         out.close();
         fileOut.close();
         System.out.println("对象被序列化并保存到 object.ser 文件");
      } catch (IOException i) {
         i.printStackTrace();
      }
   }
}

在这个例子中,我们创建了一个 SerializationExample 类,它实现了 Serializable 接口。我们创建了一个对象 obj ,设置了其属性值,并且将其序列化并保存到 object.ser 文件中。

什么是反序列化?

反序列化(deserialization)是指将字节流转换为对象的过程,通过反序列化,可以将对象从字节流中重新恢复出来。这样就可以在网络中传输对象,或者从磁盘中读取对象并进行恢复。

如何进行反序列化?

要进行反序列化,需要使用 ObjectInputStream 类,通过调用 ObjectInputStreamreadObject() 方法实现反序列化。

以下是一个反序列化的例子:

import java.io.*;

public class DeserializationExample {
   public static void main(String[] args) {
      SerializationExample obj = null;

      try {
         FileInputStream fileIn = new FileInputStream("object.ser");
         ObjectInputStream in = new ObjectInputStream(fileIn);
         obj = (SerializationExample) in.readObject();
         in.close();
         fileIn.close();
      } catch (IOException i) {
         i.printStackTrace();
         return;
      } catch (ClassNotFoundException c) {
         System.out.println("class not found");
         c.printStackTrace();
         return;
      }

      System.out.println("对象被反序列化:");
      System.out.println("Name: " + obj.getName());
      System.out.println("Age: " + obj.getAge());
   }
}

在上面的例子中,我们使用 FileInputStream 来读取 object.ser 文件的字节流,并使用 ObjectInputStreamreadObject() 方法进行反序列化。最终我们将恢复出的对象的属性值进行打印。

需要注意的是,在进行反序列化时,类的定义必须是可用的,否则会抛出 ClassNotFoundException 异常。

一些额外注意事项

在进行序列化和反序列化时,有一些需要额外注意的事项:

  • 静态变量不会被序列化,因为它们属于类而不是对象;
  • transient 修饰的变量不会被序列化,因此在反序列化时会被赋予默认值;
  • 序列化和反序列化的类必须有相同的 serialVersionUID ,否则会导致反序列化失败。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 先来看看API的使用。 1. IO 流原理及流的分类 1.1 Java IO 1. I/O是Input/Outpu...
    梧叶已秋声阅读 615评论 0 0
  • 同步与堵塞完全是两码事 有人觉得堵塞就是同步,非堵塞就是异步,其实以前我也是这么想的,其实同步与堵塞这完全是两码事...
    John13阅读 363评论 0 0
  • Java的IO和NIO 一、Java的IO Java的IO功能在java.io包下,包括输入、输出两种IO流,每种...
    Pde_fa13阅读 384评论 0 0
  • Java的IO和NIO 一、Java的IO Java的IO功能在java.io包下,包括输入、输出两种IO流,每种...
    王小冬阅读 1,022评论 0 9
  • 一、IO java的IO功能都在java.io包下,包括输入输出两种IO流,每种输入输出流又可分为字节流和字符流两...
    落地生涯阅读 585评论 0 1