BufferedReader无法读取长行

时间:2019-11-05 12:26:28

标签: java json parsing reader

我正在从HttpUrlConnection将以下文件https://www.reddit.com/r/tech/top.json?limit=100读入BufferedReader中。我已经读取了一些文件,但只读取了应有文件的1/10。如果我更改输入缓冲区的大小,它什么也不会改变-它只是在较小的块中打印相同的东西:

try{
    URL url = new URL(urlString);

    HttpURLConnection connection = (HttpURLConnection) url.openConnection();

    BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));

    StringBuilder sb = new StringBuilder();

    int charsRead;
    char[] inputBuffer = new char[500];
    while(true) {
        charsRead = reader.read(inputBuffer);
        if(charsRead < 0) {
            break;
        }
        if(charsRead > 0) {
            sb.append(String.copyValueOf(inputBuffer, 0, charsRead));
            Log.d(TAG, "Value read " + String.copyValueOf(inputBuffer, 0, charsRead));
        }
    }

    reader.close();

    return sb.toString();
} catch(Exception e){
   e.printStackTrace();
}

我认为问题在于文本全部在一行上,因为它在json中的格式不正确,而BufferedReader只能花这么长时间。有什么办法解决吗?

4 个答案:

答案 0 :(得分:0)

read()应该继续读到charsRead > 0。每次调用读取时,读取器都会标记上次读取的位置,下一次调用将从该位置开始,并一直持续到没有其他读取对象为止。它可以读取的大小没有限制。唯一的限制是数组的大小,但是文件的整体大小没有限制。

您可以尝试以下操作:

try(InputStream is = connection.getInputStream(); 
   ByteArrayOutputStream baos = new ByteArrayOutputStream()) {

  int read = 0;
  byte[] buffer = new byte[4096];

  while((read = is.read(buffer)) > 0) {
    baos.write(buffer, 0, read);
  }

  return new String(baos.toByteArray(), StandardCharsets.UTF_8);
} catch (Exception ex){}

上述方法仅使用流中的字节并将其读入输出流,然后从中创建字符串。

答案 1 :(得分:0)

  

我认为问题在于文本全部都在一行上,因为它在json中的格式不正确,而BufferedReader只能花这么长时间。

此说明不正确:

  1. 您一次不会读取一行,并且BufferedReader不会将文本视为基于行。

  2. 即使您一次从BufferedReader读取一行(即使用readLine()),对行长的唯一限制也是Java {{ 1}}的长度(2 ^ 31-1个字符),以及堆的大小。


那到底是怎么回事?

不清楚,但是这里有一些可能性:

  1. String 的固有限制为2 ^ 31-1个字符。但是,(至少)在某些实现中,如果尝试将StringBuilder增大到该限制之外,则会抛出StringBuilder。 (这种行为似乎没有记录在案,从阅读Java 8的源代码中可以明显看出。)

  2. 也许您读取数据的速度太慢(例如,由于您的网络连接速度太慢),并且服务器正在超时连接。

  3. 也许服务器对它愿意在响应中发送的数据量有限制。

由于您没有提到任何例外情况,并且您似乎总是得到相同数量的数据,所以我怀疑第三个解释是正确的。

答案 2 :(得分:0)

我的猜测是您的默认平台字符集为UTF-8,并且出现了编码问题。对于远程内容,应指定编码,并且不应假定该编码等于计算机上的默认编码。

响应数据的字符集必须正确。为此,必须检查标题。默认值应为Latin-1,ISO-8859-1,但浏览器会解释为 作为Windows Latin-1,Cp-1252。

        String charset = connection.getContentType().replace("^.*(charset=|$)", "");
        if (charset.isEmpty()) {
            charset = "Windows-1252"; // Windows Latin-1
        }

然后,您可以更好地读取字节,因为与读取的字节数和读取的字符数没有确切的对应关系。如果缓冲区的末尾是代理对的第一个字符,即两个UTF-16字符,它们构成U + FFFF以上的Unicode字形,符号,代码点,我不知道它的效率基本的“修复”。

        BufferedInputStream in = new BufferedInputStream(connection.getInputStream());
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buffer = new byte[512];
        while (true) {
            int bytesRead = in.read(buffer);
            if (bytesRead < 0) {
                break;
            }
            if (bytesRead > 0) {
                out.write(buffer, 0, bytesRead);
            }
        }
        return out.toString(charset);

确实是安全的:

sb.append(inputBuffer, 0, charsRead);

(获取副本可能是修复尝试。)

通过char[500]占用的内存几乎是byte[512]的两倍。


我看到该网站在我的浏览器中使用gzip压缩。这对于诸如json之类的文本来说是有意义的。我通过设置请求标头 Accept-Encoding:gzip 来模仿它。

    URL url = new URL("https://www.reddit.com/r/tech/top.json?limit=100");
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    connection.setRequestProperty("Accept-Encoding", "gzip");
    try (InputStream rawIn = connection.getInputStream()) {
        String charset = connection.getContentType().replaceFirst("^.*?(charset=|$)", "");
        if (charset.isEmpty()) {
            charset = "Windows-1252"; // Windows Latin-1
        }
        boolean gzipped = "gzip".equals(connection.getContentEncoding());
        System.out.println("gzip=" + gzipped);

        try (InputStream in = gzipped ? new GZIPInputStream(rawIn)
                : new BufferedInputStream(rawIn)) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            byte[] buffer = new byte[512];
            while (true) {
                int bytesRead = in.read(buffer);
                if (bytesRead < 0) {
                    break;
                }
                if (bytesRead > 0) {
                    out.write(buffer, 0, bytesRead);
                }
            }
            return out.toString(charset);
        }
    }

可能是因为gzip不符合“浏览器”的要求,因此错误地在响应中设置了压缩内容的内容长度。。这是一个错误。

答案 3 :(得分:0)

我建议使用3d派对Http客户端。它可以从字面上将您的代码减少到只有几行,而您不必担心所有这些小细节。最重要的是-有人已经写了您要编写的代码。而且它可以正常工作并且已经过测试。一些建议:

  1. Apache Http Client-一个众所周知的流行Http客户端,但是对于像您这样的简单案例而言,可能会显得有些笨重和复杂。
  2. Ok Http Client-另一个著名的Http客户端
  3. 最后,我最喜欢的(因为它是我写的)具有Http Client的MgntUtils开源库。可以找到Maven工件here,可以找到包含库本身的jar文件的GitHub,源代码和Javadoc here,而JavaDoc是here

仅是演示使用MgntUtils库编写的代码,以简化操作。 (我测试了代码,它的工作原理很吸引人)

private static void testHttpClient() {
    HttpClient client = new HttpClient();
    client.setContentType("application/json; charset=utf-8");
    client.setConnectionUrl("https://www.reddit.com/r/tech/top.json?limit=100");
    String content = null;
    try {
        content = client.sendHttpRequest(HttpMethod.GET);
    } catch (IOException e) {
        content = client.getLastResponseMessage() + TextUtils.getStacktrace(e, false);
    }
    System.out.println(content);
}