Warum beliebte Java Base64 codiert, Bibliotheken verwenden OutputStreams für Encoding und Inputstreams für die Codierung?

M. Wallace:

Ich habe versucht, ein Speicherproblem in einem Java-Programm zu lösen, wo wir eine ganze Datei in den Speicher geladen werden, base64 es kodiert und dann als Formparameter in einer Post-Anforderung verwendet wird. Dies ist Ursache OOME aufgrund der extrem großen Dateigröße.

Ich arbeite an einer Lösung , wo ich bin in der Lage , die Datei durch einen Base64 Encoder zu streamen, in die Anforderungs Körper eines Http Gesuch. Eines der gemeinsamen Muster Ich habe in allen gängigen Codierung Bibliotheken (Guava, java.util.Base64, android.util.Base64 und org.apache.batik.util) bemerkt , ist , dass , wenn die Bibliothek unterstützt mit Streams codiert, das Encoding wird immer durch einen Output getan und die Decodierung wird immer durch einen Input getan.

Ich habe Probleme finden / die Gründe für diese Entscheidungen bestimmen. Da so viele dieser beliebten und gut geschriebene Bibliotheken mit diesem api Design ausrichten, nehme ich an, dass es ein Grund dafür ist. Es ist nicht sehr schwierig erscheinen, einer dieses Decoder anzupassen ein Input oder akzeptieren einen Inputstream zu werden, aber ich frage mich, ob es ein gültiger architektonischer Grund ist dieser Encoder auf diese Weise ausgelegt ist.

Warum gemeinsame Bibliotheken tun Base64-Codierung durch eine OuputStream und Base64 Decodierung durch einen Input?

Beispiele meine Ansprüche zu sichern:

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

Martin Bodewes:

Nun, ja, man kann es umgekehrt, aber das macht am meisten Sinn. Base64 wird verwendet, um binäre Daten - erzeugt oder betrieben von der Anwendung - kompatibel mit einer textbasierten äußeren Umgebung. So ist die Basis 64 codierten Daten werden immer an der Außenseite erforderlich , und die decodierten binären Daten ist auf der Innenseite erforderlich.

Eine Anwendung der Regel führt keine Operationen auf der Basis 64 codierten Daten selbst; es wird nur benötigt , um mit einer anderen Anwendung binäre Daten zu kommunizieren , wenn eine Textschnittstelle erforderlich ist oder erwartet wird .


Wenn Sie möchten, dass Ihre binären Daten nach außen exportieren, natürlich würden Sie einen Ausgabestrom verwenden. Wenn das Datenbedarf zu 64 in der Basis codiert werden, stellen Sie sicher, dass Encodierungen auf Basis 64, um die Daten an einen Ausgangsstrom zu senden.

Wenn Sie möchten, dass Ihre binären Daten von außen importieren dann würden Sie einen Eingabestrom verwenden. Wenn diese Daten in der Basis codiert wird 64 dann entschlüsseln, müssen Sie zuerst, um es, so dass Sie sicher, entschlüsseln Sie es, bevor es als binärer Strom zu behandeln.


Lässt ein bisschen ein Bild erstellen. Angenommen, Sie haben eine Anwendung, die in einem Text-orientierten Umgebung arbeitet, sondern arbeitet auf Binärdaten. Der wichtige Teil ist die Richtung der Pfeile aus dem Kontext der Anwendung auf der linken Seite.

Dann erhalten Sie für die Eingabe (lesen Anrufe):

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

für diese natürlich Eingabe-Streams verwenden.

Lassen Sie uns also einen Blick auf den Ausgang (Schreib Anrufe):

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

Dazu verwenden Sie natürlich Ausgabeströme.

Dieser Strom kann miteinander verbunden werden , indem sie miteinander verketten , dh unter Verwendung eines Stroms als Elternteil des anderen Stroms.


Hier ist ein Beispiel in Java. Beachten Sie, dass die binären Codierer / Decodierer in der Datenklasse Schaffung selbst ein bisschen hässlich ist; im Allgemeinen würden Sie eine andere Klasse dafür verwenden - ich hoffe, es zu Demonstrationszwecken genügt.

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

Besonders beachten Sie die Verkettung der Ströme und natürlich das Fehlen von Puffer zu löschen . Ich habe URL-sichere Basis 64 verwendet (falls Sie HTTP GET stattdessen verwenden möchten).


In Ihrem Fall natürlich, könnten Sie einen HTTP - POST - Request mit einer URL und erzeugen direkt kodieren zu dem abgefragten OutputStreamStrom durch Umwickeln. Auf diese Weise keine Basis 64 Bedürfnisse codierte Daten (umfassend) werden gepuffert. Hier finden Sie Beispiele, wie man die bekommen OutputStream hier .

Denken Sie daran, wenn Sie den Puffer benötigen, sind Sie es falsch zu machen.

Wie in den Kommentaren erwähnt, HTTP POST nicht Base-64-Codierung benötigen, aber was auch immer, jetzt wissen Sie, wie Sie 64 Basis direkt an eine HTTP-Verbindung kodieren können.


java.util.Base64spezifische Anmerkung: Obwohl die Basis 64 Text ist, erzeugt der Base64 - stream / verbraucht Bytes; es geht einfach davon aus ASCII - Kodierung (dies kann UTF-16 Text Spaß machen für). Ich persönlich denke , dies ist eine schreckliche Design - Entscheidung; sie sollten eine gewickelt haben Readerund Writerstattdessen, auch wenn das verlangsamt leicht kodieren.

Zu ihrer Verteidigung, die verschiedene Basis 64 Standards und RFC auch diese falsch.

Ich denke du magst

Origin http://10.200.1.11:23101/article/api/json?id=478599&siteId=1
Empfohlen
Rangfolge