为什么流行的Java Base64编码库使用OutputStreams进行编码,而InputStreams进行编码?

时间:2020-02-07 21:29:01

标签: java encoding base64 inputstream

我一直在尝试解决Java程序中的内存问题,在Java程序中,我们将整个文件加载到内存中,对它进行base64编码,然后将其用作发布请求中的表单参数。这是由于文件非常大而导致的OOME。

我正在研究一种解决方案,该解决方案使我能够通过base64编码器将文件流式传输到Http Post请求的请求主体中。我在所有流行的编码库(Guava,java.util.Base64,android.util.Base64和org.apache.batik.util)中注意到的常见模式之一是该库 if 支持使用流进行编码,编码始终通过OutputStream进行,而解码始终通过InputStream进行。

我在寻找/确定这些决定背后的原因时遇到了麻烦。鉴于这么多流行且编写良好的库都符合此api设计,因此我认为这是有原因的。 使这些解码器之一成为InputStream或接受InputStream似乎并不困难,但我想知道这些编码器是否采用这种方式设计是否存在有效的架构原因。

为什么常见的库为什么通过OuputStream进行Base64编码而通过InputStream进行Base64解码?

支持我的主张的示例:

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 个答案:

答案 0 :(得分:4)

是的,您可以将其反转,但这是最有意义的。 Base64用于使二进制数据(由应用程序生成或操作)与基于文本的外部环境兼容。 因此,外部始终需要基数为64的编码数据,而内部始终需要已解码的二进制数据。

应用程序通常不会对基于64位编码的数据本身执行任何操作;在需要或期望使用文本界面时,只需与其他应用程序通信二进制数据即可。


如果要将二进制数据导出到外部,自然会使用输出流。如果该数据需要使用base 64进行编码,请确保将数据发送到编码为base 64的输出流。

如果要从外部导入二进制数据,则可以使用输入流。如果该数据是使用base 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安全的base 64(以防您想改用HTTP GET)。


当然,对于您而言,您可以使用URL生成HTTP POST请求,并通过包装将直接编码到检索到的OutputStream流中。这样,就不需要(广泛地)缓存基本64编码数据。请参阅有关如何到达OutputStream here的示例。

请记住,如果需要缓冲,那就错了。

如评论中所述,HTTP POST不需要base 64编码,但是无论如何,现在您知道了如何直接将base 64编码到HTTP连接。


java.util.Base64具体说明: 尽管base 64是文本,但base64流会生成/使用字节。 它只是假设采用ASCII编码(对于UTF-16文本可能很有趣)。 我个人认为这是一个糟糕的设计决策。他们应该改用ReaderWriter包装,即使这样会稍微降低编码速度。

为防御起见,各种基础64标准和RFC也弄错了。