Java IO操作和使用方法

linux系统write系统调用入参和出参

在 Linux 系统中,write 系统调用用于将数据写入文件描述符(如文件、管道、套接字等)。write 系统调用的原型如下:

write 系统调用的原型

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
参数说明
  1. fd (文件描述符)

    • 类型:int
    • 描述:表示要写入的目标文件、管道、套接字等的文件描述符。文件描述符是一个非负整数,由 openpipesocket 等系统调用返回。标准输出(stdout)的文件描述符为 1,标准错误(stderr)为 2。
  2. buf (缓冲区指针)

    • 类型:const void *
    • 描述:指向一个缓冲区的指针,包含要写入的数据。这个缓冲区中的数据将被复制到文件描述符指定的目标中。const 关键字表示 write 不会修改传入的缓冲区内容。
  3. count (写入的最大字节数)

    • 类型:size_t
    • 描述:指定最多写入的字节数。write 系统调用会尝试写入 count 个字节,但实际写入的字节数可能少于 count,具体取决于文件的状态和目标设备的可用空间。
返回值
  • 成功:返回实际写入的字节数。如果 write 成功写入了所有请求的字节,则返回值等于 count
  • 失败:返回 -1,并设置 errno 变量以指示错误类型。
  • 部分写入:如果 write 系统调用在写入了部分数据后被中断(例如由于信号处理或磁盘满),它可能会返回小于 count 的字节数。在这种情况下,应用程序可以继续调用 write 来写入剩余的数据。

常见的错误码

  • EAGAINEWOULDBLOCK:非阻塞 I/O 操作,文件描述符当前不可写入。
  • EBADF:文件描述符无效或不是可写的。
  • EFAULTbuf 指向的地址无效。
  • EINTR:写入操作被信号中断。
  • EINVALfd 不是有效的文件描述符,或者 count 为负数。
  • EIO:发生 I/O 错误。
  • ENOSPC:磁盘空间不足,无法完成写入。
  • ENOMEM:内存不足,无法完成操作。
  • EPIPE:写入到已关闭的管道或套接字。

示例代码

以下是一个简单的 C 语言示例,展示了如何使用 write 系统调用来将数据写入文件:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

int main() {
    // 打开文件,获取文件描述符
    int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 定义要写入的数据
    const char *data = "Hello, World!\n";
    size_t length = strlen(data);

    // 尝试写入数据
    ssize_t bytesWritten = write(fd, data, length);
    if (bytesWritten == -1) {
        perror("write");
        close(fd);
        return 1;
    }

    // 检查是否写入了所有数据
    if ((size_t)bytesWritten != length) {
        fprintf(stderr, "部分数据未写入: 写入 %zd 字节, 期望 %zu 字节\n", bytesWritten, length);
    } else {
        printf("数据写入成功\n");
    }

    // 关闭文件描述符
    close(fd);

    return 0;
}

解释

  1. 打开文件:使用 open 系统调用打开文件 output.txt,并获取文件描述符 fdO_WRONLY 表示只写模式,O_CREAT 表示如果文件不存在则创建,O_TRUNC 表示如果文件存在则截断为 0 长度。0644 是文件权限,表示所有者可读写,其他用户可读。

  2. 定义要写入的数据:定义一个字符串 data,并计算其长度 length

  3. 写入数据:使用 write 系统调用将 data 中的数据写入文件描述符 fdwrite 返回实际写入的字节数。如果 write 返回 -1,表示发生错误,使用 perror 打印错误信息。

  4. 检查写入结果:如果 write 返回的字节数不等于 length,表示部分数据未写入,打印警告信息。否则,打印成功消息。

  5. 关闭文件描述符:使用 close 系统调用关闭文件描述符 fd,释放资源。

注意事项

  • 部分写入write 系统调用可能会返回少于请求的字节数,尤其是在写入网络套接字或管道时。因此,应用程序应该准备好处理部分写入的情况,并在必要时继续调用 write 以写入剩余的数据。

  • 非阻塞 I/O:如果你希望 write 系统调用在无法立即写入时立即返回,而不是阻塞等待,可以将文件描述符设置为非阻塞模式。这可以通过 fcntl 系统调用实现。

  • 信号处理:如果 write 系统调用在写入过程中被信号中断,它可能会返回 -1 并设置 errnoEINTR。在这种情况下,应用程序可以选择重新调用 write 或者根据需要处理信号。

  • 磁盘空间不足:如果磁盘空间不足,write 可能会返回 -1 并设置 errnoENOSPC。应用程序应该检查这一点并采取适当的措施。

总结

write 系统调用是 Linux 系统中最基本的写入操作之一,广泛用于文件、管道、套接字等 I/O 操作。通过理解其参数和返回值,你可以更好地控制和优化 I/O 操作,确保程序的正确性和性能。

linux系统read系统调用入参

在 Linux 系统中,read 系统调用用于从文件描述符(如文件、管道、套接字等)中读取数据。read 系统调用的原型如下:

read 系统调用的原型

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
参数说明
  1. fd (文件描述符)

    • 类型:int
    • 描述:表示要读取的文件、管道、套接字等的文件描述符。文件描述符是一个非负整数,由 openpipesocket 等系统调用返回。标准输入(stdin)的文件描述符为 0,标准输出(stdout)为 1,标准错误(stderr)为 2。
  2. buf (缓冲区指针)

    • 类型:void *
    • 描述:指向一个缓冲区的指针,用于存储从文件描述符中读取的数据。这个缓冲区必须有足够的空间来容纳最多 count 个字节的数据。
  3. count (读取的最大字节数)

    • 类型:size_t
    • 描述:指定最多读取的字节数。read 系统调用会尝试读取 count 个字节,但实际读取的字节数可能少于 count,具体取决于文件的状态和可用数据量。
返回值
  • 成功:返回实际读取的字节数。如果文件已经到达末尾(EOF),则返回 0。
  • 失败:返回 -1,并设置 errno 变量以指示错误类型。
  • 部分读取:如果 read 系统调用在读取了部分数据后被中断(例如由于信号处理),它可能会返回小于 count 的字节数。在这种情况下,应用程序可以继续调用 read 来读取剩余的数据。

常见的错误码

  • EAGAINEWOULDBLOCK:非阻塞 I/O 操作,文件描述符当前没有可读取的数据。
  • EBADF:文件描述符无效或不是可读的。
  • EFAULTbuf 指向的地址无效。
  • EINTR:读取操作被信号中断。
  • EINVALfd 不是有效的文件描述符,或者 count 为负数。
  • EIO:发生 I/O 错误。
  • ENOMEM:内存不足,无法完成操作。
  • ENXIO:设备不支持读取操作。

示例代码

以下是一个简单的 C 语言示例,展示了如何使用 read 系统调用来读取文件内容:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

int main() {
    // 打开文件,获取文件描述符
    int fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 定义缓冲区,用于存储读取的数据
    char buffer[1024];
    ssize_t bytesRead;

    // 循环读取文件内容
    while ((bytesRead = read(fd, buffer, sizeof(buffer))) > 0) {
        // 将读取的数据写入标准输出
        write(STDOUT_FILENO, buffer, bytesRead);
    }

    // 检查是否发生错误
    if (bytesRead == -1) {
        perror("read");
        close(fd);
        return 1;
    }

    // 关闭文件描述符
    close(fd);

    return 0;
}

解释

  1. 打开文件:使用 open 系统调用打开文件 example.txt,并获取文件描述符 fd。如果打开失败,open 返回 -1,并使用 perror 打印错误信息。

  2. 定义缓冲区:定义一个大小为 1024 字节的缓冲区 buffer,用于存储从文件中读取的数据。

  3. 循环读取:使用 read 系统调用从文件描述符 fd 中读取数据到缓冲区 buffer 中。每次读取最多 1024 字节。如果 read 返回的字节数大于 0,表示成功读取了一些数据,然后使用 write 系统调将这些数据写入标准输出(STDOUT_FILENO)。如果 read 返回 0,表示文件已经到达末尾,循环结束。如果 read 返回 -1,表示发生错误,使用 perror 打印错误信息。

  4. 关闭文件描述符:使用 close 系统调用关闭文件描述符 fd,释放资源。

注意事项

  • 部分读取read 系统调用可能会返回少于请求的字节数,尤其是在读取网络套接字或管道时。因此,应用程序应该准备好处理部分读取的情况,并在必要时继续调用 read 以读取剩余的数据。

  • 非阻塞 I/O:如果你希望 read 系统调用在没有数据可读时立即返回,而不是阻塞等待数据,可以将文件描述符设置为非阻塞模式。这可以通过 fcntl 系统调用实现。

  • 信号处理:如果 read 系统调用在读取过程中被信号中断,它可能会返回 -1 并设置 errnoEINTR。在这种情况下,应用程序可以选择重新调用 read 或者根据需要处理信号。

总结

read 系统调用是 Linux 系统中最基本的读取操作之一,广泛用于文件、管道、套接字等 I/O 操作。通过理解其参数和返回值,你可以更好地控制和优化 I/O 操作,确保程序的正确性和性能。

fileinputStream和fileoutputStream 用字节数组,将文件拷贝到目标文件

使用 FileInputStreamFileOutputStream 通过字节数组将文件从一个位置复制到另一个位置是一个常见的任务。为了提高性能,通常会使用一个较大的缓冲区(如 8 KB 或更大)来批量读取和写入数据,而不是逐个字节进行操作。以下是一个完整的示例代码,展示了如何高效地实现文件拷贝。

示例代码:使用字节数组进行文件拷贝


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

public class FileCopyExample {
    public static void main(String[] args) {
        // 源文件路径和目标文件路径
        String sourceFilePath = "source.txt";
        String destFilePath = "destination.txt";

        // 定义缓冲区大小(例如 8 KB)
        int bufferSize = 8192;  // 8 KB
        byte[] buffer = new byte[bufferSize];

        try (FileInputStream fis = new FileInputStream(sourceFilePath);
             FileOutputStream fos = new FileOutputStream(destFilePath)) {

            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                // 只写入实际读取的字节数
                fos.write(buffer, 0, bytesRead);
            }

            System.out.println("

FileOutputStream批量写入数组


 /**
     * Writes a sub array as a sequence of bytes.
     * @param b the data to be written
     * @param off the start offset in the data
     * @param len the number of bytes that are written
     * @param append {@code true} to first advance the position to the
     *     end of file
     * @exception IOException If an I/O error has occurred.
     */
    private native void writeBytes(byte b[], int off, int len, boolean append)
        throws IOException;

FileInputStream批量读取

 /**
     * Reads a subarray as a sequence of bytes.
     * @param b the data to be written
     * @param off the start offset in the data
     * @param len the number of bytes that are written
     * @exception IOException If an I/O error has occurred.
     */
    private native int readBytes(byte b[], int off, int len) throws IOException;

在操作系统层面,字节流和字符流的IO操作的流程

在操作系统层面,字节流和字符流的 I/O 操作流程有所不同,主要体现在它们如何处理数据以及与应用程序交互的方式。以下是两者的详细流程:

字节流 (Byte Stream) 的 I/O 操作流程

  1. 文件描述符或句柄:每个打开的文件、网络连接或其他 I/O 资源在操作系统中都有一个唯一的标识符,称为文件描述符(在 Unix/Linux 系统中)或句柄(在 Windows 系统中)。这些标识符用于跟踪和管理 I/O 资源。

  2. 系统调用:当应用程序需要进行 I/O 操作时,它会通过系统调用(如 readwrite 在 Unix/Linux 系统中,ReadFileWriteFile 在 Windows 系统中)请求操作系统执行实际的读写操作。系统调用是用户空间程序与内核空间之间的桥梁。

  3. 缓冲区:操作系统通常会在内核空间为每个 I/O 操作分配一个缓冲区。这个缓冲区用于临时存储从设备读取的数据或等待写入设备的数据。这样做可以减少频繁的磁盘访问或网络传输,提高效率。

  4. I/O 操作

    • 读操作:操作系统从文件或设备读取指定数量的字节到内核缓冲区,然后将这些字节复制到用户提供的缓冲区中。
    • 写操作:操作系统将用户提供的缓冲区中的字节复制到内核缓冲区,然后将这些字节写入文件或设备。
  5. 同步与异步:I/O 操作可以是同步的(阻塞式),也可以是异步的(非阻塞式)。同步 I/O 会阻塞进程直到操作完成,而异步 I/O 允许进程继续执行其他任务,并在 I/O 操作完成后接收通知。

  6. 错误处理:如果 I/O 操作失败,操作系统会返回错误代码,应用程序可以根据这些错误代码采取相应的措施。

字符流 (Character Stream) 的 I/O 操作流程

字符流的操作流程与字节流类似,但在应用程序和操作系统之间增加了一个额外的步骤:字符编码/解码。

  1. 字符集和编码:字符流使用特定的字符集(如 UTF-8, ISO-8859-1 等)来表示文本。每个字符集都有自己的编码方式,即如何将字符映射到字节序列。例如,UTF-8 是一种变长编码,它可以使用 1 到 4 个字节来表示一个字符。

  2. 转换层:当应用程序使用字符流进行 I/O 操作时,Java 运行时环境(JRE)或应用程序本身会在应用程序和操作系统之间提供一个转换层。这个转换层负责将字符转换为字节(写操作)或将字节转换为字符(读操作)。

  3. 系统调用:转换后的字节数据通过系统调用传递给操作系统,进行实际的 I/O 操作。对于读操作,操作系统返回的字节数据会被转换层转换回字符,然后再传递给应用程序。

  4. 缓冲区:类似于字节流,字符流也会利用缓冲区来提高性能。不过,在字符流的情况下,缓冲区可能包含的是字符而不是字节。当缓冲区满或清空时,转换层会执行必要的编码/解码操作。

  5. BOM (Byte Order Mark):对于某些字符编码(如 UTF-16, UTF-32),文件开头可能会有一个字节顺序标记(BOM),用于指示字节序。字符流在读取时会自动识别并处理 BOM。

  6. 错误处理:如果遇到无法解码的字节序列,字符流可能会抛出异常或以某种方式处理无效字符(如替换为替代字符)。

流程对比

  • 直接性:字节流更直接地与操作系统和硬件交互,因为它们处理的是原始的字节数据。字符流则需要经过额外的字符编码/解码步骤。
  • 适用场景:字节流适用于处理二进制数据或需要精确控制每个字节的情况;字符流更适合处理文本数据,尤其是涉及多种字符集和语言的情况。
  • 性能:由于字符流涉及到额外的编码/解码操作,它们的性能通常比字节流稍低。然而,现代 JVM 和操作系统对这些操作进行了优化,使得性能差异在大多数情况下是可以接受的。

实际应用中的 I/O 操作

在 Java 中,当你使用 InputStreamOutputStream 时,你实际上是在直接与操作系统进行字节级别的 I/O 操作。而当你使用 ReaderWriter 时,你是在通过 Java 提供的转换层进行字符级别的 I/O 操作。例如:

  • 字节流示例

    try (InputStream in = new FileInputStream("example.bin")) {
        int byteValue;
        while ((byteValue = in.read()) != -1) {
            System.out.print((char) byteValue);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    
  • 字符流示例

    try (Reader reader = new FileReader("example.txt")) {
        int charValue;
        while ((charValue = reader.read()) != -1) {
            System.out.print((char) charValue);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    

在这个字符流的例子中,FileReader 内部使用了 FileInputStream 来读取字节,并通过平台默认的字符集将字节转换为字符。如果你明确指定了字符集(例如通过 InputStreamReader),那么你可以控制具体的编码/解码过程。

总结

无论是字节流还是字符流,最终的操作都是由操作系统执行的,但字符流在应用程序和操作系统之间增加了一层字符编码/解码的逻辑。理解这两者的工作原理有助于编写高效且正确的 I/O 代码,特别是在处理不同字符集和多语言文本时。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值