IO流以byte(字节)为最小单位,因此也称为字节流

在Java中,InputStream代表输入字节流,OuputStream代表输出字节流,这是最基本的两种IO流。

【添油加醋的Java基础】第七章 输入和输出流_输出流

第一节 数据流的基本概念

在 Java 中,数据流(Streams)是一种用于处理输入和输出(I/O)的抽象。它们是用于从数据源读取数据或向数据目的地写入数据的顺序字节流。Java 提供了丰富的 I/O 类库,主要包括两大类:输入流(InputStream)和输出流(OutputStream)。

输入流和输出流

输入流(InputStream)

InputStream 是一个抽象类,表示字节输入流,用于从数据源(如文件、网络连接等)读取字节数据。常用的 InputStream 子类有:

  • FileInputStream:用于从文件中读取字节数据。
  • ByteArrayInputStream:用于从字节数组中读取数据。
  • BufferedInputStream:为其他输入流提供缓冲功能,提高读取效率。
  • DataInputStream:允许应用程序以机器无关的方式从底层输入流中读取基本 Java 数据类型。
输出流(OutputStream)

OutputStream 是一个抽象类,表示字节输出流,用于向数据目的地(如文件、网络连接等)写入字节数据。常用的 OutputStream 子类有:

  • FileOutputStream:用于向文件中写入字节数据。
  • ByteArrayOutputStream:用于将数据写入字节数组。
  • BufferedOutputStream:为其他输出流提供缓冲功能,提高写入效率。
  • DataOutputStream:允许应用程序以机器无关的方式将基本 Java 数据类型写入到底层输出流。

输入流和输出流的使用示例

输入流示例

下面的示例演示了如何使用 FileInputStream 从文件中读取数据:

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

public class FileInputStreamExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("example.txt")) {
            int content;
            while ((content = fis.read()) != -1) {
                // 输出读取到的字符
                System.out.print((char) content);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

try 语句后面的括号 (FileInputStream fis = new FileInputStream("example.txt")) 是 Java 7 引入的 try-with-resources 语法。这种语法用于简化资源管理,确保资源在使用完毕后自动关闭,无需手动在 finally 块中关闭资源。

try-with-resources 语法用于声明一个或多个资源,这些资源会在 try 语句结束时自动关闭。任何实现了 AutoCloseable 接口(包括所有实现了 Closeable 接口的类)的资源都可以用在 try-with-resources 语句中。

工作原理:

在 try-with-resources 语法中,资源被声明和初始化在 try 语句的括号中。资源的作用域仅限于 try 块及其相关的 catchfinally 块。Java 确保在 try 块退出时自动调用资源的 close 方法。

输出流示例

下面的示例演示了如何使用 FileOutputStream 向文件中写入数据:

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

public class FileOutputStreamExample {
    public static void main(String[] args) {
        try (FileOutputStream fos = new FileOutputStream("output.txt")) {
            String content = "Hello, World!";
            fos.write(content.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

缓冲流

为了提高 I/O 操作的效率,Java 提供了缓冲流(Buffered Streams)。缓冲流通过减少实际读写操作的次数来提高性能。

缓冲输入流
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class BufferedInputStreamExample {
    public static void main(String[] args) {
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("example.txt"))) {
            int content;
            while ((content = bis.read()) != -1) {
                System.out.print((char) content);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
缓冲输出流
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class BufferedOutputStreamExample {
    public static void main(String[] args) {
        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("output.txt"))) {
            String content = "Hello, World!";
            bos.write(content.getBytes());
            bos.flush(); // 确保所有缓冲区的内容都被写出
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

读写文本数据的字符流

对于读写文本数据,Java 提供了字符流(Reader 和 Writer)。这些类以字符为单位读写数据,更适合处理文本。

文件字符输入流
import java.io.FileReader;
import java.io.IOException;

public class FileReaderExample {
    public static void main(String[] args) {
        try (FileReader fr = new FileReader("example.txt")) {
            int content;
            while ((content = fr.read()) != -1) {
                System.out.print((char) content);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
文件字符输出流
import java.io.FileWriter;
import java.io.IOException;

public class FileWriterExample {
    public static void main(String[] args) {
        try (FileWriter fw = new FileWriter("output.txt")) {
            String content = "Hello, World!";
            fw.write(content);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Java 中的输入流和输出流用于处理数据的读写操作。输入流用于从数据源读取数据,而输出流用于向数据目的地写入数据。通过使用缓冲流和字符流,可以提高 I/O 操作的效率和处理文本数据的能力。这些基本概念和用法是进行 Java I/O 编程的基础。

第二节 基本字节数据流类

Java 提供了许多类用于处理字节流数据。这些类主要分为两大类:输入流类和输出流类。

输入流类(Input Streams)

输入流类用于从各种输入源(如文件、数组、网络连接等)读取数据。它们的父类是 java.io.InputStream。以下是一些基本的输入流类:

FileInputStream从文件中读取
  • 作用:从文件中读取字节。
  • 常用构造方法:
FileInputStream(String name)
FileInputStream(File file)
  • 示例:
package com.xml.a;

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

public class Test08 {
	public static void main(String[] args) throws IOException {
		System.out.println(System.getProperty("user.dir"));  // 是项目于的根目录;我这里是D:\MyJava\test04
		FileInputStream fis = new FileInputStream("src/com/xml/a/in_example.txt");
		int data;
		while((data = fis.read()) != -1) {
			System.out.println(data);
		}
		fis.close();
		
		System.out.println("----------");
		FileInputStream fis1 = new FileInputStream("src/com/xml/a/in_example.txt");
		int data1;
		while((data1 = fis1.read()) != -1) {
			System.out.println((char)data1);  //这里再处理中文字符的时候会出现乱码问题
		}
		fis1.close();
	}
}

src/com/xml/a/in_example.txt文件内容:

【添油加醋的Java基础】第七章 输入和输出流_夏明亮_02

执行结果:

D:\MyJava\test04
104
101
108
108
111
44
13
10
119
111
114
108
100
33
13
10
228
189
160
229
165
189
227
128
130
13
10
----------
h
e
l
l
o
,




w
o
r
l
d
!




ä
½
 
å
¥
½
ã
€
‚

如果直接使用 FileInputStream 按字节读取并强制转换为字符,可能会导致乱码问题。原因是中文字符在 UTF-8 等编码中占用多个字节,直接将单个字节转换为字符会造成错误的字符显示。

为了正确读取包含中文字符的文件,你需要使用 InputStreamReaderBufferedReader,并指定正确的字符编码。这个后续就会讲到。

ByteArrayInputStream从字节数组中读取
  • 作用:从字节数组中读取数据。
  • 常用构造方法:
ByteArrayInputStream(byte[] buf)
ByteArrayInputStream(byte[] buf, int offset, int length)
  • 示例:
package com.xml.a;

import java.io.ByteArrayInputStream;
import java.io.IOException;

public class Test09 {
	public static void main(String[] ags) {
		String a = "Hello, world! 你好";  //有中文有英文
		byte[] b = null;  // 变量声明并初始化
		ByteArrayInputStream c = null; // 变量声明并初始化
		b = a.getBytes(); // 获取字符串的字节数组
		System.out.println("长度:" + b.length +"; 首元素:" + b[0]); // 打印数组的信息
		c = new ByteArrayInputStream(b); // 对象赋值
		int data;
		while((data = c.read()) != -1) { // 逐个字节读取,直到-1
			System.out.println("原始值->char值:" + data + "->" + (char)data); //强制类型转换可能会造成乱码问题
		}
		
		try {
			c.close(); // 关闭对象
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

执行结果:

长度:20; 首元素:72
原始值->char值:72->H
原始值->char值:101->e
原始值->char值:108->l
原始值->char值:108->l
原始值->char值:111->o
原始值->char值:44->,
原始值->char值:32-> 
原始值->char值:119->w
原始值->char值:111->o
原始值->char值:114->r
原始值->char值:108->l
原始值->char值:100->d
原始值->char值:33->!
原始值->char值:32-> 
原始值->char值:228->ä
原始值->char值:189->½
原始值->char值:160-> 
原始值->char值:229->å
原始值->char值:165->¥
原始值->char值:189->½
BufferedInputStream为输入流增加缓冲
  • 作用:为另一个输入流添加缓冲功能,提高读取效率。
  • BufferedInputStream 必须与其他 InputStream 一起结合使用。BufferedInputStream 本质上是一个装饰器(decorator),它通过内部缓冲区提高其他 InputStream 的读取效率。它不能单独使用,必须包装在另一个 InputStream 对象之上。
  • 需要将其他InputStream作为自己的输入。
  • BufferedInputStream 通过维护一个内部缓冲区来减少直接的底层 I/O 操作。每次调用 read() 时,如果缓冲区不为空,它会直接从缓冲区中读取数据;如果缓冲区为空,它会一次性从底层 InputStream 中读取大量数据到缓冲区,从而减少了底层 I/O 操作的次数,提高了性能。
  • 常用构造方法:
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)
  • 示例1:BufferedInputStream与FileInputStream一起使用
package com.xml.a;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class Test11 {
	public static void main(String[] args) {
		System.out.println(System.getProperty("user.dir"));  // 是项目于的根目录;我这里是D:\MyJava\test04
		FileInputStream a = null;
		BufferedInputStream b = null;
		try {
			a = new FileInputStream("src/com/xml/a/in_example.txt");
			b = new BufferedInputStream(a);
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		int data;
		try {
			while((data = b.read()) != -1) {
				System.out.println((char)data);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try { // 用完关闭
			a.close();
			b.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

执行结果:

D:\MyJava\test04
h
e
l
l
o
,




w
o
r
l
d
!




ä
½
 
å
¥
½
ã
€
‚

可以看到这个例子和使用FileInputStream从文件中读取的例子结果是一样的。这两个示例都从一个文件 in_example.txt 中读取数据并输出字符,但它们的实现方式和效率有明显不同。

  • 示例2:BufferedInputStream与ByteArrayInputStream一起使用
package com.xml.a;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.BufferedInputStream;



public class Test12 {
	public static void main(String[] args) throws IOException {
		System.out.println(System.getProperty("user.dir"));  // 是项目于的根目录;我这里是D:\MyJava\test04
		String a = "Hello, world! 你好";  //有中文有英文
		byte[] b = null;  // 变量声明并初始化
		ByteArrayInputStream c = null; // 变量声明并初始化
		b = a.getBytes(); // 获取字符串的字节数组
		System.out.println("长度:" + b.length +"; 首元素:" + b[0]); // 打印数组的信息
		c = new ByteArrayInputStream(b); // 对象赋值
		BufferedInputStream d = null;
		d = new BufferedInputStream(c);
		int data;
		while((data = d.read()) != -1) { // 逐个字节读取,直到-1
			System.out.println("原始值->char值:" + data + "->" + (char)data);
		}
		c.close();
		d.close();
	}
}

执行结果:

D:\MyJava\test04
长度:20; 首元素:72
原始值->char值:72->H
原始值->char值:101->e
原始值->char值:108->l
原始值->char值:108->l
原始值->char值:111->o
原始值->char值:44->,
原始值->char值:32-> 
原始值->char值:119->w
原始值->char值:111->o
原始值->char值:114->r
原始值->char值:108->l
原始值->char值:100->d
原始值->char值:33->!
原始值->char值:32-> 
原始值->char值:228->ä
原始值->char值:189->½
原始值->char值:160-> 
原始值->char值:229->å
原始值->char值:165->¥
原始值->char值:189->½

还可以对任意流类对象进行包装后处理。

FileInputStream VS. BufferedInputStream

FileInputStream特点

  1. 逐字节读取
  • fis.read() 每次只读取一个字节。
  • 直接从文件中读取数据,没有缓冲机制。
  1. 性能
  • 每次读取操作都会进行一次文件 I/O 操作,系统调用较多,开销大,性能较低。

BufferedInputStream特点

  1. 缓冲机制
  • BufferedInputStream 使用内部缓冲区(默认大小为 8192 字节)来减少文件 I/O 操作的次数。
  • 读取操作先从缓冲区中获取数据,缓冲区空了再从文件中读取更多数据。
  1. 性能
  • 大幅减少系统调用次数,提高读取效率,性能较高。

证明两者不同的信息:

  1. 执行时间
  • 读取大文件时,使用 BufferedInputStream 的执行时间通常显著短于直接使用 FileInputStream
  1. 系统调用次数
  • 可以使用系统监控工具(如 Linux 下的 strace)跟踪系统调用。使用 BufferedInputStream 会显著减少 read 系统调用次数。
  1. CPU 使用率
  • 使用 BufferedInputStream 时,CPU 使用率通常较低,因为减少了频繁的 I/O 操作。

总的来说:

性能BufferedInputStream 大幅提高了读取性能,特别是在处理大文件时。

系统调用BufferedInputStream 减少了系统调用次数,降低了系统负担。

内存使用BufferedInputStream 使用内部缓冲区,占用更多内存。

DataInputStream从底层输入流中读取
  • 作用:允许应用程序以与机器无关的方式从底层输入流中读取基本 Java 数据类型(如 int、float、double 等)。它不能直接从键盘读取数据,因为它主要设计用于从任何二进制流中读取数据,如文件、网络连接等。
  • DataInputStream 是一个专门用于从输入流中读取基本数据类型的类,它可以直接读取 Java 数据类型的二进制表示,这在某些特定场景下非常有用。
  • 当需要从一个文件或数据流中读取以二进制格式存储的基本数据类型时,DataInputStream 提供了一种简便的方法。例如,读取一个包含多个基本数据类型的文件或网络数据包。
  • 在客户端和服务器之间传输数据时,常常使用二进制格式以节省带宽。DataInputStreamDataOutputStream 可以方便地将数据编码为二进制格式并进行解码。
  • 在某些自定义的数据序列化方案中,数据被写入二进制流,然后再被读取回来。DataInputStream 可以直接读取这些二进制格式的数据。
  • FileInputStream 只能以字节为单位读取数据。虽然可以通过 FileInputStream 读取字节,然后手动将这些字节转换为所需的数据类型,但这种方法既繁琐又容易出错。DataInputStream 提供了更高层次的抽象,直接从输入流中读取基本数据类型,简化了编程工作。
  • 不要简单第把这个"底层"理解为键盘了。
  • 常用构造方法:
DataInputStream(InputStream in)
  • 示例:
package com.xml.a;

import java.io.FileInputStream;
import java.io.DataInputStream;
import java.io.IOException;

public class Test10 {
	public static void main(String[] args) {
		FileInputStream a = null;
		DataInputStream b = null;
		try {
			a = new FileInputStream("src/com/xml/a/05.class_demo.exe");  // 一个由C++编译的二进制程序文件
			b = new DataInputStream(a);
			int intValue = b.readInt();
            double doubleValue = b.readDouble();
            float floatValue = b.readFloat();
            long longValue = b.readLong();
            // String stringValue = b.readUTF();  // 这个会报错
            
            System.out.println("Read int: " + intValue);
            System.out.println("Read double: " + doubleValue);
            System.out.println("Read float: " + floatValue);
            System.out.println("Read long: " + longValue);
            // System.out.println("Read UTF string: " + stringValue);
		}catch(IOException x){
			x.printStackTrace();
		}
		
		try {

			b.close(); // 关闭对象
		} catch (IOException x) {
			// TODO Auto-generated catch block
			x.printStackTrace();
		}
	}
}

执行结果:

Read int: 1297780736
Read double: 3.131513109177201E-294
Read float: NaN
Read long: -5188146770730811392

使用 DataInputStream 读取任意的二进制文件时,读取到的值并不代表文件中数据的实际含义,因为 DataInputStream 是按照特定的格式和字节顺序解释数据的。文件中数据的实际含义取决于文件格式和数据的存储方式,而不是 DataInputStream 的解释。

例如,假设一个二进制文件中包含以下字节序列:

00 00 00 04 40 09 21 FB 54 44 2D 18 3F F3 C0 CA A1 49 72 65 74 65

如果我们使用 DataInputStream 按顺序读取一个 int、一个 double、一个 float 和一个 long,并假设这个字节序列是这样安排的,那么 DataInputStream 将按如下方式解释它们:

如果我们使用 DataInputStream 按顺序读取一个 int、一个 double、一个 float 和一个 long,并假设这个字节序列是这样安排的,那么 DataInputStream 将按如下方式解释它们:

  1. 读取 int: 00 00 00 04
  • 解码为整数:4
  1. 读取 double: 40 09 21 FB 54 44 2D 18
  • 解码为双精度浮点数:3.14159
  1. 读取 float: 3F F3 C0 CA
  • 解码为单精度浮点数:1.19
  1. 读取 long: A1 49 72 65 74 65 2D 18
  • 解码为长整数:-6790122813228192792

要理解文件内容的实际含义,需要知道文件的格式。例如,一个图片文件、音频文件或其他格式的文件通常有特定的头信息和数据排列方式。DataInputStream 仅仅按字节顺序读取数据,而不理解文件的具体格式。

并且上述的示例中String stringValue = b.readUTF()会报错:

java.io.UTFDataFormatException: malformed input around byte 34
	at java.base/java.io.DataInputStream.readUTF(DataInputStream.java:641)
	at java.base/java.io.DataInputStream.readUTF(DataInputStream.java:550)
	at com.xml.a.Test10.main(Test10.java:18)

因为DataInputStream 期望以特定格式写入的二进制数据。例如,readInt() 期望读取4个字节并将其解释为一个整数,readDouble() 期望读取8个字节并将其解释为一个双精度浮点数,readUTF() 期望读取一个UTF-8编码的字符串。然而,你的文件 05.class_demo.exe 的内容并不符合这些期望。

因此,这个类是比较适合使用由DataOutputStream输出的数据的(理论上任意二进制都行),且在读取时事实上应该清楚被读取对象的结构。

输出流类(Output Streams)

输出流类用于将数据写入各种输出目标(如文件、数组、网络连接等)。

它们的父类是 java.io.OutputStream。以下是一些基本的输出流类:

FileOutputStream
  • 作用:将数据写入文件。
  • 常用构造方法:
FileOutputStream(String name)
FileOutputStream(File file)
FileOutputStream(String name, boolean append)
  • 示例:
package com.xml.a;

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

public class Test21 {
	public static void main(String[] args) throws IOException {
		System.out.println(System.getProperty("user.dir"));  // 是项目于的根目录;我这里是D:\MyJava\test04
//		FileOutputStream a = new FileOutputStream("src/com/xml/a/out_example.txt", true); // true附加模式
//		b.write("Never Give Up1!\r".getBytes());
//		b.write("Never Give Up2!\r".getBytes());
//		b.write("Never Give Up2!\r".getBytes());
//		b.close();
		FileOutputStream a = new FileOutputStream("src/com/xml/a/out_example.txt"); // 默认是写入模式,覆盖原本内容
		a.write("Hello, Xiamingliang!".getBytes()); 
		a.close();
	}
}

运行后如果D:\MyJava\test04\src\com\xml\a\out_example.txt文件不存在则会自动创建。

【添油加醋的Java基础】第七章 输入和输出流_输出流_03

ByteArrayOutputStream
  • 作用:将数据写入字节数组。
  • 常用构造方法:
ByteArrayOutputStream()
ByteArrayOutputStream(int size)
  • 示例:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write("Hello, world!".getBytes());
byte[] bytes = baos.toByteArray();
baos.close();

**思考下:**示例与和byte[] bytes = "Hello, world!".getBytes()由什么区别呢?都是将字符转存入byte数组中吧?

这两种方式都可以将字符串转换为字节数组,但它们的实现方式有所不同,具体区别如下:

  1. ByteArrayOutputStream
  • ByteArrayOutputStream 是一个输出流,它将数据写入到内存中的字节数组中。
  • 当你需要将数据写入到字节数组中,并且希望能够动态地增加数组大小时,可以使用 ByteArrayOutputStream
  • 使用 write() 方法将数据写入 ByteArrayOutputStream 中,然后使用 toByteArray() 方法获取字节数组。
  1. String.getBytes()
  • String.getBytes() 方法是 String 类的方法,用于将字符串转换为字节数组。
  • 它将字符串转换为使用平台默认字符集编码的字节数组。
  • 这种方式适用于简单的字符串转换,并且不需要额外的输出流。

一般来说,如果你需要在程序中动态地将数据写入到字节数组中,你可以选择使用 ByteArrayOutputStream。如果你只需要将一个字符串转换为字节数组,而不需要其他操作,你可以直接使用 String.getBytes() 方法。

BufferedOutputStream
  • 作用:为另一个输出流添加缓冲功能,提高写入效率。
  • 常用构造方法:
BufferedOutputStream(OutputStream out)
BufferedOutputStream(OutputStream out, int size)
  • 示例:
FileOutputStream fos = new FileOutputStream("example.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write("Hello, world!".getBytes());
bos.close();
fos.close();
DataOutputStream
  • 作用:允许应用程序以与机器无关的方式将基本 Java 数据类型写入底层输出流。
  • 常用构造方法:
DataOutputStream(OutputStream out)
  • 示例:
FileOutputStream fos = new FileOutputStream("example.dat");
DataOutputStream dos = new DataOutputStream(fos);
dos.writeInt(42);
dos.writeDouble(3.14159);
dos.close();
fos.close();

组合使用输入流和输出流

通常,输入流和输出流类可以组合使用,以实现更强大的数据处理功能。例如,可以将 BufferedInputStreamBufferedOutputStreamFileInputStreamFileOutputStream 组合使用,以提高文件读写的效率。

Java 中的基本字节数据流类提供了对字节数据进行读写操作的基本方法。通过合理使用这些类,可以有效地进行文件操作、网络通信以及其他需要处理字节数据的场景。使用缓冲流(如 BufferedInputStreamBufferedOutputStream)可以显著提高 I/O 操作的效率。

第三节 基本字符流

基本概念与使用

基本字节流和字符流的主要区别在于它们处理的数据类型不同:字节流操作字节数据,而字符流操作字符数据。

在Java中,基本字符流用于处理字符数据。基本字符流主要有以下几种:

  1. FileReader:用于从文件中读取字符数据的字符输入流。
FileReader reader = new FileReader("file.txt");
  1. FileWriter:用于向文件中写入字符数据的字符输出流。
FileWriter writer = new FileWriter("file.txt"); //写入时以覆盖模式
FileWriter writer = new FileWriter("file.txt", true); //写入时以附加模式
  1. BufferedReader:用于缓冲输入字符流,提供了读取文本的一些便捷方法。
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));

BufferedReader 提供了 readLine() 方法,可以一次读取一行文本。

  1. BufferedWriter:用于缓冲输出字符流,提供了写入文本的一些便捷方法。
BufferedWriter writer = new BufferedWriter(new FileWriter("file.txt"));
  1. CharArrayReader:用于从字符数组中读取字符数据的字符输入流。
char[] chars = {'a', 'b', 'c'};
CharArrayReader reader = new CharArrayReader(chars);
  1. CharArrayWriter:用于向字符数组中写入字符数据的字符输出流。
CharArrayWriter writer = new CharArrayWriter();
writer.write('a');

这些基本字符流可以处理字符数据,提供了读取和写入字符的方法,并且可以与其他字符流组合使用,例如使用 BufferedReaderBufferedWriter 进行缓冲处理,提高读写效率。

字符流 VS. 字符流

基本字节流示例(FileInputStream 和 FileOutputStream)
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ByteStreamExample {
    public static void main(String[] args) {
        String filename = "example.txt";

        // 写入字节数据到文件
        try (FileOutputStream fos = new FileOutputStream(filename)) {
            byte[] data = {65, 66, 67}; // ASCII码值对应的字母 A, B, C
            fos.write(data);
            System.out.println("Bytes written to file successfully.");
        } catch (IOException e) {
            System.out.println("An error occurred while writing to the file: " + e.getMessage());
        }

        // 从文件中读取字节数据
        try (FileInputStream fis = new FileInputStream(filename)) {
            int byteData;
            while ((byteData = fis.read()) != -1) {
                System.out.print((char) byteData); // 将字节数据转换为字符输出
            }
        } catch (IOException e) {
            System.out.println("An error occurred while reading from the file: " + e.getMessage());
        }
    }
}

在上面的示例中,我们使用了 FileInputStreamFileOutputStream,它们是基本的字节流。我们使用字节数组来写入字节数据到文件,然后再使用字节流从文件中读取字节数据,并将其转换为字符输出。

基本字符流示例(FileReader 和 FileWriter)
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CharacterStreamExample {
    public static void main(String[] args) {
        String filename = "example.txt";

        // 写入字符数据到文件
        try (FileWriter writer = new FileWriter(filename)) {
            writer.write("ABC"); // 写入字符数据到文件
            System.out.println("Characters written to file successfully.");
        } catch (IOException e) {
            System.out.println("An error occurred while writing to the file: " + e.getMessage());
        }

        // 从文件中读取字符数据
        try (FileReader reader = new FileReader(filename)) {
            int charData;
            while ((charData = reader.read()) != -1) {
                System.out.print((char) charData); // 直接输出读取到的字符数据
            }
        } catch (IOException e) {
            System.out.println("An error occurred while reading from the file: " + e.getMessage());
        }
    }
}

在这个示例中,我们使用了 FileReaderFileWriter,它们是基本的字符流。我们直接使用字符数据来写入到文件,然后再使用字符流从文件中读取字符数据,直接输出字符数据。

  • 基本字节流(例如 FileInputStreamFileOutputStream)操作字节数据,可以用于处理任何类型的文件,但是不能直接处理文本文件中的字符数据。
  • 基本字符流(例如 FileReaderFileWriter)操作字符数据,专门用于处理文本文件中的字符数据,提供了直接读写字符的方法,并且能够正确处理字符编码。

第四节 文件的处理

文件和I/O

还有一些关于文件和I/O的类,我们也需要知道:

  • File Class(类)
  • FileReader Class(类)
  • FileWriter Class(类)

Java中的目录

创建目录:

File类中有两个方法可以用来创建文件夹:

  • **mkdir( )**方法创建一个文件夹,成功则返回true,失败则返回false。失败表明File对象指定的路径已经存在,或者由于整个路径还不存在,该文件夹不能被创建。
  • **mkdirs()**方法创建一个文件夹和它的所有父文件夹。

下面的例子创建 "/tmp/user/java/bin"文件夹:

import java.io.File;
 
public class CreateDir {
    public static void main(String[] args) {
        String dirname = "/tmp/xxx";
        File d = new File(dirname);
        // 现在创建目录
        d.mkdirs();
    }
}

编译并执行上面代码来创建目录 "/tmp/xxx"。

注意: Java 在 UNIX 和 Windows 自动按约定分辨文件路径分隔符。如果你在 Windows 版本的 Java 中使用分隔符 (/) ,路径依然能够被正确解析。

读取目录

一个目录其实就是一个 File 对象,它包含其他文件和文件夹。

如果创建一个 File 对象并且它是一个目录,那么调用 isDirectory() 方法会返回 true。

可以通过调用该对象上的 list() 方法,来提取它包含的文件和文件夹的列表。

下面展示的例子说明如何使用 list() 方法来检查一个文件夹中包含的内容:

import java.io.File;
 
public class DirList {
    public static void main(String args[]) {
        String dirname = "/tmp";
        File f1 = new File(dirname);
        if (f1.isDirectory()) {
            System.out.println("目录 " + dirname);
            String s[] = f1.list();
            for (int i = 0; i < s.length; i++) {
                File f = new File(dirname + "/" + s[i]);
                if (f.isDirectory()) {
                    System.out.println(s[i] + " 是一个目录");
                } else {
                    System.out.println(s[i] + " 是一个文件");
                }
            }
        } else {
            System.out.println(dirname + " 不是一个目录");
        }
    }
}

以上实例编译运行结果如下:

目录 /tmp
xxx 是一个目录
lib 是一个目录
demo 是一个目录
test.txt 是一个文件
README 是一个文件
index.html 是一个文件
include 是一个目录
删除目录或文件

删除文件可以使用 java.io.File.delete() 方法。

以下代码会删除目录 /tmp/java/,需要注意的是当删除某一目录时,必须保证该目录下没有其他文件才能正确删除,否则将删除失败。

测试目录结构:

/tmp/java/
|-- 1.log
|-- test

代码如下:

import java.io.File;
 
public class DeleteFileDemo {
    public static void main(String[] args) {
        // 这里修改为自己的测试目录
        File folder = new File("/tmp/java/");
        deleteFolder(folder);
    }
 
    // 删除文件及目录
    public static void deleteFolder(File folder) {
        File[] files = folder.listFiles();
        if (files != null) {
            for (File f : files) {
                if (f.isDirectory()) {
                    deleteFolder(f);
                } else {
                    f.delete();
                }
            }
        }
        folder.delete();
    }
}

本章小结

习题