LineNumberReader 和 FileWriter 同时使用碰到的问题

今天上班有个任务就是将之前产品中的示范代码有一些不规范的地方进行修改,使得能在多平台顺利运行,主要是文件名大小写的问题。由于之前示范代码编写者在跨平台上编码经验相对不足,所以编写者在写代码的时候并没有严格的大小写意识 (主要原因还是因为 Windows 不区分大小写,而代码的测试均是在 Windows 下进行的),造成很多遗留问题。产品中有示范代码 64 个,大小写问题几乎无处不在,还有一个就是跨平台产品支持的数据引擎与 Windows 不一致,之前的 Windows 版本中有不少的示范代码使用了 Windows 平台特定的引擎类型,也需要进行修改。

修改的方法可以有很多,最为简单和直接的方法,应该是一个个程序运行,一个个文件检查,逐个进行修改。我想作为程序员的我们,肯定是不愿意做这么无聊而重复的工作的。那么搞定这个无聊而重复的工作呢?首先分析一下已有的示范代码的特征,虽然之前的代码存在诸多不规范的地方,但是在一些方面还是存在很多共性的,大小写出现错误的肯定是在设置数据路径的时候出现的,设置引擎类型的代码更是固定的,之需要使用几个非常简单的正则表达式就可以将其完全正确替换了。所以我们选择了读写文件的方法来完成这个任务。

在实现这个功能的时候,使用到了 LineNumberReader 和 FileWriter 两个类,每读取一行,使用 replaceAll(String regx,String str) 方法对该行进行替换,之后再使用 FileWriter 将替换后的文本写入文件:

执行的结果是,64 个示范代码中,总会有几个会出现一些很奇怪的问题,那就是修改后的文件丢失了很多文本,导致编译不通过。那么问题在哪儿呢?经过多次分析和调试,最终发现当 LineNumberReader 和 FileWriter 指向同一个文件的时候会出现该问题,如果将源文件处理后的结果写入到一个新的文件中便不会存在这样的问题。那么究竟是哪里出了问题呢?来看一张图

在这张图中我们能看到一些信息,LineNumberReader 对象中有一个 cb 字符数组,大小为 8192=1024*8.

在我整个的调试过程中,我一直关注着最后一个字符,在整个过程中,这个位置的字符一直没有发生改变,而最终写入到文件中的最后一个字符就是该字符,这难道只是一个巧合吗?

现在让我们更换一下代码将原本使用同一个 File 对象的代码,改成使用另一个新文件对象的代码来重新调试一下。

先执行代码,查看结果发现,文本的内容完全被写入到新文件中,并没有出现丢失文本这样的情况,那么我们再来调试一下 ,我们再次来监视 cb[8191] 处的值,这次我们可以发现在 LineNumberReader 对象 lineNumber==319 的时候,该值发生改变。

====我是分割线======

综合上面的两种情况,我们可以得出一个结论,当 LineNumberReader 和 FileWriter 同时指向一个文件的时候,系统在写入文件的时候并不是逐行逐行写入,而是先记录下来逐行的内容,并且是记录到 LineNumberReader 的 cb 字符数组中。该数组的大小固定为内存最小单位 8K,也就是大小为 8192 的数组,该数组将保存初次初始化 FileWriter 对象时读取的文件内容,在之后的每次 FileWriter 写入操作均是改变该数组中存储的字符值。这样导致的结果就是,如果当前的读取的文件较大,就会存在丢失内容的问题 (最多保存 8192 个字符, 含空格和换行)。而使用一个新的文件来进行修改后内容的写入就不存在这样的问题,当 FileWriter 需要使用到 flush() 方法来将当前缓冲区中的所有内容写入文件之后,LineNumberReader 中的 cb 数组内容也将发生改变,指针下移,读取下一个块大小为 8192 的内容,直到文件末尾。

所以在使用 LineNumberReader 和 FileWriter 的时候需要注意这一点,如果实在不愿意多生成一堆新文件的话,可以在写入完成之后,将原始文件删除,而将新文件更名一下就好了。