Почему популярные библиотеки кодирования Java Base64 используют OutputStreams для Encoding и InputStreams для кодирования? - PullRequest
2 голосов
/ 08 февраля 2020

Я пытался решить проблему с памятью в программе Java, где мы загружаем весь файл в память, кодируем его в base64, а затем используем его в качестве параметра формы в пост-запросе. Это является причиной OOME из-за чрезвычайно большого размера файла.

Я работаю над решением, в котором я могу передать файл через кодировщик base64 в тело запроса Http Post. Один из общих шаблонов, которые я заметил во всех популярных библиотеках кодирования (Guava, java .util.Base64, android .util.Base64 и org. apache .batik.util), заключается в том, что if библиотека поддерживает кодирование с помощью потоков, кодирование всегда выполняется через OutputStream, а декодирование всегда выполняется через InputStream.

У меня проблемы с поиском / определением причин этих решений. Учитывая, что многие из этих популярных и хорошо написанных библиотек соответствуют этому дизайну API, я предполагаю, что для этого есть причина. Кажется, не очень сложно адаптировать один из этих декодеров, чтобы он стал InputStream или принимал InputStream, но мне интересно, есть ли действительная архитектурная причина, по которой эти кодировщики разработаны таким образом.

Почему обычные библиотеки выполняют кодирование Base64 через OuputStream и декодирование Base64 через InputStream?

Примеры, подтверждающие мои заявления:

java.util.Base64
 - Base64.Decoder.wrap(InputStream stream)
 - Base64.Encoder.wrap(OutputStream stream)

android.util.Base64
 - Base64InputStream  // An InputStream that does Base64 decoding on the data read through it.
 - Base64OutputStream // An OutputStream that does Base64 encoding

google.common.io.BaseEncoding
 - decodingStream(Reader reader)
 - encodingStream(Writer writer)

org.apache.batik.util
 - Base64DecodeStream implements InputStream
 - Base64EncodeStream implements OutputStream

1 Ответ

3 голосов
/ 08 февраля 2020

Ну, да, вы можете изменить это, но это имеет смысл. Base64 используется для создания двоичных данных - сгенерированных или управляемых приложением - совместимых с текстовой внешней средой. Таким образом, закодированные в base 64 данные всегда требуются снаружи, а декодированные двоичные данные - внутри.

Приложение обычно не выполняет никаких операций над базой 64 * 1007. * закодированные сами данные; просто необходимо передавать двоичные данные с другим приложением , когда требуется или ожидается текстовый интерфейс .


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

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


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

Затем вы получите для ввода (чтение вызовов):

{APPLICATION} <- (binary data decoding) <- (base64 decoding) <- (file input stream) <- [BASE 64 ENCODED FILE]

для этого вы, естественно, используете входные потоки.

Итак, давайте посмотрим на вывод (запись вызовов):

{APPLICATION} -> (binary data encoding) -> (base64 encoding) -> (file output stream) -> [BASE 64 ENCODED FILE]

, для этого вы, естественно, используете выходные потоки.

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


Вот пример в Java. Обратите внимание, что создание двоичного кодера / декодера в самом классе данных немного уродливо; как правило, вы бы использовали для этого другой класс - я надеюсь, что этого достаточно для демонстрационных целей.

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Base64;

public class BinaryHandlingApplication {

    /**
     * A data class that encodes to binary output, e.g. to interact with an application in another language.
     * 
     * Binary format: [32 bit int element string size][UTF-8 element string][32 bit element count]
     * The integers are signed, big endian values.
     * The UTF-8 string should not contain a BOM.
     * Note that this class doesn't know anything about files or base 64 encoding.
     */
    public static class DataClass {
        private String element;
        private int elementCount;

        public DataClass(String element) {
            this.element = element;
            this.elementCount = 1;
        }

        public String getElement() {
            return element;
        }

        public void setElementCount(int count) {
            this.elementCount = count;
        }

        public int getElementCount() {
            return elementCount;
        }

        public String toString() {
            return String.format("%s count is %d", element, elementCount);
        }

        public void save(OutputStream out) throws IOException {

            DataOutputStream dataOutputStream = new DataOutputStream(out);

            // so here we have a chain of:
            // a dataoutputstream on a base 64 encoding stream on a fileoutputstream 


            byte[] utf8EncodedString = element.getBytes(UTF_8);
            dataOutputStream.writeInt(utf8EncodedString.length);
            dataOutputStream.write(utf8EncodedString);

            dataOutputStream.writeInt(elementCount);
        }

        public void load(InputStream in) throws IOException {
            DataInputStream dataInputStream = new DataInputStream(in);

            // so here we have a chain of:
            // a datainputstream on a base 64 decoding stream on a fileinputstream 

            int utf8EncodedStringSize = dataInputStream.readInt();
            byte[] utf8EncodedString = new byte[utf8EncodedStringSize];
            dataInputStream.readFully(utf8EncodedString);
            this.element = new String(utf8EncodedString, UTF_8);

            this.elementCount = dataInputStream.readInt();
        }

    }

    /**
     * Create the a base 64 output stream to a file; the file is the text oriented
     * environment.
     */
    private static OutputStream createBase64OutputStreamToFile(String filename) throws FileNotFoundException {
        FileOutputStream textOutputStream = new FileOutputStream(filename);
        return Base64.getUrlEncoder().wrap(textOutputStream);
    }

    /**
     * Create the a base 64 input stream from a file; the file is the text oriented
     * environment.
     */
    private static InputStream createBase64InputStreamFromFile(String filename) throws FileNotFoundException {
        FileInputStream textInputStream = new FileInputStream(filename);
        return Base64.getUrlDecoder().wrap(textInputStream);
    }

    public static void main(String[] args) throws IOException {
        // this text file acts as the text oriented environment for which we need to encode
        String filename = "apples.txt";

        // create the initial class
        DataClass instance = new DataClass("them apples");
        System.out.println(instance);

        // perform some operation on the data
        int newElementCount = instance.getElementCount() + 2;
        instance.setElementCount(newElementCount);

        // write it away
        try (OutputStream out = createBase64OutputStreamToFile(filename)) {
            instance.save(out);
        }

        // read it into another instance, who cares
        DataClass changedInstance = new DataClass("Uh yeah, forgot no-parameter constructor");
        try (InputStream in = createBase64InputStreamFromFile(filename)) {
            changedInstance.load(in);
        }
        System.out.println(changedInstance);
    }
}

Особо обратите внимание на цепочку потоков и, конечно, отсутствие каких-либо буферов вообще . Я использовал URL-безопасную базу 64 (если вы хотите использовать вместо нее HTTP GET).


В вашем случае, конечно, вы можете сгенерировать запрос HTTP POST, используя URL и напрямую кодируйте в извлеченный поток OutputStream, обернув его. Таким образом, нет необходимости кодировать (широко) буферизованные данные в формате base64. Смотрите примеры того, как добраться до OutputStream здесь .

Помните, что если вам нужно выполнить буферизацию, вы делаете это неправильно.

Как уже упоминалось в комментариях, HTTP POST не требует кодирования base 64, но теперь вы знаете, как можно кодировать base 64 напрямую в соединение HTTP.


java.util.Base64 speci c примечание: хотя base 64 - это текст, поток base64 генерирует / потребляет байты; это просто предполагает кодировку ASCII (это может быть забавно для текста UTF-16). Лично я думаю, что это ужасное дизайнерское решение; вместо этого они должны были обернуть Reader и Writer, даже если это немного замедляет кодирование.

В свою защиту различные стандарты base 64 и RF C также ошибаются.

...