如何在Java中从BufferedReader对象中提取全部内容的最佳方法?

6

我正在尝试通过URLConnection获取整个网页。

最高效的方法是什么?

我已经在这样做了:

URL url = new URL("http://www.google.com/");
URLConnection connection;
connection = url.openConnection();
InputStream in = connection.getInputStream();        
BufferedReader bf = new BufferedReader(new InputStreamReader(in));
StringBuffer html = new StringBuffer();
String line = bf.readLine();
while(line!=null){
    html.append(line);
    line = bf.readLine();
}
bf.close();

HTML包含整个HTML页面。


请定义您所说的“高效”是什么意思。 - Thorbjørn Ravn Andersen
我认为这是最好的方法,使用StringBuffer非常好,因为它创建的对象较少,这有很大帮助,我不会做任何不同的事情。 - mklfarha
2
如果我没记错的话,readLine() 会剥离行分隔符,所以如果你没有明确地将它们放回去,你的输出将会被全部剥离。此外,如果这是 Java 1.5 或更新版本,请考虑使用 StringBuilder 而不是 StringBuffer - Powerlord
是的,@EJP,你说得对。你怎么称呼它? - santiagobasulto
感谢您的回答。我选择了mikera,因为他直接回答了问题。但是BalusC也帮了我很多。Marcus Adams关于HTTPURLConnection的评论也很有帮助。 - santiagobasulto
显示剩余3条评论
5个回答

8

我认为这是最好的方法。页面大小固定("它就是它"),所以你不能在内存上进行优化。也许在获取内容后,可以将其压缩,但这种形式并不太有用。我想最终您会想要将HTML解析为DOM树。

任何使读取操作并行化的操作都会使解决方案过于复杂。

我建议使用StringBuilder,大小默认为2048或4096。

为什么您认为您发布的代码不足?你听起来像是犯了过早优化的错误。

用现有的东西去运行,然后安心睡觉。


哈哈,谢谢 @duffymo。我只是想知道有没有更好的方法。谢谢。 - santiagobasulto

3
你想如何处理获取到的HTML?解析它吗?值得一提的是,一个相当不错的HTML解析器可能已经有一个构造函数或方法参数,可以直接使用URL或InputStream,这样就不需要担心流的性能问题。
假设你想做的事情都在你之前的问题中描述,例如使用Jsoup,你可以轻松地获取所有这些新闻链接,如下所示:
Document document = Jsoup.connect("http://news.google.com.ar/nwshp?hl=es&tab=wn").get();
Elements newsLinks = document.select("h2.title a:eq(0)");
for (Element newsLink : newsLinks) {
    System.out.println(newsLink.attr("href"));
}

几秒钟后,这将产生以下结果:
http://www.infobae.com/mundo/541259-100970-0-Pinera-confirmo-que-el-rescate-comenzara-las-20-y-durara-24-y-48-horas
http://www.lagaceta.com.ar/nota/403112/Argentina/Boudou-disculpo-con-DAIA-pero-volvio-cuestionar-medios.html
http://www.abc.es/agencias/noticia.asp?noticia=550415
http://www.google.com/hostednews/epa/article/ALeqM5i6x9rhP150KfqGJvwh56O-thi4VA?docId=1383133
http://www.abc.es/agencias/noticia.asp?noticia=550292
http://www.univision.com/contentroot/wirefeeds/noticias/8307387.shtml
http://noticias.terra.com.ar/internacionales/ecuador-apoya-reclamo-argentino-por-ejercicios-en-malvinas,3361af2a712ab210VgnVCM4000009bf154d0RCRD.html
http://www.infocielo.com/IC/Home/index.php?ver_nota=22642
http://www.larazon.com.ar/economia/Cristina-Fernandez-Censo-indispensable-pais_0_176100098.html
http://www.infobae.com/finanzas/541254-101275-0-Energeticas-llevaron-la-Bolsa-portena-ganancias
http://www.telam.com.ar/vernota.php?tipo=N&idPub=200661&id=381154&dis=1&sec=1
http://www.ambito.com/noticia.asp?id=547722
http://www.canal-ar.com.ar/noticias/noticiamuestra.asp?Id=9469
http://www.pagina12.com.ar/diario/cdigital/31-154760-2010-10-12.html
http://www.lanacion.com.ar/nota.asp?nota_id=1314014
http://www.rpp.com.pe/2010-10-12-ganador-del-pulitzer-destaca-nobel-de-mvll-noticia_302221.html
http://www.lanueva.com/hoy/nota/b44a7553a7/1/79481.html
http://www.larazon.com.ar/show/sdf_0_176100096.html
http://www.losandes.com.ar/notas/2010/10/12/batista-siento-comodo-dieron-respaldo-520595.asp
http://deportes.terra.com.ar/futbol/los-rumores-empiezan-a-complicar-la-vida-de-river-y-vuelve-a-sonar-gallego,a24483b8702ab210VgnVCM20000099f154d0RCRD.html
http://www.clarin.com/deportes/futbol/Exigieron-Roman-regreso-Huracan_0_352164993.html
http://www.el-litoral.com.ar/leer_noticia.asp?idnoticia=146622
http://www.nuevodiarioweb.com.ar/nota/181453/Locales/C%C3%A1ncer_mama:_200_casos_a%C3%B1o_Santiago.html
http://www.ultimahora.com/notas/367322-Funcionarios-sanitarios-capacitaran-sobre-cancer-de-mama
http://www.lanueva.com/hoy/nota/65092f2044/1/79477.html
http://www.infobae.com/policiales/541220-101275-0-Se-suspendio-la-declaracion-del-marido-Fernanda-Lemos
http://www.clarin.com/sociedad/educacion/titulo_0_352164863.html

有人已经说过,正则表达式绝对不是解析HTML的正确工具。;)

另请参阅:


@BalusC 非常感谢。你对 API 的理解是正确的。它真的很棒(我也在使用 HTMLParser,但并不简单)。只有一个最后的问题。JSoup.connect() 和 JSoup.parse() 之间是否有任何性能差异?在 parse() 文档中说应该使用 connect()。 - santiagobasulto
啊,没错,那是1.3 API中的新功能。是的,请使用它。我会相应地更新答案。 - BalusC
太好了。我已经读了你的博客。你在委内瑞拉吗?我在阿根廷。问候朋友!非常感谢! - santiagobasulto
不,在库拉索(位于加勒比海,靠近委内瑞拉的一个岛屿)。问候! - BalusC
@BalusC 我会继续使用HTMLEditorKit。它更快(我已经对几个解析器进行了基准测试)。JSoup需要5000毫秒才能获取所有链接,而HTMLEditorKit只需要1000毫秒。感谢您的帮助! - santiagobasulto
显示剩余2条评论

2
您可以尝试使用Apache的commons-io库(http://commons.apache.org/io/api-release/org/apache/commons/io/IOUtils.html)来解决这个问题。
new String(IOUtils.toCharArray(connection.getInputStream()))

使用非常稳定的Apache Commons,您可以节省大量代码和潜在错误。+1 - Dean J
String data = IOUtils.toString(connection.getInputStream()); 看起来很容易。 - Steven

2

你的方法看起来很不错,但是你可以通过避免为每一行创建中间字符串对象使其更加高效。

做法是直接读入一个临时char[]缓冲区。

这里是稍微修改过的代码版本,它使用了这种方法(为了清晰起见,省略了所有错误检查、异常处理等):

        URL url = new URL("http://www.google.com/");
        URLConnection connection;
        connection = url.openConnection();
        InputStream in = connection.getInputStream();        
        BufferedReader bf = new BufferedReader(new InputStreamReader(in));
        StringBuffer html = new StringBuffer();

        char[] charBuffer = new char[4096];
        int count=0;

        do {
            count=bf.read(charBuffer, 0, 4096);
            if (count>=0) html.append(charBuffer,0,count);
        } while (count>0);
        bf.close();

为了获得更高的性能,当然可以做一些额外的小事情,比如在这段代码将被频繁调用时预先分配字符数组和StringBuffer。


你可以直接从InputStreamReader中读取到char[]缓冲区中,这样可能会更好。虽然我没有测试过这种方法是否更好,但是值得考虑,因为你可能会消除一个不必要的缓冲层。 - mikera
谢谢!我会试一下。只有一个问题,为什么是4096? - santiagobasulto
“Hmmm 4096只是一个‘有根据的猜测’。太大会浪费内存,太小则需要为大文件进行太多的单独迭代/读取。如果您愿意,您可以随时进行实验以找到更适合您环境的值。” - mikera
html.append(charBuffer, 0, count); html.append(charBuffer, 0, count); - user207421
@EJP 不错的发现 - 我不知道如何在JavaDocs中忽略了那个重载!已修复答案以反映。 - mikera

1

有一些技术上的考虑。您可能希望使用HTTPURLConnection而不是URLConnection。

HTTPURLConnection支持分块传输编码,这使您可以按块处理数据,而不是在开始工作之前缓冲所有内容。这可以提高用户体验。

此外,HTTPURLConnection支持持久连接。如果您要立即请求另一个资源,为什么要关闭该连接?保持与Web服务器的TCP连接打开,允许您的应用程序快速下载多个资源,而无需花费建立每个资源的新TCP连接的开销(延迟)。

如果响应头指示内容已压缩,请告诉服务器您支持gzip,并在GZIPInputStream周围包装BufferedReader。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接