玩转Java IO流:轻松读写文件、网络

申明:本人于公众号Java筑基期,CSDN先后发当前文章,标明原创,转载二次发文请注明转载公众号,另外请不要再标原创 ,注意违规

字符流和字节流

在Java中,IO(输入输出)操作涉及字符流和字节流。它们是两种不同的抽象类,用于处理不同类型的数据。

下面我会对字符流和字节流进行简单的解释:

  1. 字节流(Byte Stream)

    • 字节流是以字节为单位进行读写数据的IO流。

    • InputStreamOutputStream是字节流的抽象类。

    • 字节流适用于处理二进制数据,例如图像、音频、视频文件等。

    • 示例:

    • FileInputStream用于从文件读取字节数据

      public static void main(String[] args) {
              String filePath = "D:\\xxx\\resources\\readme.txt";
      ​
              try (FileInputStream fis = new FileInputStream(filePath)) {
                  byte[] buffer = new byte[1024]; // 缓冲区大小,用于存储读取的字节数据
                  int bytesRead; // 每次读取的字节数
      ​
                  // 使用 while 循环读取文件中的字节数据
                  while ((bytesRead = fis.read(buffer)) != -1) {
                      // 在这里处理读取的字节数据
                      // 注意:buffer 中的最后几个字节可能是无效数据,需要根据 bytesRead 的值来确定实际有效的字节数
                      for (int i = 0; i < bytesRead; i++) {
                          // 处理字节数据,例如打印每个字节的值
                          System.out.print(buffer[i] + " ");
                      }
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }

    • FileOutputStream用于将字节数据写入文件。

      public class OutputMain {
          public static void main(String[] args) {
              String filePath = "D:\\xxx\\resources\\readme.txt";
              byte[] data = "Hello, FileOutputStream!".getBytes(); // 要写入文件的字节数据
      ​
              try (FileOutputStream fos = new FileOutputStream(filePath)) {
                  fos.write(data); // 将字节数据写入文件
                  System.out.println("数据写入成功!");
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }

      玩转Java IO流:轻松读写文件、网络_第1张图片

       

    • ByteArrayInputStreamByteArrayOutputStream来读写文件

      public static void main(String[] args) {
          String sourceFilePath = "D:\\xxx\\resources\\readme.txt";
          String destinationFilePath = "D:\\xxx\\resources\\write.txt";
      ​
          try (FileInputStream fis = new FileInputStream(sourceFilePath);
               ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
      ​
              // 使用一个缓冲区来读取文件内容
              byte[] buffer = new byte[1024];
              int bytesRead;
      ​
              // 读取文件内容并写入 ByteArrayOutputStream 中
              while ((bytesRead = fis.read(buffer)) != -1) {
                  baos.write(buffer, 0, bytesRead);
              }
      ​
              // 将 ByteArrayOutputStream 中的内容写入另一个文件
              try (ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                   FileOutputStream fos = new FileOutputStream(destinationFilePath)) {
      ​
                  // 使用一个缓冲区来写入文件内容
                  byte[] writeBuffer = new byte[1024];
                  int bytesWritten;
      ​
                  // 从 ByteArrayInputStream 中读取内容并写入文件
                  while ((bytesWritten = bais.read(writeBuffer)) != -1) {
                      fos.write(writeBuffer, 0, bytesWritten);
                  }
      ​
                  System.out.println("文件内容写入成功!");
              }
          } catch (Exception e) {
              e.printStackTrace();
          }
      }

  2. 字符流(Character Stream)

    • 字符流是以字符为单位进行读写数据的IO流。

    • ReaderWriter是字符流的抽象类。

    • 字符流适用于处理文本数据,例如文本文件中的字符数据。

    • 字符流会自动处理字符的编码和解码,支持指定字符集进行数据读写。

    • 示例:

    • FileReader用于从文件读取字符数据

      public static void main(String[] args) {
          String filePath = "D:\\xxx\\resources\\readme.txt";
      ​
          try (FileReader fileReader = new FileReader(filePath)) {
              int data; // 用于存储每次读取的字符数据
      ​
              // 使用 while 循环读取文件中的字符数据
              while ((data = fileReader.read()) != -1) {
                  // 在这里处理读取的字符数据
                  // 注意:data 是一个 int 值,代表读取的字符的 ASCII 码
                  char character = (char) data; // 将 int 转换为 char
                  System.out.print(character);
              }
              System.out.println();
          } catch (IOException e) {
              e.printStackTrace();
          }
      }

      玩转Java IO流:轻松读写文件、网络_第2张图片

       

    • FileWriter用于将字符数据写入文件。

      public static void main(String[] args) {
          String filePath = "D:\\xxx\\resources\\readme.txt";
          String data = "Hello, FileWriter!"; // 要写入文件的字符数据
      ​
          try (FileWriter fileWriter = new FileWriter(filePath)) {
              fileWriter.write(data); // 将字符数据写入文件
              System.out.println("数据写入成功!");
          } catch (IOException e) {
              e.printStackTrace();
          }
      }

    • BufferedReaderBufferedWriter 它们提供缓冲功能,提高IO性能

      public static void main(String[] args) {
              String sourceFilePath = "D:\\xxx\\resources\\readme.txt";
              String destinationFilePath = "D:\\xxx\\resources\\write.txt";
      ​
              try (BufferedReader reader = new BufferedReader(new FileReader(sourceFilePath));
                   BufferedWriter writer = new BufferedWriter(new FileWriter(destinationFilePath))) {
      ​
                  String line; // 用于存储每次读取的行数据
      ​
                  // 使用 while 循环读取文件中的每一行数据
                  while ((line = reader.readLine()) != null) {
                      // 在这里处理读取的行数据
                      // 例如,可以在写入文件时添加额外的内容
                      String processedLine = "Processed: " + line;
                      writer.write(processedLine);
                      writer.newLine(); // 写入一个换行符,以分隔不同行的数据
                  }
      ​
                  System.out.println("文件内容写入成功!");
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }

总结:

  • 字节流适用于处理二进制数据,以字节为单位读写。

  • 字符流适用于处理文本数据,以字符为单位读写,并自动处理字符的编码和解码。

  • 通常情况下,如果处理文本数据,使用字符流更加方便,因为它会自动处理字符编码的问题,而字节流通常用于处理非文本的二进制数据。

对象序列化

对象序列化是将对象转换成字节序列的过程,以便可以将其保存到文件中或通过网络传输。

在Java中,通过实现 Serializable 接口,可以使一个类成为可序列化的,也就是可以被序列化为字节流。序列化后的字节流可以保存到文件、数据库或进行网络传输,而在需要时,可以通过反序列化将字节流还原成原始的Java对象。

实现对象序列化的步骤:

  1. 让类实现 Serializable 接口,该接口是一个标记接口,没有需要实现的方法。

    class MyClass implements Serializable {
        
        private String name;
        
        private int age;
    ​
        private transient String tel;
    ​
        public MyClass(String name, int age,String tel) {
            this.name = name;
            this.age = age;
            this.tel = tel;
        }
    ​
        public String getName() {
            return name;
        }
    ​
        public int getAge() {
            return age;
        }
    ​
        public String getTel() {
            return tel;
        }
    }

    Serializable 是一个标记接口(marker interface),它没有任何方法需要实现。当一个类实现了 Serializable 接口时,表示该类的对象可以被序列化。

    在某些情况下,我们希望在序列化过程中排除某些敏感信息或不需要序列化的数据。在成员变量声明前加上 transient 关键字,可以将该成员变量标记为瞬态(transient),表示它不参与对象的序列化。在反序列化时,这些成员变量的值将被初始化为默认值(对于基本数据类型是0,对于对象引用是null)。

  2. 在类中定义需要保存的成员变量和方法。

    // 创建一个示例对象,假设它是一个可序列化的类的实例
    MyClass obj = new MyClass("John Doe", 30,"123456789");
  3. 使用 ObjectOutputStream 将对象序列化为字节流。

    String filePath = "D:\\xxx\\resources\\readme.txt";
    FileOutputStream fos = new FileOutputStream(filePath);
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    // 将对象写入文件,实现对象的序列化
    oos.writeObject(obj);
  4. 使用 ObjectInputStream 将字节流反序列化为原始的Java对象。

    FileInputStream fis = new FileInputStream(filePath);
    ObjectInputStream ois = new ObjectInputStream(fis)
    // 从文件读取对象,实现对象的反序列化
    MyClass obj = (MyClass) ois.readObject();
    // 对象已经反序列化为原始的Java对象,可以使用它了
    System.out.println("反序列化后的对象:");
    System.out.println("Name: " + obj.getName());
    System.out.println("Age: " + obj.getAge());
    System.out.println("Tel: " + obj.getTel());

输出:

 

除了tel以外,其他的确实是序列化进去了。

字符集和编码

在Java中,字符集(Character Set)和编码(Encoding)是涉及文本字符处理的重要概念。在Java中,字符集(Character Set)和编码(Encoding)是涉及文本字符处理的重要概念。

字符集(Character Set): 字符集是一组字符的集合,它将字符映射到唯一的整数值,也称为字符编码点(Code Point)。每个字符都有一个唯一的字符编码点,字符集中的字符编码点是固定的。

在Java中,char 类型代表一个16位的Unicode字符,因此Java的字符集采用的是Unicode字符集,即字符编码点的范围是0x0000至0xFFFF。Unicode字符集允许覆盖几乎所有的世界语言和符号。

编码(Encoding): 编码是将字符编码点映射成字节序列的过程。由于计算机中处理的是二进制数据,因此文本字符需要转换成字节才能在计算机中存储和传输。

UTF-8和UTF-16是两种常见的Unicode编码方案:

  1. UTF-8: UTF-8(8-bit Unicode Transformation Format)是一种可变长度的编码方案。它使用1至4个字节表示一个Unicode字符,对于常用的ASCII字符,UTF-8只使用1个字节,而对于非ASCII字符,使用多个字节进行编码。UTF-8编码在存储和传输文本时节省空间,因为它对于英文字符使用较少的字节。

  2. UTF-16: UTF-16(16-bit Unicode Transformation Format)是一种固定长度的编码方案。它使用2或4个字节表示一个Unicode字符。UTF-16编码对于大部分字符使用2个字节,而对于一些辅助字符,使用4个字节进行编码。

在Java中,默认的编码方案取决于平台和系统的设置,通常情况下,Java使用UTF-8编码来处理文本数据,但可以通过设置特定的编码方式来控制文本的读写和传输。

例如,在使用 FileReaderFileWriter 读写文本文件时,默认使用的是系统的字符集,可以通过指定字符集来明确使用UTF-8或其他编码方式:

// 使用UTF-8编码读写文件
try (FileReader fileReader = new FileReader("file.txt", StandardCharsets.UTF_8);
     FileWriter fileWriter = new FileWriter("file.txt", StandardCharsets.UTF_8)) {
    // 处理文本数据
} catch (IOException e) {
    e.printStackTrace();
}

// 使用UTF-8编码读写文件
try (FileReader fileReader = new FileReader("file.txt", StandardCharsets.UTF_8);
     FileWriter fileWriter = new FileWriter("file.txt", StandardCharsets.UTF_8)) {
    // 处理文本数据
} catch (IOException e) {
    e.printStackTrace();
}

总结:

  • 字符集是一组字符的集合,将字符映射到唯一的整数值(字符编码点)。

  • 编码是将字符编码点映射成字节序列的过程。

  • Java中采用的字符集是Unicode字符集,字符类型char代表一个16位的Unicode字符。

  • UTF-8是可变长度编码,UTF-16是固定长度编码,两者都是Unicode编码方案。

Java IO

Java NIO(New I/O)是Java提供的一种新的I/O操作方式,相较于传统的Java IO(也称为IO流或Stream)更为灵活和高效。

Java NIO引入了通道(Channel)和缓冲区(Buffer)的概念,使得可以通过非阻塞的方式进行数据读写操作。

  1. 通道(Channel): 通道是Java NIO中用于进行数据传输的抽象。它表示一个连接到文件、套接字或其他IO资源的开放连接。通过通道,可以在缓冲区和IO设备之间直接传输数据,而无需通过中间的IO流。通道是双向的,可以用于读取和写入数据。

    在Java NIO中,常用的通道包括 FileChannel(用于文件IO)、SocketChannel(用于TCP网络通信)、ServerSocketChannel(用于TCP服务器)和 DatagramChannel(用于UDP网络通信)。

  2. 缓冲区(Buffer): 缓冲区是用于存储数据的对象。数据从通道读取到缓冲区,或从缓冲区写入到通道。缓冲区提供了一种高效的方式来管理数据,可以在内存中预先分配空间,并支持直接在内存中进行数据操作。使用缓冲区进行数据传输可以避免频繁的系统调用,提高数据传输的效率。

    在Java NIO中,缓冲区是一个数组,可以是基本数据类型的数组(如 ByteBufferCharBufferShortBuffer 等)或对象数据类型的数组(如 ObjectBuffer)。不同类型的缓冲区适用于不同的数据类型。

通常,数据通过缓冲区来读取和写入。当从通道读取数据时,数据会被读取到缓冲区中;当写入数据到通道时,数据会从缓冲区写入。

Java NIO的基本使用步骤如下:

  1. 创建一个通道(Channel)对象,连接到数据源(如文件或网络套接字)。

    String sourceFilePath = "D:\\IDEA_Work\\LinkCV\\src\\main\\resources\\write.txt";
    String destinationFilePath = "D:\\IDEA_Work\\LinkCV\\src\\main\\resources\\readme.txt";
    FileInputStream fis = new FileInputStream(sourceFilePath);
    FileOutputStream fos = new FileOutputStream(destinationFilePath);
    FileChannel sourceChannel = fis.getChannel();
    FileChannel destinationChannel = fos.getChannel()
    

    这里首先是打开源文件和目标文件的通道。

  2. 创建一个缓冲区(Buffer)对象,用于存储要读取或写入的数据。

    // 创建一个缓冲区
    ByteBuffer buffer = ByteBuffer.allocate(1024);

    然后创建一个缓冲区

  3. 将数据从通道读取到缓冲区中(读操作)或将数据从缓冲区写入到通道中(写操作)。

    while (sourceChannel.read(buffer) != -1) {
    	//读取或写入
    }

    并在循环中从源通道读取数据到缓冲区

  4. 处理读取或写入的数据。

    // 切换缓冲区为读模式
    buffer.flip();
    
    // 将缓冲区的数据写入目标通道
    destinationChannel.write(buffer);

    再将缓冲区的数据写入目标通道

  5. 关闭通道和缓冲区。

    // 清空缓冲区以便继续读取
    buffer.clear();

    最后关闭通道释放资源

完整代码:

package com.java.CharacterStream;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class IOMain {
    public static void main(String[] args) {
        String sourceFilePath = "D:\\xxx\\resources\\write.txt";
        String destinationFilePath = "D:\\xxx\\resources\\readme.txt";

        try (FileInputStream fis = new FileInputStream(sourceFilePath);
             FileOutputStream fos = new FileOutputStream(destinationFilePath);
             FileChannel sourceChannel = fis.getChannel();
             FileChannel destinationChannel = fos.getChannel()) {

            // 创建一个缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            // 从源通道读取数据到缓冲区
            while (sourceChannel.read(buffer) != -1) {
                // 切换缓冲区为读模式
                buffer.flip();

                // 将缓冲区的数据写入目标通道
                destinationChannel.write(buffer);

                // 清空缓冲区以便继续读取
                buffer.clear();
            }

            System.out.println("文件复制完成!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在Java NIO(New I/O)中,FileChannelSocketChannelServerSocketChannel是用于文件和网络I/O操作的核心类

它们分别对应文件IO和网络IO的通道。

  1. FileChannelFileChannel 是用于文件IO的通道,通过它可以直接在文件和缓冲区之间进行数据传输。它提供了高效的文件读写功能,并支持对文件的定位操作。

    常用的方法包括:

    • read(ByteBuffer dst):从通道读取数据到缓冲区。

    • write(ByteBuffer src):将缓冲区的数据写入到通道。

    • position(long newPosition):设置通道的当前位置。

    • size():返回通道关联文件的大小。

    • truncate(long size):截断文件大小。

    • transferTo(long position, long count, WritableByteChannel target):将通道数据传输到另一个通道。

      String sourceFilePath = "D:\\IDEA_Work\\LinkCV\\src\\main\\resources\\write.txt";
      String destinationFilePath = "D:\\IDEA_Work\\LinkCV\\src\\main\\resources\\readme.txt";
      // 使用FileChannel进行文件复制
      try (FileInputStream fis = new FileInputStream(sourceFilePath);
           FileOutputStream fos = new FileOutputStream(destinationFilePath);
           FileChannel sourceChannel = fis.getChannel();
           FileChannel destinationChannel = fos.getChannel()) {
      
          ByteBuffer buffer = ByteBuffer.allocate(1024);
      
          while (sourceChannel.read(buffer) != -1) {
              buffer.flip();
              destinationChannel.write(buffer);
              buffer.clear();
          }
      
          System.out.println("文件复制完成!");
      } catch (Exception e) {
          e.printStackTrace();
      }

  2. SocketChannelSocketChannel 是用于TCP网络通信的通道,它可以连接到远程服务器,并实现非阻塞的读写操作。它支持异步非阻塞I/O,可通过 Selector 来实现多路复用。

    常用的方法包括:

    • connect(SocketAddress remote):连接到远程服务器。

    • read(ByteBuffer dst):从通道读取数据到缓冲区。

    • write(ByteBuffer src):将缓冲区的数据写入到通道。

    • finishConnect():完成通道连接的操作。

    public static void main(String[] args) {
        try (SocketChannel socketChannel = SocketChannel.open()) {
            // 连接到服务器端的ServerSocketChannel
            socketChannel.connect(new InetSocketAddress("localhost", 8080));
            System.out.println("连接到服务器端...");
    
            // 向服务器端发送数据
            String message = "Hello, server!";
            ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
            socketChannel.write(buffer);
            System.out.println("向服务器端发送数据:" + message);
    
            // 接收服务器端的响应
            ByteBuffer responseBuffer = ByteBuffer.allocate(1024);
            int bytesRead = socketChannel.read(responseBuffer);
            if (bytesRead != -1) {
                responseBuffer.flip();
                byte[] responseData = new byte[responseBuffer.remaining()];
                responseBuffer.get(responseData);
                String responseMessage = new String(responseData);
                System.out.println("从服务器端接收到响应:" + responseMessage);
            } else {
                System.out.println("服务器端关闭了连接。");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

  3. ServerSocketChannelServerSocketChannel 是用于TCP服务器的通道,它可以监听客户端的连接请求,并返回对应的 SocketChannel 来进行通信。

    常用的方法包括:

    • bind(SocketAddress local):绑定服务器的本地地址。

    • accept():接受客户端的连接请求,返回对应的 SocketChannel

      // 使用SocketChannel进行网络通信
      try (ServerSocketChannel serverChannel = ServerSocketChannel.open()) {
          serverChannel.bind(new InetSocketAddress(8080));
          System.out.println("服务器已启动,监听端口8080...");
      ​
          while (true) {
              SocketChannel clientChannel = serverChannel.accept();
              System.out.println("接受来自客户端的连接:" + clientChannel.getRemoteAddress());
      ​
              ByteBuffer buffer = ByteBuffer.allocate(1024);
      ​
              while (clientChannel.read(buffer) != -1) {
                  buffer.flip();
                  clientChannel.write(buffer);
                  buffer.clear();
              }
      ​
              clientChannel.close();
          }
      } catch (Exception e) {
          e.printStackTrace();
      }

    发散一下想法,作为思考题:开发一个需求,使用 ServerSocketChannel 建立服务端,使用SocketChannel 建立客户端,由客户端发起连接并且发送内容,服务端收到后,发送一条信息告诉客户端已经收到消息,并且把收到的消息存到本地。

    代码实现

 
 

你可能感兴趣的:(Java基础,面试题,java,面试)