Java 文件读写操作全解析
1. 数据表示与流的概念
在计算机中,有多种方式来表示数据。通过创建对象,我们已经接触过一种方式,对象包含以变量形式存在的数据以及对其他对象的引用,还包含利用这些数据完成任务的方法。
为了处理其他类型的数据,比如硬盘上的文件和 Web 服务器上的文档,我们可以使用
java.io
包中的类。该包名中的 “IO” 代表 “输入/输出”,这些类用于访问各种数据源,如硬盘、CD - ROM 或计算机内存。
我们可以通过一种称为流(streams)的通信系统将数据引入程序或从程序中输出。流是一种将信息从一个地方传输到另一个地方的对象。
在接下来的操作中,我们将使用流来完成以下任务:
- 从文件中读取字节到程序中
- 在计算机上创建新文件
- 将字节数组保存到文件中
- 修改文件中存储的数据
流可以分为两种类型:
| 流类型 | 描述 |
| ---- | ---- |
| 输入流(Input streams) | 从数据源读取数据 |
| 输出流(Output streams) | 向数据源写入数据 |
所有的输入和输出流都是由字节组成的,字节是取值范围在 0 到 255 之间的单个整数。我们可以使用这种格式来表示各种数据,如可执行程序、文字处理文档和 MP3 音乐文件等。Java 类文件以字节码(bytecode)的形式存储为字节,Java 解释器可以运行字节码,这些字节码不一定由 Java 语言生成,也可以是其他语言(如 NetRexx 和 JPython)编译生成的。
除了字节流,还有一种更专门的数据处理方式是以字符形式,即单个字母、数字、标点符号等。当读写文本文件、文字处理文档、网页等时,可以使用字符流。
无论处理的是字节流、字符流还是其他类型的信息,总体过程都是相似的:
1. 创建与数据关联的流对象。
2. 调用流的方法将信息放入流中或从流中取出信息。
3. 调用对象的
close()
方法关闭流。
2. Java 中的文件表示
在 Java 中,文件由
File
类表示,该类也是
java.io
包的一部分。文件可以从硬盘、软盘、CD - ROM 等存储设备中读取。
File
对象可以表示已存在的文件和想要创建的文件。创建
File
对象时,可以使用文件名作为构造函数的参数,例如:
File bookName = new File("address.dat");
这将在当前文件夹中为名为
address.dat
的文件创建一个对象。也可以在文件名中包含路径:
File bookName = new File("data\\address.dat");
在 Windows 系统中,路径和文件名使用反斜杠(
\
)作为分隔符,而 Linux 和其他基于 Unix 的系统使用正斜杠(
/
)。为了编写一个能在不同操作系统上都能正确引用文件的 Java 程序,可以使用
File.pathSeparator
类变量,例如:
File bookName = new File("data" + File.pathSeparator + "address.dat");
创建
File
对象后,可以调用一些有用的方法:
| 方法 | 描述 |
| ---- | ---- |
|
exists()
| 如果文件存在返回
true
,否则返回
false
|
|
getName()
| 以字符串形式返回文件的名称 |
|
length()
| 以长整型值返回文件的大小 |
|
createNewFile()
| 如果文件不存在,则创建同名文件 |
|
delete()
| 如果文件存在,则删除该文件 |
|
renameTo(File)
| 使用指定的
File
对象的名称重命名文件 |
此外,
File
对象还可以表示系统中的文件夹。可以在
File
构造函数中指定文件夹名称,该名称可以是绝对路径(如 “C:\MyDocuments\”)或相对路径(如 “java\database”)。创建表示文件夹的对象后,可以调用其
listFiles()
方法查看文件夹内的内容,该方法返回一个
File
对象数组,代表文件夹中包含的每个文件和子文件夹。
3. 从流中读取数据
第一个任务是使用输入流从文件中读取数据。我们可以使用
FileInputStream
类,它表示从文件中以字节形式读取的输入流。
创建文件输入流时,可以将文件名或
File
对象作为
FileInputStream()
构造函数的参数。在创建文件输入流之前,文件必须存在,否则在尝试创建流时会抛出
IOException
。为了处理这个异常,通常将涉及文件的所有语句放在一个
try - catch
块中,例如:
try {
File cookie = new File("cookie.web");
FileInputStream file = new FileInputStream(cookie);
System.out.println("Length of file: " + cookie.length());
} catch (IOException e) {
System.out.println("Could not read file.");
}
文件输入流以字节形式读取数据。我们可以通过调用流的
read()
方法(无参数)来读取单个字节。如果已经到达文件末尾,没有更多字节可用,则返回字节值 - 1。当读取输入流时,从流的第一个字节开始,例如文件的第一个字节。我们可以通过调用流的
skip()
方法并传入一个整数参数(表示要跳过的字节数)来跳过流中的一些字节,例如:
scanData.skip(1024);
如果想一次读取多个字节,可以按以下步骤操作:
1. 创建一个大小与要读取的字节数完全相同的字节数组。
2. 调用流的
read()
方法并传入该数组作为参数,数组将被填充从流中读取的字节。
下面是一个读取 MP3 音频文件中 ID3 数据的示例。由于 MP3 是一种流行的音乐文件格式,通常会在 ID3 文件末尾添加 128 个字节来保存歌曲的相关信息,如标题、艺术家和专辑等。
以下是
ReadID3.java
的完整代码:
import java.io.*;
public class ReadID3 {
public static void main(String[] arguments) {
try {
File song = new File(arguments[0]);
FileInputStream file = new FileInputStream(song);
int size = (int)song.length();
file.skip(size - 128);
byte[] last128 = new byte[128];
file.read(last128);
String id3 = new String(last128);
String tag = id3.substring(0, 3);
if (tag.equals("TAG")) {
System.out.println("Title: " + id3.substring(3, 32));
System.out.println("Artist: " + id3.substring(33, 62));
System.out.println("Album: " + id3.substring(63, 91));
System.out.println("Year: " + id3.substring(93, 97));
} else
System.out.println(arguments[0] + " does not contain" + " ID3 info.");
file.close();
} catch (Exception e) {
System.out.println("Error — " + e.toString());
}
}
}
编译这个源文件后,会创建一个
ReadID3
类文件。可以将其作为应用程序运行,并在命令行中指定系统上的一个 MP3 文件作为参数,例如:
java ReadID3 Everlong.mp3
如果系统上有
Everlong.mp3
文件,运行该应用程序可能会输出如下信息:
Title: Everlong
Artist: Foo Fighters
Album: The Colour and the Shape
Year: 1997
4. 缓冲输入流
提高读取输入流程序性能的一种方法是对输入进行缓冲。缓冲是将数据保存在内存中,以便程序需要时使用的过程。当 Java 程序需要从缓冲输入流中获取数据时,会先查看缓冲区,这比直接从文件等数据源读取要快。
要使用缓冲输入流,首先创建一个输入流(如
FileInputStream
对象),然后使用该对象创建缓冲流。调用
BufferedInputStream(InputStream)
构造函数,将输入流作为唯一参数。数据在从输入流读取时会被缓冲。
从缓冲流中读取数据时,调用其
read()
方法(无参数),会返回一个 0 到 255 之间的整数,表示流中的下一个字节数据。如果没有更多字节可用,则返回 - 1。
下面是一个使用缓冲流实现控制台输入的示例。
System
类中有一个类变量
in
,它表示一个
InputStream
对象,该对象从键盘接收输入并将其作为流提供。
以下是
ReadConsole.java
的完整代码:
import java.io.*;
public class ReadConsole {
public static String readLine() {
StringBuffer response = new StringBuffer();
try {
BufferedInputStream bin = new BufferedInputStream(System.in);
int in = 0;
char inChar;
do {
in = bin.read();
inChar = (char) in;
if (in != -1) {
response.append(inChar);
}
} while ((in != -1) & (inChar != '\n'));
bin.close();
return response.toString();
} catch (IOException e) {
System.out.println("Exception: " + e.getMessage());
return null;
}
}
public static void main(String[] arguments) {
System.out.print("You are standing at the end of the road ");
System.out.print("before a small brick building. Around you ");
System.out.print("is a forest. A small stream flows out of ");
System.out.println("the building and down a gully.\n");
System.out.print("> ");
String input = ReadConsole.readLine();
System.out.println("That’s not a verb I recognize.");
}
}
编译并运行该应用程序时,输出可能如下:
You are standing at the end of the road before a small brick building.
Around you is a forest. A small stream flows out of the building and
down a gully.
> go north
That’s not a verb I recognize.
ReadConsole
类包含一个类方法
readLine()
,用于从控制台接收字符。当按下回车键时,
readLine()
会返回一个包含所有接收到字符的
String
对象。如果将
ReadConsole
类保存在
CLASSPATH
环境变量列出的文件夹中,就可以在编写的任何 Java 程序中调用
ReadConsole.readLine()
方法。
5. 向流中写入数据
在
java.io
包中,处理流的类是成对出现的。例如,有用于处理字节流的
FileInputStream
和
FileOutputStream
类,用于处理字符流的
FileReader
和
FileWriter
类等。
要开始从字节流中读取数据,首先要创建一个与输出流关联的
File
对象,这个文件不一定需要在系统中存在。
创建
FileOutputStream
有两种方式:
- 如果要将字节追加到现有文件中,调用
FileOutputStream()
构造函数,传入两个参数:表示文件的
File
对象和布尔值
true
。写入流的字节将被添加到文件末尾。
- 如果要将字节写入新文件,调用
FileOutputStream()
构造函数,将
File
对象作为唯一参数。
创建输出流后,可以调用不同的
write()
方法向流中写入字节:
- 调用
write()
方法,传入一个字节作为唯一参数,将该字节写入流中。
- 调用
write()
方法,传入一个字节数组作为唯一参数,将数组中的所有字节写入流中。
- 调用
write()
方法,传入三个参数:一个字节数组、表示要写入流的数组的第一个元素的整数以及要写入的字节数。
以下是一个创建包含 10 个字节的字节数组并将最后 5 个字节写入输出流的示例:
File dat = new File("data.dat");
FileOutputStream datStream = new FileOutputStream(dat);
byte[] data = { 5, 12, 4, 13, 3, 15, 2, 17, 1, 18 };
datStream.write(data, 5, 5);
完成向流中写入字节后,调用流的
close()
方法关闭流。
6. 向 MP3 文件写入字节的示例
下面是一个将 MP3 文件中的部分字节替换为修改后数据的示例。
以下是
TrashMP3.java
的完整代码:
import java.io.*;
public class TrashMP3 {
static int INTERFERENCE = 500;
static int RANDOMBYTE = 5;
public static void main(String[] arguments) {
try {
File song = new File(arguments[0]);
FileInputStream file = new FileInputStream(song);
File trashedSong = new File("trashed.mp3");
FileOutputStream trash = new FileOutputStream(trashedSong);
boolean eof = false;
int count = 0;
System.out.println("Creating file ...");
while (!eof) {
int input = file.read();
if (input == -1)
eof = true;
else {
count++;
if (count % INTERFERENCE == 0) {
int newInput = input + RANDOMBYTE;
if (newInput > 255)
newInput = newInput - 255;
trash.write(newInput);
} else
trash.write(input);
}
}
file.close();
trash.close();
System.out.println("Done");
} catch (Exception e) {
System.out.println("Error — " + e.toString());
}
}
}
编译该文件后,运行应用程序时需要指定一个 MP3 文件作为命令行参数,例如:
java TrashMP3 MuskratLove.mp3
该程序会读取 MP3 文件中的所有数据,并将其中一小部分字节替换为修改后的数据,最终生成一个新的 MP3 文件。
通过以上内容,我们详细介绍了 Java 中文件读写的相关操作,包括流的概念、文件的表示、数据的读写以及缓冲流的使用等。希望这些内容能帮助你更好地处理 Java 中的文件操作。
Java 文件读写操作全解析
7. 操作流程总结
为了更清晰地理解 Java 文件读写操作,我们可以将整个过程总结为以下流程图:
graph TD;
A[开始] --> B[创建流对象];
B --> C{流类型};
C -->|输入流| D[读取数据];
C -->|输出流| E[写入数据];
D --> F[处理数据];
E --> G[完成写入];
F --> H[关闭流];
G --> H;
H --> I[结束];
这个流程图展示了 Java 文件读写的基本操作流程。首先创建流对象,根据流的类型进行读取或写入操作,处理读取的数据或完成写入后,关闭流以释放资源。
8. 不同场景下的应用
在实际开发中,Java 文件读写操作有多种应用场景,以下是一些常见的场景及对应的操作示例。
8.1 配置文件读取
在很多 Java 应用程序中,我们需要读取配置文件来获取应用程序的配置信息。例如,我们可以创建一个配置文件
config.properties
,内容如下:
database.url=jdbc:mysql://localhost:3306/mydb
database.username=root
database.password=123456
以下是读取该配置文件的 Java 代码:
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class ConfigReader {
public static void main(String[] args) {
Properties properties = new Properties();
try (FileInputStream fis = new FileInputStream("config.properties")) {
properties.load(fis);
String url = properties.getProperty("database.url");
String username = properties.getProperty("database.username");
String password = properties.getProperty("database.password");
System.out.println("Database URL: " + url);
System.out.println("Username: " + username);
System.out.println("Password: " + password);
} catch (IOException e) {
System.out.println("Error reading config file: " + e.getMessage());
}
}
}
在这个示例中,我们使用
Properties
类来读取配置文件,通过
FileInputStream
打开文件并加载到
Properties
对象中,然后可以根据键获取相应的值。
8.2 日志文件写入
在 Java 应用程序中,记录日志是非常重要的。我们可以将日志信息写入日志文件中。以下是一个简单的日志写入示例:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.time.LocalDateTime;
public class Logger {
public static void log(String message) {
try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream("app.log", true)))) {
String timestamp = LocalDateTime.now().toString();
writer.println(timestamp + " - " + message);
} catch (IOException e) {
System.out.println("Error writing log: " + e.getMessage());
}
}
public static void main(String[] args) {
log("Application started");
log("Performing some operation");
log("Application ended");
}
}
在这个示例中,我们使用
PrintWriter
将日志信息追加到
app.log
文件中,同时记录了日志的时间戳。
9. 异常处理与资源管理
在进行 Java 文件读写操作时,异常处理和资源管理是非常重要的。如前面提到的,很多文件操作方法会抛出
IOException
,我们需要捕获并处理这些异常。同时,为了避免资源泄漏,我们应该确保在使用完流后关闭它们。
在 Java 7 及以后的版本中,我们可以使用
try-with-resources
语句来自动关闭实现了
AutoCloseable
接口的资源。例如,前面的
ConfigReader
示例中就使用了
try-with-resources
语句:
try (FileInputStream fis = new FileInputStream("config.properties")) {
properties.load(fis);
// 处理数据
} catch (IOException e) {
System.out.println("Error reading config file: " + e.getMessage());
}
在这个语句中,
FileInputStream
对象会在
try
块执行完毕后自动关闭,无需手动调用
close()
方法。
10. 性能优化建议
为了提高 Java 文件读写操作的性能,我们可以采取以下一些建议:
-
使用缓冲流
:如前面介绍的缓冲输入流和缓冲输出流,它们可以减少与数据源的交互次数,提高读写效率。
-
批量读写
:尽量一次读取或写入多个字节或字符,而不是一个一个地操作。例如,使用
read(byte[] b)
方法读取多个字节。
-
选择合适的流类型
:根据数据的类型选择合适的流,如处理文本数据时使用字符流,处理二进制数据时使用字节流。
11. 总结与展望
通过本文的介绍,我们全面了解了 Java 文件读写操作的相关知识,包括流的概念、文件的表示、不同类型流的使用、异常处理和性能优化等方面。这些知识对于开发 Java 应用程序时处理文件操作非常重要。
在未来的 Java 开发中,随着技术的不断发展,文件读写操作可能会有更多的优化和改进。例如,可能会出现更高效的流处理方式,或者对不同文件格式的支持更加完善。我们需要不断学习和关注这些变化,以提高我们的开发能力和应用程序的性能。
希望本文能帮助你更好地掌握 Java 文件读写操作,在实际开发中能够灵活运用这些知识解决各种文件处理问题。
超级会员免费看
2391

被折叠的 条评论
为什么被折叠?



