【问题标题】:Writing a long string to an HTML file, InputStream vs FileWriter vs BufferedReader将长字符串写入 HTML 文件,InputStream vs FileWriter vs BufferedReader
【发布时间】:2015-06-09 01:20:30
【问题描述】:

我对执行此操作的最佳方法有点困惑。我在这里看到了很多关于 SO 的例子,许多答案都有不同的解决方案。所以我想知道将很长的字符串写入新的 html 文件(即从字符串创建 html 文件)的最有效方法。是否真的更喜欢将所有内容包装到缓冲区中?喜欢:

    fileWriter = new FileWriter(new File(dir, appBook.getPath()));
    bufferWritter = new BufferedWriter(fileWriter);
    bufferWritter.append(htmlContent);

或者我可以这样做(不损失性能)

    fileWriter = new FileWriter(new File(dir, appBook.getPath()));
    fileWriter .append(htmlContent);

..

这是我已经使用了一段时间的方法:

//Will run out of memory if i dont split the string in 650000 chunks
    String[] bookPieces = splitString(htmlContent, Math.round(htmlContent.length()/650000));
    OutputStream outputStream = null;
    InputStream inputStream = null;

    try {
        outputStream = new FileOutputStream(new File(dir, appBook.getPath())); //.html path
        for (String text : bookPieces) {
            byte[] theBytes = text.getBytes(Charset.forName("UTF-16"));
            inputStream = new ByteArrayInputStream(theBytes);
            byte[] bufferData = new byte[1024];
            int bytesRead = inputStream.read(bufferData);

            while (bytesRead != -1) {
                outputStream.write(bufferData, 0, bytesRead); //add the bufferData data to the "new file"
                bytesRead = inputStream.read(bufferData); // keep on reading and filling the dynamic byte araay until it returns -1
            }
            //need to GC the inputsteam myself!!!!
            inputStream = null;

        }
        toReturn = true;

    } 

比我读到它更喜欢使用 BufferedReader 来处理长文本字符串。所以我改为:

    String[] bookPieces = splitString(htmlContent, Math.round(htmlContent.length()/650000));
    OutputStream outputStream = null;
    InputStream inputStream = null;

    OutputStreamWriter oo;

    try {
        outputStream = new FileOutputStream(new File(dir, appBook.getPath()));
        for (String text : bookPieces) {

            byte[] theBytes = text.getBytes(Charset.forName("UTF-16"));
            inputStream = new ByteArrayInputStream(theBytes);

            InputStreamReader iReader = new InputStreamReader(inputStream,Charset.forName("UTF-16"));
            BufferedReader bufferedReader = new BufferedReader(iReader);

            oo = new OutputStreamWriter(outputStream);

            String nextLine;

            while ((nextLine = bufferedReader.readLine())!=null) {
                oo.write(nextLine);
            }
            //need to GC the inputsteam myself!!!!
            inputStream = null;

        } 

但我无法使用该方法正确编码,某些字符会有所不同,例如“-”变成“〔。而且我仍然必须将字符串分成块,所以我看不到更改的目的(我是否以错误的方式执行此操作?请告诉我使用 bufferedReader 的正确方法)。

...而且我终于找到了两种更快的方法,甚至不需要我将字符串分成这么多块。

    String[] bookPieces = splitString(htmlContent, Math.round(htmlContent.length()/100));
    FileWriter fileWriter = null;
    BufferedWriter bufferWritter = null;
    try {
        fileWriter = new FileWriter(new File(dir, appBook.getPath()));
        bufferWritter = new BufferedWriter(fileWriter);

        //Has to append, if write than OOM.
        bufferWritter.append(htmlContent);

        toReturn = true;

    }

// 比上面的 Encoding 略慢

    //Need to split large strings in 100 chuncks
    String[] bookPieces = splitString(htmlContent, Math.round(htmlContent.length()/100));
    BufferedWriter bufferWritter = null;
    OutputStreamWriter osw= null;
    try {
        // Create osw and assign it an Encoding
        osw = new OutputStreamWriter(
                new FileOutputStream(new File(dir, appBook.getPath())),
                Charset.forName("UTF-16"));
        bufferWritter = new BufferedWriter(osw);
        for (String text : bookPieces) {
            bufferWritter.write(text); //write faster than append here
        }

        toReturn = true;

    }

【问题讨论】:

  • write() 并不比append(). 快而且您不需要“自己对输入流进行 GC”。它是一个局部变量,在方法退出时会超出范围,并且会自动被 GC 处理。并且清空引用变量并不一定会导致 GC。
  • 只是在类似的步骤上阅读您的答案,帮助我将字符串分块:)。如果我不将 inputStream 设为 null,我将收到 OOM 错误:/,因此它必须在某处的堆中徘徊(如果有任何区别,请在 android 手机上运行它)。
  • 嗯,我的立场是正确的,只是尝试过并将其设置为 null 并没有任何区别。不过,在此之前,由于某种原因,这个小变化阻止了我离开 OOME,肯定是别的原因。但是,这些方法中的哪一个是最好的。我是否应该将其包装在缓冲区中?
  • 你应该总是缓冲。

标签: java inputstream nio bufferedreader filewriter


【解决方案1】:

这是一种简单但更高效的代码编写方式,IMO:

int buffSize = Math.min(65536, htmlContent.length());
try (Writer osw = new OutputStreamWriter(
            new FileOutputStream(new File(dir, appBook.getPath())),
            Charset.forName("UTF-16"));
     BufferedWriter bw = new BufferedWriter(osw, buffSize)) {
    bw.write(htmlContent);
}

代码注释:

  1. 此版本不拆分文本。 BufferedWriter.write(String) 代码根据BufferedWriters 缓冲区大小提取、转换和写出块中的字符串字符。自己做分块是没有意义的。

  2. 此版本根据正在写入的字符串的大小设置BufferedWriter 的缓冲区大小。但超过一定大小(猜测为 65K)后,您不会通过增加缓冲区大小获得任何性能优势。

  3. 此版本使用“try with resources”来防止资源泄漏。


进一步的想法。

使用 NIO 可能会获得更高的性能。

使用 nasty 反射访问String 对象的private 字符数组可能会获得更高的性能。 (不要这样做。这是个坏主意!)

更好的方法可能是不要将 HTML 组装成一个巨大的字符串。相反,将构成 HTML 的字符/字符串直接写入BufferedWriter。这避免了一次将整个 HTML 保存在内存中的需要1.


1 - 假设您使用的 StringBuilder 没有良好的大小提示,您将需要多达 3N 个 char[] 字符来组装大小为 N 的字符串。如果您有良好的大小提示,您只需要 2N 个字符 ...

【讨论】:

  • 我不认为我理解你的观点,尽管它们可能很好。我不明白 buffSize 在您的代码中是如何使用的?我正在从 web 视图中提取所有文本(这是 htmlContent),而不是用该文本创建一个新网页。也尝试使用 req API 19 :(
  • 更正了错误。现在看到了吗?关于不分块的东西,你需要查看BufferedWriter.write 的源代码......看看它调用了什么。
  • “我正在从 web 视图中提取所有文本(这是 htmlContent),而不是使用该文本创建一个新网页。” - 这并不一定意味着你无法流式传输数据...而不将其全部缓冲为字符串。这仅取决于您准备付出多少努力。
猜你喜欢
  • 2016-02-18
  • 2013-07-12
  • 2016-10-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-13
相关资源
最近更新 更多