【问题标题】:StringBuilders ending with mass nul characters以大量 nul 字符结尾的 StringBuilders
【发布时间】:2011-07-20 15:26:48
【问题描述】:

我很难调试我正在构建的应用程序的问题。我似乎无法用具有相同问题的代表性测试程序来重现问题本身,这使得难以证明。不幸的是,由于安全原因,我无法分享我的实际来源,但是,以下测试很好地代表了我正在做的事情,文件和数据是 unix 样式 EOL,使用 PrintWriter 写入 zip 文件以及使用 StringBuilders 的事实:

public class Tester {

    public static void main(String[] args) {
        // variables
        File target = new File("TESTSAVE.zip");
        PrintWriter printout1;
        ZipOutputStream zipStream;
        ZipEntry ent1;
        StringBuilder testtext1 = new StringBuilder();
        StringBuilder replacetext = new StringBuilder();
        // ensure file replace
        if (target.exists()) {
            target.delete();
        }
        try {
            // open the streams
            zipStream = new ZipOutputStream(new FileOutputStream(target, true));
            printout1 = new PrintWriter(zipStream);
            ent1 = new ZipEntry("testfile.txt");
            zipStream.putNextEntry(ent1);

            // construct the data
            for (int i = 0; i < 30; i++) {
            testtext1.append("Testing 1 2 3 Many! \n");
            }
            replacetext.append("Testing 4 5 6 LOTS! \n");
            replacetext.append("Testing 4 5 6 LOTS! \n");

            // the replace operation
            testtext1.replace(21, 42, replacetext.toString());

            // write it
            printout1 = new PrintWriter(zipStream);
            printout1.println(testtext1);
            // save it
            printout1.flush();
            zipStream.closeEntry();
            printout1.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

问题的核心是我在我身边看到的文件正在生成一个 16.3k 字符的文件。我的朋友,无论他是在他的电脑上使用该应用程序,还是他查看与我完全相同的文件,都会看到一个包含 19.999k 个字符的文件,额外的字符是 CRLF,后跟大量的空字符。无论我使用什么应用程序、编码或视图,我都看不到这些 nul 字符,我只在最后一行看到一个 LF,但我确实看到了一个 20k 的文件。在所有情况下,在两台机器上使用完全相同的文件看到的内容之间存在差异,即使它们都是 Windows 机器并且都使用相同的编辑软件进行查看。

我还不能用任何数量的虚拟程序重现这种行为。但是,我已经能够将最后一行的杂散 CRLF 追溯到我在 PrintWriter 上使用 println。当我用print(s + '\n') 替换println(s) 时,问题似乎消失了(文件大小为16.3k)。但是,当我将程序返回到 println(s) 时,问题似乎没有返回。我目前正在法国的一位朋友验证文件,看看问题是否真的消失了(因为我看不到 nuls 但他可以),但这种行为已经完全混淆了。

我还注意到 StringBuilder 的替换函数声明“如果需要,此序列将被延长以适应指定的字符串”。鉴于 stringbuilders setLength 函数使用 nul 字符填充,并且 ensureCapacity 函数将容量设置为输入或(currentCapacity*2)+2 中的较大者,我怀疑某处存在关系。但是,在用这个想法进行测试时,我只有一次能够得到一个代表我所看到的结果,并且从那以后就无法重现它。

有没有人知道是什么原因导致了这个错误,或者至少对测试的方向有什么建议?

编辑因为 cmets 部分对我来说已损坏: 只是为了澄清,无论操作系统如何,输出都必须是 unix 格式,因此直接使用 '\n' 而不是通过格式化程序。插入的原始 StringBuilder 实际上并不是向我生成的,而是程序读取的文件的内容。我很高兴阅读过程有效,因为其中的信息在整个应用程序中被大量使用。我也做了一些探索,发现在保存之前,缓冲区的容量是正确的,并且调用 toString() 时的输出是正确的长度(即它不包含空字符并且长度为 16,363,而不是 19,999 )。这会将错误的原因放在生成字符串和保存 zip 文件之间。

【问题讨论】:

  • 你为什么用zipStream.closeEntry();而不是zipStream.close();
  • @MByD : closeEntry() 关闭 ZipEntry 并将其索引写入 zip 分配表。 close() 关闭流,并且在不定义 zip 分配表的情况下这样做只会导致 zip 文件损坏。我很难学到这一点;)。当 PrintWriter 的 close() 方法被调用时,流被 PrintWriter 关闭
  • @K.巴拉德,好的,但是在关闭条目之后,您不应该关闭流吗?似乎它不会发生在您的代码中......
  • 而不是 x.append("Testing 1 2 3 Many!\n");您应该使用 x.append(String.format("%s\n", "Testing 1 2 3 Many!")) 作为 String.format 处理定义的特定操作系统的结束行。
  • @MByD: printout1.close() 应该在内部关闭底层流,因此调用zipStream.close()

标签: java text file null printwriter


【解决方案1】:

终于找到原因了。设法重现了几次问题,并将原因追溯到代码的输出端而不是输入端。我的文件读取功能基本上是这样的:

char[] buf;
int charcount = 0;
StringBuilder line = new StringBuilder(2048);
InputStreamReader reader = new InputStreamReader(stream);// provides a line-wise read
BufferedReader file = new BufferedReader(reader);
do { // capture loop
    try {
    buf = new char[2048];
    charcount = file.read(buf, 0, 2048);
    } catch (IOException e) {
    return null; // unknown IO error
    }
    line.append(buf);
} while (charcount != -1);
// close and output

问题是附加了一个未满的缓冲区,所以后面的值仍然是它们的初始值 null。我无法重现它的原因是因为有些数据很好地填充了缓冲区,有些则没有。

为什么我似乎无法在我的文本编辑器上查看我仍然不知道的问题,但我现在应该能够解决这个问题。欢迎就最佳方式提出任何建议,因为这是我的长期实用程序库之一,我希望使其尽可能通用和优化。

【讨论】:

  • 简单修复:用line.append(buf, 0, charcount)替换line.append(buf)(如果charcount==-1,你也需要跳过它,'虽然)。
  • @Joachim :阅读 5 分钟后,我想出了相同的方法。很高兴有其他人确认。实际上我正在考虑 charcount > 0 的情况,好像没有读取任何内容我不应该尝试追加。当然,我不明白为什么实际上应该返回 0,但它更安全。
  • 根据the documentation,返回0的唯一情况是buf的大小为0。在所有其他情况下,至少读取一个字节抛出异常。此外,将 0 个字符附加到 StringBuilder 无论如何都是无操作的。
猜你喜欢
  • 2020-12-14
  • 1970-01-01
  • 2015-04-11
  • 2019-11-26
  • 1970-01-01
  • 2018-11-12
  • 1970-01-01
  • 2010-10-03
  • 1970-01-01
相关资源
最近更新 更多