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

标签 java encoding base64 inputstream

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

我正在开发一个解决方案,我可以通过 Base64 编码器将文件流式传输到 Http Post 请求的请求正文中。我在所有流行的编码库(Guava、java.util.Base64、android.util.Base64 和 org.apache.batik.util)中注意到的常见模式之一是如果库支持使用流进行编码,编码始终通过输出流完成,解码始终通过输入流完成。

我无法找到/确定这些决定背后的原因。鉴于许多流行且编写良好的库都与此 api 设计保持一致,我认为这是有原因的。 将这些解码器之一调整为输入流或接受输入流似乎并不困难,但我想知道这些编码器以这种方式设计是否有有效的架构原因。

为什么常见的库通过OutputStream进行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

最佳答案

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

应用程序通常不会对 64 位编码数据本身执行任何操作; 当需要或期望文本接口(interface)时,只需要与另一个应用程序通信二进制数据。

<小时/>

如果你想将二进制数据导出到外部,自然你会使用输出流。如果该数据需要以 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 流。这样,就不需要(大量)缓冲 Base 64 编码数据。请参阅有关如何获取 OutputStream here 的示例.

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

正如评论中提到的,HTTP POST 不需要 Base 64 编码,但无论如何,现在您知道如何将 Base 64 直接编码到 HTTP 连接。

<小时/>

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

为他们辩护,各种 Base 64 标准和 RFC 也犯了这个错误。

关于java - 为什么流行的Java Base64编码库使用OutputStreams进行编码,使用InputStreams进行编码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60121335/

相关文章:

Java 连接到 MicrosoftSQLServer 2005

java - Spring bean 的多个实例

perl - 修复由 UTF-8 和 Windows-1252 组成的文件

encoding - 带问号的钻石

c# - 参数在 Image.Save 上无效

java - android sqlite数据库,空指针

javascript - UTF-8 编码算法如何在 8 位 block 上工作(在 JavaScript 中)?

javascript - Base64 Nodejs 中的读取文件

python - 如何使用python对图像进行base64编码

java - 写入文件代码导致无限循环