BufferedReader не может читать длинную строку - PullRequest
0 голосов
/ 05 ноября 2019

Я читаю этот файл: https://www.reddit.com/r/tech/top.json?limit=100 в BufferedReader из HttpUrlConnection. У меня есть это, чтобы прочитать часть файла, но он читает только около 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 голосов
/ 05 ноября 2019

Я предлагаю использовать сторонний Http-клиент. Это может сократить ваш код буквально до нескольких строк, и вам не нужно беспокоиться обо всех этих мелких деталях. Итог - кто-то уже написал код, который вы пытаетесь написать. И это работает и уже хорошо проверено. Несколько предложений:

  1. Apache Http Client - Хорошо известный и популярный Http-клиент, но может быть немного громоздким и сложным для простого случая, подобного вашему.
  2. Ok Http Client - Другой известный Http-клиент
  3. И, наконец, мой любимый (потому что он написан мной) MgntUtils Open Source библиотека с Http-клиентом. Здесь можно найти артефакты Maven здесь , GitHub, который включает в себя саму библиотеку в виде файла JAR, исходный код и Javadoc, можно найти здесь , а JavaDoc здесь здесь

Чтобы продемонстрировать простоту того, что вы хотите здесь сделать, приведите код с использованием библиотеки 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);
}
0 голосов
/ 05 ноября 2019

Я считаю, что проблема в том, что текст все в одной строке, поскольку он неправильно отформатирован в json, и BufferedReader может занимать только одну строку.

Это объяснение неверно:

  1. Вы не читаете строку за раз, а BufferedReader не обрабатывает текст как строку.

  2. Даже когда вы читаете из BufferedReader строки за раз (то есть, используя readLine()), единственными ограничениями на длину строки являются внутренние ограничения Java* Длина 1017 * (2 ^ 31 - 1 символов) и размер вашей кучи.


Так что же на самом деле происходит?

Неясно,но вот некоторые возможности:

  1. A StringBuilder также имеет ограничение в 2 ^ 31 - 1 символов. Однако, с (по крайней мере) некоторыми реализациями, если вы попытаетесь увеличить StringBuilder за этот предел, он выдаст OutOfMemoryError. (Это поведение, по-видимому, не задокументировано, это ясно из чтения исходного кода в Java 8.)

  2. Возможно, вы читаете данные слишком медленно (например, потому что ваша сетьсоединение слишком медленное) и сервер прерывает соединение.

  3. Возможно, сервер имеет ограничение на количество данных, которые он хочет отправить в ответе.

Поскольку вы не упомянули никаких исключений и, кажется, вы всегда получаете один и тот же объем данных, я подозреваю, что третье объяснение является правильным.

0 голосов
/ 05 ноября 2019

Мое предположение состоит в том, что вашей кодовой платформой по умолчанию была 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, которые образуют глиф Unicode, символ, кодовую точку выше U + FFFF, я не знаю эффективностилежащий в основе «ремонт».

        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, длиной содержимого сжатый контент был ошибочно установлен в ответе. Что является ошибкой.

0 голосов
/ 05 ноября 2019

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){}

Приведенный выше метод использует только байты изпоток и чтение его в выходной поток, а затем создание строки из этого.

...