【Linux】Java文件IO之普通IO与Buffer IO

发布时间:2024年01月05日

在Java中,输入输出(IO)操作是编程中一项重要的任务。无论是从外部文件中读取数据,还是向文件写入数据,或者是与用户进行交互,都需要用到IO操作。

普通IO

FileOutputStream的使用

FileOutputStream是Java中的一个类,属于java.io包。它用于将数据写入文件。当你需要将数据(通常是字节或字符)写入一个文件时,可以使用FileOutputStream。

以下是FileOutputStream的一些基本用法:

创建一个新的文件并写入数据:

package com.morris.io;

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

/**
 * 创建一个新的文件并写入数据
 */
public class FileOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        FileOutputStream outputStream = new FileOutputStream("/home/vagrant/testfileio/FileOutputStreamDemo.txt");
        outputStream.write("Hello, World!".getBytes());
        outputStream.close();
    }
}

我们可以使用strace命令追踪上面程序执行过程中产生的系统调用:

openat(AT_FDCWD, "/home/vagrant/testfileio/FileOutputStreamDemo.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 4
fstat(4, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
write(4, "Hello, World!", 13)           = 13
close(4)

向现有文件追加数据:

如果你想向一个已经存在的文件追加数据,而不是覆盖它,你可以使用FileOutputStream的另一个构造函数:

package com.morris.io;

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

/**
 * 向现有文件追加数据
 */
public class FileOutputStreamAppendDemo {
    public static void main(String[] args) throws IOException {
        FileOutputStream outputStream = new FileOutputStream("/home/vagrant/testfileio/FileOutputStreamDemo.txt", true);
        outputStream.write("Appended data".getBytes());
        outputStream.close();
    }
}

产生的系统调用如下:

openat(AT_FDCWD, "/home/vagrant/testfileio/FileOutputStreamDemo.txt", O_WRONLY|O_CREAT|O_APPEND, 0666) = 4
fstat(4, {st_mode=S_IFREG|0664, st_size=13, ...}) = 0
write(4, "Appended data", 13)           = 13
close(4)

可以看到在创建新的文件时,openat系统调用传的标志为O_TRUNC,在追加数据时传的标志为O_APPEND

O_TRUNCO_APPEND的区别:

  • O_TRUNC:打开一个文件时,如果文件已经存在,它的长度将被截断为 0,然后可以重新写入。换句话说,这个操作会清空文件内容。如果文件不存在,则会创建一个新文件。

  • O_APPEND:打开一个文件时,写操作总是发生在文件的末尾。即使写入操作在文件的开始部分进行,写指针也会被放在文件的末尾。如果文件不存在,则会创建一个新文件。

注意:当使用FileOutputStream或其他IO类时,始终建议在完成操作后关闭流。这可以通过使用try-with-resources语句或手动调用close()方法来完成。长时间不关闭流可能会导致资源泄露。

FileWriter的使用

FileWriter是Java中的一个类,它属于java.io包,主要用于将字符数据写入文件。这个类提供了方便的方法来将字符串或者字符数组写入文件。

FileWriter的构造方法有多种,可以根据不同的需求选择:

  • FileWriter(String fileName):根据给定的文件名构造一个FileWriter对象。如果文件不存在,它将被创建。如果文件已经存在,它的内容将被覆盖。

  • FileWriter(String fileName, boolean append):根据给定的文件名以及指示是否附加写入数据的boolean值来构造FileWriter对象。如果append为true,则数据将被附加到文件末尾而不是覆盖原有内容;如果append为false,则数据将覆盖原有内容。

  • FileWriter(File file):根据给定的File对象构造一个FileWriter对象。如果文件不存在,它将被创建。如果文件已经存在,它的内容将被覆盖。

  • FileWriter(FileDescriptor fd):构造与文件描述符关联的FileWriter对象。
    此外,FileWriter类还提供了flush()和close()方法来刷新缓冲区和关闭流。在完成写入操作后,应始终关闭流以释放系统资源。

需要注意的是,在某些平台上,如果一个文件已经被另一个FileOutputStream(或其他文件写入对象)打开进行写入,那么在这个文件上再使用FileWriter进行写入可能会失败。因此,在使用FileWriter时,应确保在尝试写入之前没有其他对象已经打开该文件进行写入操作。

FileWriter的使用如下:

package com.morris.io;

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

/**
 * FileWriter的使用,直接写入字符
 */
public class FileWriterDemo {
    public static void main(String[] args) throws IOException {
        FileWriter fileWriter = new FileWriter("/home/vagrant/testfileio/FileWriterDemo.txt");
        fileWriter.write("Hello, World!");
        fileWriter.close();
    }
}

产生的系统调用如下:

openat(AT_FDCWD, "/home/vagrant/testfileio/FileWriterDemo.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 4
fstat(4, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
write(4, "Hello, World!", 13)           = 13
close(4)

可以看到与FileOutputStream产生的系统调用一致,说明FileWriter只是java层面的封装,底层还是按字节来写。

FileOutputStream和FileWriter的区别

FileOutputStream和FileWriter都是Java中用于文件操作的类,但它们之间存在一些重要的区别。

  • 工作方式:FileOutputStream用于二进制数据的写入,每次读入或输出的是8位二进制。而FileWriter则是字符流,每次读入或输出的是16位二进制,即两个字节。

  • 处理方式:FileOutputStream是字节流,对于图像数据等二进制数据更为合适。而FileWriter是字符流,更适合处理unicode编码的字符数据,如文本文件。

带Buffer的IO

BufferedOutputStream的使用

BufferedOutputStream是一个缓冲输出流,用于将数据写入底层输出流。它通过建立内部缓冲区来存储数据,从而提高写入效率。使用BufferedOutputStream,应用程序可以将多个字节写入缓冲区,而不是为每个字节编写对底层系统的调用。

BufferedOutputStream的构造器可以接受一个OutputStream对象作为参数,用于创建一个新的缓冲输出流,将数据写入指定的底层输出流。

使用BufferedOutputStream时,可以调用write()方法将数据写入缓冲区。当缓冲区满时,数据会自动刷新到底层输出流中。此外,还可以调用flush()方法强制刷新缓冲区并将数据写入底层输出流。

BufferedOutputStream的使用如下:

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * BufferedOutputStream的使用
 */
public class BufferedOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        FileOutputStream outputStream = new FileOutputStream("/home/vagrant/testfileio/BufferedOutputStreamDemo.txt");
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
        bufferedOutputStream.write("Hello, World!".getBytes(StandardCharsets.UTF_8));
        bufferedOutputStream.close();
    }
}

产生的系统调用如下:

openat(AT_FDCWD, "/home/vagrant/testfileio/BufferedOutputStreamDemo.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 4
write(4, "Hello, World!", 13)           = 13
close(4)

可以看到与FileOutputStream产生的系统调用一致,那么BufferedOutputStream与BufferedOutputStream到底有什么区别呢?

FileOutputStream与BufferedOutputStream的区别

先来看一个数据的对比:

FileOutputStream速度测试:

package com.morris.io;

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

/**
 * FileOutputStream速度测试
 */
public class FileOutputStreamTest {
    public static void main(String[] args) throws IOException {
        FileOutputStream outputStream = new FileOutputStream("/home/vagrant/testfileio/FileOutputStreamTest.txt");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000_0000; i++) {
            outputStream.write("abc1234567890".getBytes());
        }
        outputStream.close();
        long end = System.currentTimeMillis();
        System.out.println("cost : " + (end - start));
    }
}

上面代码运行的结果耗时为10659

BufferedOutputStream速度测试:

package com.morris.io;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * BufferedOutputStream速度测试
 */
public class BufferedOutputStreamTest {
    public static void main(String[] args) throws IOException {
        FileOutputStream outputStream = new FileOutputStream("/home/vagrant/testfileio/BufferedOutputStreamTest.txt");
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000_0000; i++) {
            bufferedOutputStream.write("abc1234567890".getBytes());
        }
        bufferedOutputStream.close();
        long end = System.currentTimeMillis();
        System.out.println("cost : " + (end - start));
    }
}

上面代码运行的结果耗时为400

是什么原因会导致上面两个例子的耗时相差几十倍呢?

我们先来看下这两个例子对应的系统调用:

FileOutputStreamTest:

openat(AT_FDCWD, "/home/vagrant/testfileio/FileOutputStreamTest.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 4
fstat(4, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
write(4, "abc1234567890", 13)           = 13
write(4, "abc1234567890", 13)           = 13
write(4, "abc1234567890", 13)           = 13
write(4, "abc1234567890", 13)           = 13
write(4, "abc1234567890", 13)           = 13

BufferedOutputStreamTest:

openat(AT_FDCWD, "/home/vagrant/testfileio/BufferedOutputStreamTest.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 4
fstat(4, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
write(4, "abc1234567890abc1234567890abc123"..., 8190) = 8190
write(4, "abc1234567890abc1234567890abc123"..., 8190) = 8190
write(4, "abc1234567890abc1234567890abc123"..., 8190) = 8190
write(4, "abc1234567890abc1234567890abc123"..., 8190) = 8190
write(4, "abc1234567890abc1234567890abc123"..., 8190) = 8190

可以看到BufferedOutputStream内置了一个8kb的缓冲区来存储数据,当缓冲区满时,数据会被刷新到Page Cache中,大大的减少了系统调用的次数。这种机制允许BufferedOutputStream在需要时自动刷新缓冲区,确保数据及时写入底层输出流。

BufferedOutputStream是一个装饰者模式的应用。装饰者模式是一种设计模式,用于在不修改现有类的前提下,动态地给一个对象添加新的功能。

BufferedOutputStream作为一个装饰者,扩展了OutputStream的功能,添加了缓冲区来存储数据。它接受一个OutputStream对象作为参数,并在内部维护了一个缓冲区。通过缓冲区,它可以批量写入数据,而不是为每个字节都进行一次底层调用,从而提高了写入效率。

当应用程序调用BufferedOutputStream的write()方法时,数据被写入缓冲区而不是直接写入底层输出流。当缓冲区满时,数据会被刷新到底层输出流中。这种机制允许BufferedOutputStream在需要时自动刷新缓冲区,确保数据及时写入底层输出流。

BufferedWriter的使用

BufferedWriter用于将文本写入字符输出流,并缓冲字符以便更有效地写入单个字符、数组和字符串。它继承自Writer类,并提供了额外的缓冲功能来提高写入效率。

BufferedWriter的主要特点包括:

  • 缓冲机制:BufferedWriter内部维护了一个缓冲区,用于存储写入的数据。通过将数据写入缓冲区,可以提高写入效率,减少对底层输出流的直接写入操作。当缓冲区满时,数据会自动刷新到底层输出流中。

  • 数据类型:BufferedWriter的write()方法可以接受字符、字符数组和字符串作为参数,以便写入不同类型的数据。

  • 行分隔符:BufferedWriter提供了newLine()方法,用于写入一个行分隔符,以便在输出中自动添加换行符。这使得在输出文本时可以方便地添加换行操作。

  • 异常处理:BufferedWriter的方法可能会抛出IOException异常,因此需要在代码中进行适当的异常处理。

  • 构造函数参数:BufferedWriter的构造函数可以接受一个Writer对象作为参数,用于创建新的BufferedWriter对象。

  • 刷新缓存:BufferedWriter提供了flush()方法,用于强制刷新缓冲区并将数据写入底层输出流。

使用BufferedWriter时,可以将数据逐个写入缓冲区,而不是直接写入底层输出流。当需要将数据刷新到底层输出流时,可以调用flush()方法。此外,通过使用newLine()方法,可以在输出中自动添加换行符,简化换行操作。

BufferedWriter的使用:

package com.morris.io;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

/**
 * BufferedWriter的使用
 * 相当于FileWriter+BufferedOutputStream的结合
 */
public class BufferedWriterDemo {
    public static void main(String[] args) throws IOException {
        FileOutputStream outputStream = new FileOutputStream("/home/vagrant/testfileio/BufferedWriterDemo.txt");
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.write("Hello, World!");
        bufferedWriter.close();
    }
}

PrintWriter的使用

PrintWriter向文本输出流打印对象的格式化表示形式(Prints formatted representations of objects to a text-output stream)。

PrintWriter相对于BufferedWriter的好处在于,如果PrintWriter开启了自动刷新,那么当PrintWriter调用println,prinlf或format方法时,输出流中的数据就会自动刷新出去。PrintWriter不但能接收字符流,也能接收字节流。

PrintWriter的使用:

package com.morris.io;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

/**
 * PrintWriter的使用
 * 对BufferedWriter的升级
 */
public class PrintWriterDemo {
    public static void main(String[] args) throws IOException {
        FileOutputStream outputStream = new FileOutputStream("/home/vagrant/testfileio/BufferedWriterDemo.txt");
        PrintWriter bufferedWriter = new PrintWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.println("Hello, World!");
        bufferedWriter.close();
    }
}

Socket编程中,尽量用PrintWriter取代BufferedWriter,下面是PrintWriter的优点:

  • PrintWriter的print、println方法可以接受任意类型的参数,而BufferedWriter的write方法只能接受字符、字符数组和字符串;

  • PrintWriter的println方法自动添加换行,BufferedWriter需要显示调用newLine方法;

  • PrintWriter的方法不会抛异常,若关心异常,需要调用checkError方法看是否有异常发生;

  • PrintWriter构造方法可指定参数,实现自动刷新缓存(autoflush);

  • PrintWriter的构造方法更广。

文章来源:https://blog.csdn.net/u022812849/article/details/135414460
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。