JavaSE中的IO(输入/输出)知识是一个广泛的领域,它涵盖了如何在Java程序中进行数据的读取和写入。以下是对JavaSE中IO知识的一个清晰归纳:
一、基础知识
流(Stream)的概念
流是一组有顺序的、有起点和终点的字节集合,用于数据传输。Java的I/O流提供了读写数据的标准方法。Java的I/O流分为输入流(从其他设备读取数据到内存)和输出流(从内存写出数据到其他设备)。
字节流与字符流
字节流:以字节为单位,读写数据的流。例如,InputStream和OutputStream是字节流的抽象类。
字符流:以字符为单位,读写数据的流。例如,Reader和Writer是字符流的抽象类。
主要类和方法
1. InputStream
字节输入流的抽象类,包含read(), read(byte[] b), close()等方法。下面是一个java代码的示例,说明了InputStream的read()、read(byte[] b)和close()方法的用法:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class InputStreamExample {
public static void main(String[] args) {
try {
// 创建一个FileInputStream对象
InputStream inputStream = new FileInputStream("example.txt");
// 使用read()方法逐个字节读取文件内容并打印
int data;
while ((data = inputStream.read()) != -1) {
System.out.print((char) data);
}
// 关闭输入流
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上面的例子中,我们创建了一个FileInputStream对象来读取名为"example.txt"的文件。然后,使用read()方法逐个字节地读取文件内容,直到读取到文件的末尾(返回-1)。每次读取的字节数据都会被转换为字符并打印出来。
下面是另一个示例,演示了使用read(byte[] b)方法从InputStream中读取多个字节到一个字节数组中:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class InputStreamExample {
public static void main(String[] args) {
try {
// 创建一个FileInputStream对象
InputStream inputStream = new FileInputStream("example.txt");
// 创建一个byte数组用于存储读取的数据
byte[] buffer = new byte[1024];
// 使用read(byte[] b)方法读取文件内容并打印
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
System.out.write(buffer, 0, bytesRead);
}
// 关闭输入流
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个示例中,我们创建了一个大小为1024的字节数组作为缓冲区。然后,使用read(byte[] b)方法从输入流中读取数据,将读取的字节数保存到bytesRead变量中。接下来,通过System.out.write()方法将读取到的数据以字符的形式打印出来。
最后,我们使用close()方法关闭输入流,确保资源被正确释放。
2.OutputStream
字节输出流的抽象类,包含write(int b), write(byte[] b), close(), flush()等方法。下面是一个java代码的示例,说明了OutputStream的write(int b),write(byte[] b),close()和flush()方法的用法:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class OutputStreamExample {
public static void main(String[] args) {
try {
// 创建一个FileOutputStream对象
OutputStream outputStream = new FileOutputStream("example.txt");
// 使用write(int b)方法将单个字节写入文件
outputStream.write(65); // 写入字母"A"
// 使用write(byte[] b)方法将字节数组写入文件
byte[] buffer = {66, 67, 68}; // 字节数组包含字母"B"、"C"和"D"
outputStream.write(buffer);
// 关闭输出流
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上面的例子中,我们创建了一个FileOutputStream对象来写入名为"example.txt"的文件。然后,使用write(int b)方法将单个字节写入文件。这里使用了ASCII码值65,代表字母"A"。接下来,使用write(byte[] b)方法将字节数组写入文件。字节数组包含ASCII码值为66、67和68的字节,分别对应字母"B"、“C"和"D”。
最后,我们使用close()方法关闭输出流,确保资源被正确释放。
另外,OutputStream还有一个flush()方法,用于强制将缓冲区中的数据写入到输出流中。下面是一个示例:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class OutputStreamExample {
public static void main(String[] args) {
try {
// 创建一个FileOutputStream对象
OutputStream outputStream = new FileOutputStream("example.txt");
// 使用write()方法将字节写入缓冲区
outputStream.write(65); // 写入字母"A"
// 使用flush()方法强制将缓冲区中的数据写入输出流
outputStream.flush();
// 关闭输出流
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个示例中,我们写入了字母"A"到输出流的缓冲区中,然后使用flush()方法将缓冲区中的数据强制写入输出流。
这些是OutputStream的基本用法,可以通过这些方法将字节写入到输出流中。
3.Reader和Writer
分别为字符输入流和字符输出流的抽象类,提供了类似字节流的方法,但操作的是字符。下面是一个java代码的示例,说明了Reader和Writer的基本用法:
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
public class ReaderWriterExample {
public static void main(String[] args) {
try {
// 创建一个FileReader对象来读取文件
Reader reader = new FileReader("input.txt");
// 创建一个FileWriter对象来写入文件
Writer writer = new FileWriter("output.txt");
// 使用read()方法逐个字符读取文件内容并写入输出文件
int character;
while ((character = reader.read()) != -1) {
writer.write(character);
}
// 关闭输入和输出流
reader.close();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上面的例子中,我们创建了一个FileReader对象来读取名为"input.txt"的文件。然后,我们创建了一个FileWriter对象来写入名为"output.txt"的文件。使用read()方法逐个字符地读取输入文件的内容,并使用write()方法将字符写入输出文件。
最后,我们使用close()方法关闭输入和输出流,确保资源被正确释放。
这些是Reader和Writer的基本用法,可以通过这些方法进行字符的输入和输出操作。
二、常用子类
1.文件流
FileInputStream和FileOutputStream:用于从文件和向文件读写字节。
FileReader和FileWriter:用于从文件和向文件读写字符。
字节流适用于处理二进制数据,字符流适用于处理文本数据。对于文本数据,推荐使用字符流,因为字符流可以自动处理字符编码的转换,而字节流需要手动进行编码和解码操作。
下面是一个Java代码示例,分别使用FileInputStream、FileOutputStream、FileReader和FileWriter来读写字节和字符:
使用FileInputStream和FileOutputStream读写字节:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileInputStreamExample {
public static void main(String[] args) {
try {
// 创建一个FileInputStream对象来读取文件
FileInputStream fis = new FileInputStream("input.txt");
// 创建一个FileOutputStream对象来写入文件
FileOutputStream fos = new FileOutputStream("output.txt");
// 使用read()方法读取字节,并使用write()方法写入字节
int data;
while ((data = fis.read()) != -1) {
fos.write(data);
}
// 关闭输入和输出流
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用FileReader和FileWriter读写字符:
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileReaderWriterExample {
public static void main(String[] args) {
try {
// 创建一个FileReader对象来读取文件
FileReader fr = new FileReader("input.txt");
// 创建一个FileWriter对象来写入文件
FileWriter fw = new FileWriter("output.txt");
// 使用read()方法读取字符,并使用write()方法写入字符
int data;
while ((data = fr.read()) != -1) {
fw.write(data);
}
// 关闭输入和输出流
fr.close();
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这两个例子中,我们使用了不同的输入流和输出流来进行字节和字符的读写操作。在读取文件时,我们使用read()方法读取每个字节或字符,并使用write()方法将字节或字符写入输出文件。
最后,我们使用close()方法关闭输入和输出流,确保资源被正确释放。
这些是使用FileInputStream、FileOutputStream、FileReader和FileWriter进行字节和字符读写的基本示例。
2.缓冲流
BufferedInputStream和BufferedOutputStream:提供了带缓冲区的字节流,可以提高读写效率。
BufferedReader和BufferedWriter:提供了带缓冲区的字符流。
下面我将用代码举例说明如何使用BufferedInputStream和BufferedOutputStream以及BufferedReader和BufferedWriter。
- 使用BufferedInputStream和BufferedOutputStream从文件中读取和写入字节:
import java.io.*;
public class BufferedStreamExample {
public static void main(String[] args) {
String sourceFile = "source.txt";
String targetFile = "target.txt";
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(targetFile))) {
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int bytesRead;
while ((bytesRead = bis.read(buffer, 0, bufferSize)) != -1) {
bos.write(buffer, 0, bytesRead);
}
System.out.println("File copied successfully!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上面的示例中,我们使用BufferedInputStream从名为"source.txt"的文件中读取字节,并使用BufferedOutputStream将字节写入名为"target.txt"的文件中。我们使用了一个内部缓冲区,大小为1024字节,并循环读取和写入缓冲区中的字节。
- 使用BufferedReader和BufferedWriter从文件中读取和写入字符:
import java.io.*;
public class BufferedWriterExample {
public static void main(String[] args) {
String sourceFile = "source.txt";
String targetFile = "target.txt";
try (BufferedReader reader = new BufferedReader(new FileReader(sourceFile));
BufferedWriter writer = new BufferedWriter(new FileWriter(targetFile))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
System.out.println("File copied successfully!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上面的示例中,我们使用BufferedReader从名为"source.txt"的文件中读取字符,并使用BufferedWriter将字符写入名为"target.txt"的文件中。我们循环读取每一行字符,并将其写入目标文件。我们使用了一个内部缓冲区来提高读写效率,并使用writer.newLine()方法在每个写入行的末尾添加一个换行符。
这些示例演示了如何使用缓冲流来提高读写操作的效率,并且适用于处理字节和字符数据。请注意,使用完缓冲流后,我们需要及时关闭它们,以确保资源被释放。以上示例中使用了try-with-resources语句,可以在不需要显式关闭流的情况下自动关闭它们。
3.转换流
InputStreamReader和OutputStreamWriter:用于在字节流和字符流之间进行转换。
InputStreamReader是将字节流转换为字符流的桥梁。它读取字节并使用指定的字符编码将其转换为字符。它提供了读取字符的方法,比如read()和readLine()。
OutputStreamWriter则是将字符流转换为字节流的桥梁。它将字符写入输出流,并使用指定的字符编码将其转换为字节。它提供了写入字符的方法,比如write()和flush()。
下面是一个示例,展示如何使用InputStreamReader和OutputStreamWriter进行字节流和字符流的转换:
import java.io.*;
public class InputStreamReaderExample {
public static void main(String[] args) {
String sourceFile = "source.txt";
String targetFile = "target.txt";
try (FileInputStream fis = new FileInputStream(sourceFile);
FileOutputStream fos = new FileOutputStream(targetFile);
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8")) {
int c;
while ((c = isr.read()) != -1) {
osw.write(c);
}
System.out.println("File converted successfully!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上面的示例中,我们使用FileInputStream从名为"source.txt"的文件中读取字节。然后,我们使用InputStreamReader将字节流转换为字符流,并指定了字符编码为UTF-8。接着,我们使用FileOutputStream将字符流转换为字节流,并指定了字符编码为UTF-8。最后,我们使用OutputStreamWriter将字符写入名为"target.txt"的文件中。
请注意,示例中的try-with-resources语句用于自动关闭流。此外,我们还可以在InputStreamReader和OutputStreamWriter的构造函数中指定其他的字符编码,以根据实际需求进行转换。
三、其他重要概念
1.编码:
不同的编码方式会影响字符和字节之间的转换。常见的编码方式有GBK、UTF-8、UTF-16等。
2.File类:
java.io.File类用于表示文件或目录,提供了许多操作文件和目录的方法,如创建、删除、判断是否存在等。
当使用File类时,我们可以使用下面的几个例子来说明它的用法:
- 创建文件:
File file = new File("path/to/file.txt");
try {
boolean created = file.createNewFile();
if (created) {
System.out.println("文件创建成功!");
} else {
System.out.println("文件已存在!");
}
} catch (IOException e) {
e.printStackTrace();
}
- 创建目录:
File dir = new File("path/to/directory");
boolean created = dir.mkdir();
if (created) {
System.out.println("目录创建成功!");
} else {
System.out.println("目录已存在!");
}
- 删除文件:
File file = new File("path/to/file.txt");
boolean deleted = file.delete();
if (deleted) {
System.out.println("文件删除成功!");
} else {
System.out.println("文件不存在或删除失败!");
}
- 判断文件是否存在并获取文件属性:
File file = new File("path/to/file.txt");
if (file.exists()) {
System.out.println("文件存在!");
System.out.println("文件路径:" + file.getPath());
System.out.println("文件大小:" + file.length() + "字节");
System.out.println("最后修改时间:" + new Date(file.lastModified()));
} else {
System.out.println("文件不存在!");
}
- 重命名文件:
File file = new File("path/to/file.txt");
File renamedFile = new File("path/to/renamedFile.txt");
boolean renamed = file.renameTo(renamedFile);
if (renamed) {
System.out.println("文件重命名成功!");
} else {
System.out.println("文件重命名失败!");
}
RandomAccessFile类:
RandomAccessFile类是Java提供的一个用于随机访问文件的类。它既可以读取文件内容,也可以向文件中写入数据,并且可以在文件中定位到指定的位置进行读写操作。
下面是一些RandomAccessFile类的例子:
- 读取文件内容:
RandomAccessFile raf = new RandomAccessFile("path/to/file.txt", "r");
byte[] buffer = new byte[1024];
int bytesRead = raf.read(buffer);
String contents = new String(buffer, 0, bytesRead);
System.out.println("文件内容:" + contents);
raf.close();
- 向文件中写入数据:
RandomAccessFile raf = new RandomAccessFile("path/to/file.txt", "rw");
String data = "Hello, world!";
raf.write(data.getBytes());
raf.close();
System.out.println("数据已写入文件!");
- 定位到指定位置进行读写操作:
RandomAccessFile raf = new RandomAccessFile("path/to/file.txt", "rw");
raf.seek(10); // 定位到文件的第10个字节处
byte[] buffer = new byte[1024];
int bytesRead = raf.read(buffer);
String contents = new String(buffer, 0, bytesRead);
System.out.println("文件内容:" + contents);
raf.close();
RandomAccessFile类还提供了其他方法,如获取文件长度、设置文件指针位置、截取文件等。通过使用RandomAccessFile类,你可以更灵活地读取和写入文件的内容,并且可以随时定位到文件的任意位置进行操作。
3.序列化与反序列化:
序列化和反序列化是Java中用于对象持久化和网络传输的重要概念。
序列化(Serialization)是指将对象转换为字节序列的过程,可以将对象保存在磁盘中或通过网络进行传输。在序列化过程中,对象的状态信息被转换为字节数据,可以包括对象的属性值、方法等信息。Java中的序列化由java.io.Serializable接口实现,只有实现了该接口的类才能被序列化。
下面是一个简单的示例,展示了如何将一个对象进行序列化:
import java.io.*;
public class SerializationExample {
public static void main(String[] args) {
// 创建一个对象
Person person = new Person("John", 25);
try {
// 创建一个输出流,并将对象进行序列化
FileOutputStream fileOut = new FileOutputStream("person.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(person);
out.close();
fileOut.close();
System.out.println("对象已序列化并保存在 person.ser 文件中");
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
反序列化(Deserialization)是指将字节序列恢复为对象的过程。反序列化可以从磁盘文件或网络传输的字节流中恢复出对象的状态信息,并创建一个对象实例。Java中的反序列化通过ObjectInputStream类来实现。
下面是一个简单的示例,展示了如何从文件中反序列化一个对象:
import java.io.*;
public class DeserializationExample {
public static void main(String[] args) {
Person person = null;
try {
// 创建一个输入流,并从文件中读取字节序列反序列化为对象
FileInputStream fileIn = new FileInputStream("person.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
person = (Person) in.readObject();
in.close();
fileIn.close();
} catch (IOException e) {
e.printStackTrace();
return;
} catch (ClassNotFoundException e) {
e.printStackTrace();
return;
}
System.out.println("反序列化后的对象:");
System.out.println("姓名: " + person.getName());
System.out.println("年龄: " + person.getAge());
}
}
注意:要进行序列化和反序列化,对象的类必须加上implements Serializable接口,否则会抛出NotSerializableException异常。序列化是将对象转换为字节序列以便存储或传输的过程,反序列化则是将字节序列恢复为对象的过程。