Java读取长文本文件很慢

Posted

技术标签:

【中文标题】Java读取长文本文件很慢【英文标题】:Java reading long text file is very slow 【发布时间】:2013-03-28 19:11:48 【问题描述】:

我有一个 63000 行 (3.5 MB) 长的文本文件(使用 XStream 创建的 XML)。我正在尝试使用缓冲阅读器阅读它:

                BufferedReader br = new BufferedReader(new FileReader(file));
                try 
                    String s = "";
                    String tempString;
                    int i = 0;
                    while ((tempString = br.readLine()) != null) 
                        s = s.concat(tempString);
//                        s=s+tempString;
                        i = i + 1;
                        if (i % 1000 == 0) 
                            System.out.println(Integer.toString(i));
                        
                    
                    br.close();

在这里你可以看到我测量阅读速度的尝试。而且非常低。在 10000 行之后读取 1000 行需要几秒钟。我显然做错了什么,但不明白是什么。提前感谢您的帮助。

【问题讨论】:

你打算解析这个文件吗?为什么不直接用 Xerces/SAX/其他解析工具来加载呢? String +concat 如果字符串很大,则效率非常低。使用StringBuilder 或将InputStream/Reader 直接传递给xml 解析器。 或者如果你真的需要线条,使用类似这样的东西 - commons.apache.org/proper/commons-io/javadocs/api-2.4/org/…。 是的,我正在尝试解析这个文件并再次将其输入 Xstream 以读取保存的类。行并不重要。 如果在XStream中需要的话,不如直接把reader传给XStream,而不是自己读完再传字符串。 【参考方案1】:

@PaulGrime 是对的。每次循环读取一行时,您都在复制字符串。一旦字符串变大(比如 10,000 行大),它就会做很多工作来进行复制。

试试这个:

StringBuilder sb = new StringBuilder();
while (...reading lines..) 
   ....
   sb.append(tempString);  //should add newline
   ...


s = sb.toString();

注意:请阅读下面 Paul 的回答,了解为什么剥离换行符会使这是一种读取文件的不好方法。此外,正如问题 cmets 中所述,XStream 提供了一种读取文件的方法,即使它没有,IOUtils.toString(reader) 也是一种更安全的读取文件的方法。

【讨论】:

谢谢!确实加快了加载速度。 -1 性能损失只是不复制,Stringbuilder 是文档中建议的那个,PaulGrime is right 并不是真正值得接受的答案......和10000?为什么? 我说,“say 10,000”的意思是,“例如,当 10,000 行大时”。我还解释了为什么 Paul 是对的,并给出了一个代码示例。另外,请澄清“不仅仅是复制”的意思。 我也从 StringBuffer 更改为 StringBuilder - 它们是等效的类,但 StringBuilder 不是线程安全的(在这种情况下很好)。【参考方案2】:

您可以立即进行一些改进:

使用StringBuilder 代替concat+。使用+concat 确实会影响性能,尤其是在循环中使用时。

减少对磁盘的访问。您可以使用large buffer:

BufferedReader br = new BufferedReader(new FileReader("someFile.txt"), SIZE);

【讨论】:

【参考方案3】:

您应该使用StringBuilder,因为String 连接对于即使是小字符串也非常很慢。

此外,尝试使用 NIO 而不是 BufferedReader

public static void main(String[] args) throws IOException 
    final File file = //some file
    try (final FileChannel fileChannel = new RandomAccessFile(file, "r").getChannel()) 
        final StringBuilder stringBuilder = new StringBuilder();
        final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        final CharsetDecoder charsetDecoder = Charset.forName("UTF-8").newDecoder();
        while (fileChannel.read(byteBuffer) > 0) 
            byteBuffer.flip();
            stringBuilder.append(charsetDecoder.decode(byteBuffer));
            byteBuffer.clear();
        
    

如果缓冲区仍然太慢,您可以调整缓冲区大小 - 这在很大程度上取决于系统,哪种缓冲区大小效果更好。对我来说,缓冲区是 1K 还是 4K 几乎没有区别,但在其他系统上,我知道这种变化可以将速度提高一个数量级。

【讨论】:

【参考方案4】:

除了已经说过的之外,根据您对 XML 的使用,您的代码可能不正确,因为它丢弃了行尾。例如这段代码:

package temp.***.q15849706;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;

import com.thoughtworks.xstream.XStream;

public class ReadXmlLines 
    public String read1(BufferedReader br) throws IOException 
        try 
            String s = "";
            String tempString;
            int i = 0;
            while ((tempString = br.readLine()) != null) 
                s = s.concat(tempString);
                // s=s+tempString;
                i = i + 1;
                if (i % 1000 == 0) 
                    System.out.println(Integer.toString(i));
                
            
            return s;
         finally 
            br.close();
        
    

    public static void main(String[] args) throws IOException 
        ReadXmlLines r = new ReadXmlLines();

        URL url = ReadXmlLines.class.getResource("xml.xml");
        String xmlStr = r.read1(new BufferedReader(new InputStreamReader(url
                .openStream())));

        Object ob = null;

        XStream xs = new XStream();
        xs.alias("root", Root.class);

        // This is incorrectly read/parsed, as the line endings are not
        // preserved.
        System.out.println("----------1");
        System.out.println(xmlStr);
        ob = xs.fromXML(xmlStr);
        System.out.println(ob);

        // This is correctly read/parsed, when passing in the URL directly
        ob = xs.fromXML(url);
        System.out.println("----------2");
        System.out.println(ob);

        // This is correctly read/parsed, when passing in the InputStream
        // directly
        ob = xs.fromXML(url.openStream());
        System.out.println("----------3");
        System.out.println(ob);
    

    public static class Root 
        public String script;

        public String toString() 
            return script;
        
    

类路径上的这个 xml.xml 文件(与类在同一个包中):

<root>
    <script>
<![CDATA[
// taken from http://www.w3schools.com/xml/xml_cdata.asp
function matchwo(a,b)

if (a < b && a < 0) then
  
  return 1;
  
else
  
  return 0;
  

]]>
    </script>
</root>

产生以下输出。前两行显示行尾已被删除,因此使 CDATA 部分中的 javascript 无效(因为第一个 JS 注释现在删除了整个 JS,因为 JS 行已被合并)。

----------1
<root>    <script><![CDATA[// taken from http://www.w3schools.com/xml/xml_cdata.aspfunction matchwo(a,b)if (a < b && a < 0) then    return 1;  else    return 0;  ]]>    </script></root>
// taken from http://www.w3schools.com/xml/xml_cdata.aspfunction matchwo(a,b)if (a < b && a < 0) then    return 1;  else    return 0;      
----------2


// taken from http://www.w3schools.com/xml/xml_cdata.asp
function matchwo(a,b)

if (a < b && a < 0) then
  
  return 1;
  
else
  
  return 0;
  

...

【讨论】:

以上是关于Java读取长文本文件很慢的主要内容,如果未能解决你的问题,请参考以下文章

是否有可能知道从文件中读取的长文本将在 C 中使用多少个字符?

java读取一个10G大小的文本文件,怎么才能快速的得到该文本文件里面长度最长的单词?

如何将长文本文件字段放入列表框?

java如何读取压缩包中的文本文件

如何提高java读取大文本文件的效率

怎样使用ReadFile读取文本文件?