实验报告五 输入输出流
一、实验目的及要求
-
实验目的:掌握File类,熟悉字节流和字符流的常用方法,掌握文件字节流和文件字符流,进行文件读写操作。
-
实验要求:利用文件字节流和文件字符流的构造方法创建对象,通过read和write方法对数据进行读取和写入,对实验中出现的问题进行分析,确定调试步骤和测试方法,直至文件读写操作成功。
-
上机实验内容:编写应用程序,创建文件对象,分别完成2部分内容:通过文件字节输入流和输出流,完成文件内容的读取和写入操作;通过文件字符输入流和输出流,完成文件内容的写入和读取操作,完成实验报告。
二、实验环境
-
硬件要求:计算机一台
-
软件要求:Windows操作系统,使用Java语言,集成开发环境不限,建议使用如Eclipse、MyEclipse或IntelliJ IDEA等。
三、实验内容
- 流的概念
流在Java中是用于表示输入和输出的数据传输过程的抽象概念。流代表了从一个源读取数据或者向一个目标写入数据的序列。流提供了一个统一的方式来处理不同来源和类型的数据,比如文件、网络数据等。
- 输入流与输出流
- 输入流
是用于从外部数据源(如文件、网络等)读取数据到程序内部的流。当程序需要从某个数据源(如文件、内存或网络)读取数据时,就会开启一个输入流。
输入流示意图
- 输出流
是用于从程序内部写入数据到外部数据目的地(如文件、网络等)的流。当程序需要把数据写入到某个数据目标(如文件、内存或网络)时,就会开启一个输出流。
输出流示意图
- 字节流与字符流
- 字节流
表示以字节为单位从stream中读取或往stream中写入信息,即io包中的inputstream类和outputstream类的派生类。通常用来读取二进制数据,如图象和声音。
- 字符流
以Unicode字符为导向的stream,表示以Unicode字符为单位从stream中读取或往stream中写入信息。
- 字节流和字符流的区别:
字节流和字符流都是Java中用于输入/输出操作的两种流,它们的主要区别在于操作的数据单位不同。字节流以字节为单位进行读写操作,能处理所有类型的数据,效率高但需要自行解码字符,适用于处理非文本数据如图片等二进制文件。字符流以字符为单位,只能处理文本数据,每次读写需要将字节转换为字符所以效率相对较低,但可以直接读取字符值,适用于处理文本信息。总之,如果数据是文本,则优先考虑使用字符流;如果数据是二进制文件,则使用字节流会更高效。两者都可以完成IO操作,选择使用哪一种流需要根据实际数据类型和效率要求来决定。
- Java流的分类
Java字节流的分类包括:
-
字节输入流:用于从数据源(如文件、网络)读取字节数据的流,常用的类有InputStream和其子类如FileInputStream、ByteArrayInputStream等。
-
字节输出流:用于向目标(如文件、网络)写入字节数据的流,常用的类有OutputStream和其子类如FileOutputStream、ByteArrayOutputStream等。
-
缓冲流:对字节流进行缓冲处理,提高读写效率,常用的类有BufferedInputStream和BufferedOutputStream。
-
数据流:用于读写基本数据类型和字符串的流,常用的类有DataInputStream和DataOutputStream。
-
对象流:用于读写对象的流,实现了对象的序列化和反序列化,常用的类有ObjectInputStream和ObjectOutputStream。
Java字符流的分类包括:
-
字符输入流:用于从数据源(如文件、网络)读取字符数据的流,常用的类有Reader和其子类如FileReader、InputStreamReader等。
-
字符输出流:用于向目标(如文件、网络)写入字符数据的流,常用的类有Writer和其子类如FileWriter、OutputStreamWriter等。
-
缓冲流:对字符流进行缓冲处理,提高读写效率,常用的类有BufferedReader和BufferedWriter。
-
转换流:用于字节流和字符流之间的转换,常用的类有InputStreamReader和OutputStreamWriter。
-
打印流:用于格式化输出数据的流,常用的类有PrintWriter。
- java常用io类继承关系
输入输出流的继承关系图
- InputStream类
InputStream是输入字节数据用的类,所以InputStream类提供了3种重载的read方法。
6.1 常用方法
-
public abstract int read( ):读取一个byte的数据,返回值是高位补0的int类型值。
-
public int read(byte b[ ]):读取b.length个字节的数据放到b数组中。返回值是读取的字节数。该方法实际上是调用下一个方法实现的
-
public int read(byte b[ ], int off, int len):从输入流中最多读取len个字节的数据,存放到偏移量为off的b数组中。
-
public int available( ):返回输入流中可以读取的字节数。注意:若输入阻塞,当前线程将被挂起,如果InputStream对象调用这个方法的话,它只会返回0,这个方法必须由继承InputStream类的子类对象调用才有用,
-
public long skip(long n):忽略输入流中的n个字节,返回值是实际忽略的字节数, 跳过一些字节来读取
-
public int close( ) :我们在使用完后,必须对我们打开的流进行关闭.
6.2 注意事项:
-
InputStream是一个抽象类,不能直接实例化,需要通过其子类如FileInputStream来实例化。
-
每次读取数据都可能不完整,可能读取一个字节或者几个字节,所以需要在一个循环中多次读取直到没有数据为止。
-
使用完流一定要关闭,关闭后系统资源可以释放。
-
读取字节时需要注意数据的编码格式,如UTF-8编码需要把字节转换为字符。
-
使用InputStream读取大文件时需要适当的缓冲策略避免频繁小块读取影响效率。
- OutputStream类
OutputStream提供了3个write方法来做数据的输出,这个是和InputStream是相对应的。
7.1 常用方法:
-
public void write(byte b[ ]):将参数b中的字节写到输出流。
-
public void write(byte b[ ], int off, int len) :将参数b的从偏移量off开始的len个字节写到输出流。
-
public abstract void write(int b) :先将int转换为byte类型,把低字节写入到输出流中。
-
public void flush( ) : 将数据缓冲区中数据全部输出,并清空缓冲区。
-
public void close( ) : 关闭输出流并释放与流相关的系统资源。
7.2 注意事项:
-
OutputStream是一个抽象类,使用时需要通过其子类如FileOutputStream实例化。
-
每次写入数据不一定能成功写入全部字节,需要在循环中多次写入确保数据完整性。
-
使用完毕后一定要调用close()方法关闭输出流,释放系统资源。
-
写入文件时需要确认文件路径是否正确,是否有写权限。
-
使用缓冲可以提高写入效率,但需要调用flush()强制输出缓冲区内容。
-
OutputStream只负责写入字节,如写入文本需要设置编码后写入字节数组。
- Reader类及其子类FileReader
Java的java.io.Reader类是所有字符输入流的超类,这些流读取字符并将它们解码为char。它定义了一些基本的读取操作,比如read(), read(char[] cbuf), skip(long n), close()等等。
8.1 常用方法
-
public int read(): 读取一个字符并作为整数值返回,如果到达文件末尾返回-1。
-
public int read(char[] cbuf): 读取多个字符放入字符数组中,返回实际读取的字符个数。
-
public long skip(long n): 跳过指定的字符数,返回实际跳过的字符数。
-
public void close(): 关闭流并释放与之相关的系统资源。
8.2 注意事项:
-
Reader是一个抽象类,使用时需要通过其子类FileReader等进行实例化。
-
每次读取返回的字符数不一定是一个完整的字符,需要循环读取直至没有更多数据。
-
读取文件时需要确认文件路径是否正确和文件是否存在。
-
使用完毕后一定要调用close()方法关闭流,释放系统资源。
-
文件编码需要与Reader编码一致,否则会出现乱码问题。
-
读取大文件时不要使用单个大数组,应使用多个较小数组循环读取。
-
Reader只负责读取字符,如需要读取字符串需要另外处理。
- Writer类及其子类FileWriter
与Reader类似,java.io.Writer类是所有字符输出流的超类,这些流将字符编码为字节数并将其写入流。
9.1 常用方法
-
FileWriter(String fileName): 使用给定的文件路径名创建一个FileWriter对象。
-
FileWriter(File file): 使用给定的File对象创建一个FileWriter对象。
-
public void write(int c): 写入一个字符。
-
public void write(char[] cbuf): 写入一个字符数组。
-
public void write(String str): 写入一个字符串。
-
public void write(String str, int off, int len): 写入字符串的一部分,off指定起始索引,len指定长度。
-
public void flush(): 刷新缓冲区,把缓冲区中的数据写入文件,但不关闭文件。
-
public void close(): 关闭此流,并先调用flush()方法写入缓冲区的数据,然后释放与此流相关联的任何系统资源。
9.2 注意事项:
-
Writer是一个抽象类,使用时需要通过子类FileWriter进行实例化。
-
每次写入返回的字符数不一定是一个完整的字符,需要循环写入直至数据全部写出。
-
文件编码需要与Writer编码一致,否则可能导致写入乱码。
-
写入大文件时不要使用单个大数组,应使用多个较小数组循环写入。
-
flush()方法只是刷新缓冲但不关闭文件,close()会先flush再关闭文件。
- RandomAccessFile类
RandomAccessFile类用于随机访问文件。它可以用于读写一个文件,而不必一次读写整个文件。
10.1 常用方法
-
public RandomAccessFile(String name, String mode) 构造一个用于访问指定文件名和模式的RandomAccessFile对象。
-
public void seek(long pos)设置文件指针到文件指定位置的方法。
-
public int read() 读取文件指针位置的一个字节。
-
public int read(byte[] b)将文件指针位置开始的一段字节读取到字节数组中。
-
public int read(byte[] b, int off, int len) 读取部分字节到字节数组指定位置的方法。
-
public void write(int b) 写入一个字节数据到文件指针位置。
-
public void write(byte[] b) 将字节数组写入到文件指针位置。
-
public void write(byte[] b, int off, int len) 写入部分字节数组到文件指针位置。
-
public void close() 关闭RandomAccessFile对象并释放资源的方法。
10.2 注意事项:
-
构造RandomAccessFile对象时需要指定正确的文件名和操作模式(“r”,"rw"等)。
-
每次读写操作的位置需要通过seek()方法设置文件指针位置。
-
每次读写返回的字节数不一定是完整的,需要循环操作确保数据一致。
-
读取数据时需要判断返回值是否到达文件末尾。
-
使用完毕后必须调用close()方法关闭文件释放资源。
-
构造函数和close()方法可能抛出FileNotFoundException,需要捕获处理。
-
读写大文件时不要使用单个大数组,应使用缓冲策略提高效率。
- 实验案例:
示例一:将由键盘中录入的信息保存到文件中
编写saveMessageToFile方法从控制台读取用户输入的字符串,使用PrintWriter写入到指定的文件中。编写readFileMessage方法使用BufferedReader从指定文件中读取每行字符串,并打印输出。主方法中先调用saveMessageToFile方法保存输入,然后调用readFileMessage方法读取刚才保存的文件并输出到控制台。
实验步骤
-
定义需要使用的文件名变量
-
调用saveMessageToFile方法,从控制台读取输入内容并写入文件,具体包括:
-
创建文件输出流对象,提示用户输入,使用输入流从控制台读取内容,循环写入文件直到输入结束,关闭流输出
-
调用readFileMessage方法读取文件内容,具体包括:
-
创建文件输入流对象,循环读取每行内容,输出每行内容,关闭文件输入流
-
在主方法中先后调用保存方法和读取方法实现从控制台输入保存到文件并读取文件的功能
-
输出结果验证是否成功实现了内容保存和读取需求
示例二:追加文件内容
编写appendMethod_one和appendMethod_two两个方法,分别使用RandomAccessFile和FileWriter实现文件内容的追加。showFileContent方法读取文件每行内容并打印,用于显示文件内容。主方法首先调用showFileContent显示原文件内容。然后调用appendMethod_one向文件追加内容,再追加一行,调用showFileContent查看效果。继续调用appendMethod_two向文件追加内容,再追加一行,最后再调用showFileContent查看全部效果。
实验步骤
-
定义需要使用的文件名和要追加的内容字符串变量
-
调用showFileContent方法显示原始文件内容,为后续对比提供基线
-
使用RandomAccessFile实现文件的内容追加操作:创建RandomAccessFile对象,获取文件长度,将写文件指针定位到尾部,按字节写内容,关闭流
-
再次调用showFileContent方法显示追加后文件内容,验证RandomAccessFile方式追加是否成功
-
使用FileWriter实现文件的内容追加操作:创建FileWriter对象,写入内容,关闭流
-
再次调用showFileContent方法显示追加后文件内容,验证FileWriter方式追加是否成功
-
对比RandomAccessFile和FileWriter两种实现文件的内容追加方式在内部原理和优劣点上有何不同
-
总结实验过程和结果,验证了Java中两种常见的文件内容追加实现方法
##四、实验结果与分析
- 实验结果
示例一 实验结果
示例二 实验结果
- 实验结果分析:
-
在示例一实验结果中可以看出,成功实现将用户从控制台输入的内容保存到了指定的文件中,并能够正确地读取并显示出文件中的内容。
-
在示例二结果中可以看出,无论是使用RandomAccessFile还是FileWriter,都能够成功地向文件中追加新的内容,并且都能够成功地显示出文件的新的内容。
-
这两个实验成功地演示了如何在Java中创建文件、写入文件、读取文件以及向文件中追加内容。
- 实验不足
-
文件内容读取后没有进行验证,无法检查读取是否正确。应添加校验读取结果与文件内容是否一致。
-
文件写入和读取操作没有进行异常处理,可能会出现异常崩溃。需要添加更全面异常处理。
-
实验结果打印输出信息简单,没有进行详细分析。应给出更全面结论。
- 实验中遇到的问题与解决方法
-
问题:文件路径字符串错误拼写、文件不存在等
解决方法:检查路径字符串是否正确,是否指向实际文件,可以打印路径进行调试。
-
问题:文件权限不足无法打开,路径错误无法找到文件
解决方法:检查文件权限设置是否正确,打印路径调试是否正确,使用try catch捕获异常。
-
问题:读取位置错误超出文件范围,写入失败如磁盘空间不足等
解决方法:检查读取/写入位置指针是否正确,写入内容是否太大,使用try catch捕获IO异常。
-
问题:文件内容数据类型与程序预期不符,如字符串转int错误
解决方法:检查文件内容格式是否与程序定义一致,添加内容格式校验,使用try catch处理异常。
-
问题:未正确关闭IO流对象导致资源无法释放
解决方法:使用try-with-resources或在finally中关闭所有流对象。
- 心得体会:
-
进一步掌握了Java常用的IO流知识,如File、FileReader、FileWriter等,能够熟练使用这些类完成文件操作。
-
理解了文件读写的基本流程,包括打开流、读写操作、关闭流等步骤。掌握了try-with-resources和finally块正确关闭流的重要性。
-
体会到异常处理的重要性,需要全面考虑可能出现的异常情况并给出合理的处理。
-
认识到测试工作的重要性。后续需要设计更全面细致的测试用例来验证代码正确性。
##五、附源程序
dongbin_shitiyi.java
// 包声明
package donbin_shiyanbaogao;
// 导入IO包
import java.io.*;
// 类声明
public class dongbin_shitiyi {
// 将由键盘中输入的内容保存在指定的文件中
public static void saveMessageToFile(String fileName) {
// 创建文件对象
File file = new File(fileName);
// 创建打印流对象,用于向文件写入数据
PrintWriter pw = null;
// 创建缓冲字符输入流,用于从控制台读取数据
BufferedReader in = null;
try {
// 创建文件写入流
pw = new PrintWriter(new FileWriter(file));
// 提示用户输入并读取输入
System.out.println("请输入文件的内容并输入end结束");
// 创建输入流
in = new BufferedReader(new InputStreamReader(System.in));
String inputLine = null;
// 循环读取输入直到输入end
while (((inputLine = in.readLine()) != null) && (!inputLine.equals("end"))) {
// 将读取结果写入文件
pw.println(inputLine);
}
// 清空缓冲区
pw.flush();
// 关闭流
pw.close();
} catch (IOException e) {
// 捕获异常
System.out.println(e.getMessage());
} finally {
// 关闭流
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 按照文件中格式将文件内容显示出来
public static void readFileMessage(String fileName) {
// 创建文件对象
File file = new File(fileName);
// 创建缓冲字符输入流读取文件
BufferedReader reader = null;
try {
// 提示用户
System.out.println("按顺序读取文件的内容如下:");
// 创建文件读取流
reader = new BufferedReader(new FileReader(file));
String string = null;
int line = 1;
// 循环读取每行
while ((string = reader.readLine()) != null) {
// 输出每行
System.out.println("line " + line + ": " + string);
line++;
}
// 关闭流
reader.close();
} catch (IOException e) {
// 捕获异常
e.printStackTrace();
} finally {
// 关闭流
if (reader != null) {
try {
reader.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
public static void main(String[] args) {
String fileName = "C:/Users/董斌/Desktop/dongbin_shiyanyi_jieguo.txt";
// 调用保存方法
dongbin_shitiyi.saveMessageToFile(fileName);
System.out.println();
// 输出文件内容
System.out.println("输出文件的内容:");
dongbin_shitiyi.readFileMessage(fileName);
}
}
dongbin_shiyaner.java
// 包声明
package shiyanbaogao;
// 导入IO包
import java.io.*;
// 类声明
public class dongbin_shiyaner {
// 使用RandomAccessFile实现文件的追加,其中:fileName表示文件名;content表示要追加的内容
public static void appendMethod_one(String fileName, String content) {
// try-catch块
try {
// 创建一个随机访问文件流
RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
// 获取文件的长度即字节数
long fileLength = raf.length();
// 将写文件指针移到文件尾
raf.seek(fileLength);
// 按字节的形式将内容写到随机访问文件流中
raf.write(content.getBytes("utf-8"));
// 关闭流
raf.close();
} catch (IOException e) {
// 输出异常信息
e.printStackTrace();
}
}
// 使用FileWriter实现文件的追加,其中:fileName表示文件名;content表示要追加的内容
public static void appendMethod_two(String fileName, String content) {
// try-catch块
try {
// 创建一个FileWriter对象,其中boolean型参数则表示是否以追加形式写文件
FileWriter fw = new FileWriter(fileName, true);
// 追加内容
fw.write(content, 0, content.length());
// 关闭文件输出流
fw.close();
} catch (IOException e) {
// 输出异常信息
e.printStackTrace();
}
}
// 显示文件内容
public static void showFileContent(String fileName) {
// 创建File对象
File file = new File(fileName);
// 创建BufferedReader对象
BufferedReader reader = null;
try {
// 输出提示信息
System.out.println("以行为单位读取文件内容,一次读一整行:");
// 创建文件读取流
reader = new BufferedReader(new FileReader(file));
// 临时字符串变量
String tempString = null;
// 行号
int line = 1;
// 循环读取文件
while ((tempString = reader.readLine()) != null) {
// 打印行号和内容
System.out.println(line + ": " + tempString);
// 行号加1
line++;
}
// 关闭流
reader.close();
} catch (IOException e) {
// 输出异常信息
e.printStackTrace();
} finally {
// 关闭流
if (reader != null) {
try {
reader.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
// 主方法
public static void main(String[] args) {
// 文件名
String fileName = "C:/Users/董斌/Desktop/dongbin_shiyanyi_jieguo.txt/";
// 追加内容1
String content1 = "最终迎来的是";
// 输出文件名和内容
System.out.println(fileName + "文件的内容如下:");
// 调用显示文件内容方法
dongbin_shiyaner.showFileContent(fileName);
// 输出提示信息
System.out.println("\n按RandomAccessFile的形式追加文件后的内容如下:");
// 调用RandomAccessFile追加方法
dongbin_shiyaner.appendMethod_one(fileName, content1);
// 再追加一行
dongbin_shiyaner.appendMethod_one(fileName, "\n我所期望的世界!\n");
// 再次调用显示文件内容方法
dongbin_shiyaner.showFileContent(fileName);
// 输出提示信息
System.out.println("\n按FileWriter的形式追加文件后的内容如下:");
// 追加内容2
String content2 = "一切都是命运石之门的选择!";
// 调用FileWriter追加方法
dongbin_shiyaner.appendMethod_two(fileName, content2);
// 再追加一行
dongbin_shiyaner.appendMethod_two(fileName, "\n世界将被,重新构建!\n");
// 再次调用显示文件内容方法
dongbin_shiyaner.showFileContent(fileName);
}
}