目录
一、前言
笔者在大二下初学软件构造课,课程实验要求使用Java语言,由于事先仅仅接触过C/C++语言,且编写代码能力并不高,难免磕磕碰碰,借此机会记录一些错误以及心得体会。Java语言对笔者来说是一片新天地,若理解有失偏颇方面,还请见谅。
二、问题
在课程实验一构造当中,有这样一个要求:
从指定文本文件路径中获取信息
事先笔者并未接触过有关Java语言文件读入的相关信息与方法,只能从Java学习渠道中搜索解决方法。以下将从基础层次逐步解读问题。
- IO简介
输入(Input)和输出(Output)是编程中常见的功能需求,同时IO代表着程序需要与外部进行数据交流。IO只是目的,其具体实现才是我们需要关心的内容。而要达到这一目的,我们需要一种机制来帮助我们完全,这种机制就是"流"、或者叫"数据流"。
- 数据流解读
数据流是一串连续不断的数据的集合,就像水流,无论加入水流中的水是一滴还是一盆,水流只会呈现连续不断的样子。同样,数据写入程序可以是一段一段地向数据流管中写入数据,这些数据段会按先后顺序形成一个长的数据流。对数据读取程序来说,只能先读取前面的数据,再读取后面的数据。不管写入时是将数据分多次写入,还是作为一个整体一次写入,读取时的效果都是完全一样的。
- 字符流与字节流
IO流主要可以分为字符流和字节流。不论是磁盘还是网络传输,最小的存储单元都是字节,而不是字符,所有IO流操作得都是对于字节。数据流机制就是Java为其封装的一个接口。
- 文件读入的方式
Reader是文件读入的主类,而其他都是它的子类。在这里,笔者采用的方法是使用一个文件对象(File()方法)来创建一个输入流对象来读取文件,之后使用FileReader类来对这个流进行操作。为了提高字符流读写的效率,可以引入缓冲机制,我们都知道,缓存读取的速度是快于磁盘读取的,并且大部分的内存操作最终都需要使用缓存。缓存能给进行字符批量的读写,提高了单个字符读写的效率。其中BufferedReader用于加快读取字符的速度。BufferedReader类拥有8KB个字符的缓冲区。当BufferedReader在读取文本文件时,会先尽量从文件中读入字符数据并放满缓冲区,而之后若使用read()之类的方法,会先从缓冲区中进行读取。如果缓冲区数据不足,才会再从文件中读取。
public class FileReader extends InputStreamReader {
/**
* Creates a new {@code FileReader}, given the {@code File} to read,
* using the platform's
* {@linkplain java.nio.charset.Charset#defaultCharset() default charset}.
*
* @param file the {@code File} to read
* @throws FileNotFoundException if the file does not exist,
* is a directory rather than a regular file,
* or for some other reason cannot be opened for
* reading.
*/
public FileReader(File file) throws FileNotFoundException {
super(new FileInputStream(file));
}
}
这部分代码段为所用到的BufferedReader的定义。
public class BufferedReader extends Reader {
private static int defaultCharBufferSize = 8192;
/**
* Creates a buffering character-input stream that uses a default-sized
* input buffer.
*
* @param in A Reader
*/
public BufferedReader(Reader in) {
this(in, defaultCharBufferSize);
}
}
- 笔者的实现
笔者在实现这一功能模块时,逐步定义各类所需的变量,代码中间部分是完成对文本文件的行数统计,之后关闭文件。在对代码调试之后,这一段代码也完成了预期所需的功能,不过在此之后的文件使用出了一点偏颇。
File file = new File(fileName);
FileReader fileReader = new FileReader(file);
BufferedReader bufferedReader = new BufferedReader(fileReader);
int n = 0;
while (bufferedReader.readLine() != null) {
n++;
}
bufferedReader.close();
来看下一段代码。
BufferedReader testfile = new BufferedReader(fileReader);
while ((perline = testfile.readLine()) != null) {
linesplit = perline.split("\t");
count = linesplit.length;
if (count != n) {
System.out.println("error!" + fileName + ":not use tab to cut!");
testfile.close();
return false;
}
}
- 出现的异常
在这段代码的实现中,选择使用上一段代码已经定义并使用的FileReader型继续读取这一段文本文件,功能定义为检查每个字符都用"\t"分隔开。但运行程序,却显示文件读取失败。这让笔者百思不得其解,以上的定义从常量认知并没有错误,由唯一文件对象File建立的FileReader型变量也应该是唯一的,且以上BufferedReader也已经使用close()方法关闭,新建立的变量(BufferedReader)testfile应该完成第一段代码同样的功能即读取文件并执行操作。
三、解释
- 整体结构解释
在此之前,笔者已经介绍了相关IO流的部分定义,FileReader类为文件读取建立了一道数据流,在这个基础上完成对文件的读取操作。所以,FileReader表示的数据流由唯一的文件路径存储的文件唯一定义,文件的不变性是确定的。问题只能出在BufferedReader上,从BufferedReader的定义可知,它只是借助了一个8KB的数据缓冲区来暂时存放文件中的数据。通过它的参数Reader可以确定,它所建立的这8KB缓冲区也由Reader也就是FileReader确定,并且,这个缓冲区大概率是一个immutable(不可变)类型的变量。关于可变与不可变,以下给出部分粗略的解释。
immutable不可变类型
mutable可变类型
- 分析与结论
有不可变类型这样解释的理由是文件读取一般不需要对文件做出修改,所以结构设计是无需多此一举加入这一并不常用的功能设计。不可变类型也就决定了,当BufferedReader由唯一FileReader建立的缓冲区是唯一存在的,即使我们将BufferedReader用close()方法关闭之后,它仅仅是切断了变量与缓冲区之间的指派关系,并没有将缓冲区消除。以至于,当我们再次使用FileReader建立BufferedReader变量时,这个新建立的变量依旧要指向之前建立的缓冲区。这可以做到吗,似乎没有问题,但其实这是不允许的。
可变类型与不可变类型的具体区别
已经被指向过的不可变类型无法再被另一个类型指向,这就是这个问题的关键之所在。继续沿用已经定义过的FileReader,所建立的缓冲区无法在被同类型的其他变量重新指派。正确的做法是再重新定义一个FileReader类型变量来建立缓冲区,这样得到了虽然与之前一样的缓冲区,但其本质上是另一个"影子"。如以下方法:
BufferedReader testfile = new BufferedReader(new FileReader(file));
四、总结
学习编程的道路是永无止境的,Java对于笔者来说是一个新的挑战,只能继续加油。