从sun.misc.BASE64迁移到Java 8 java.util.Base64

问题描述 投票:25回答:4

Java 8 java.util.Base64 MIME编码器和解码器是不受支持的内部Java API sun.misc.BASE64Encodersun.misc.BASE64Decoder的直接替代品吗?

到目前为止我的想法和原因

根据我的调查和快速测试(见下面的代码),它应该是替代品,因为

  • 基于JavaDoc的sun.misc.BASE64Encoder是RFC1521中规定的BASE64字符编码器。此RFC是MIME规范的一部分......
  • java.util.Base64基于其JavaDoc使用RFC 2045表1中指定的“Base64 Alphabet”进行编码和解码操作...在MIME下

假设RFC 1521和2045没有重大变化(我找不到任何变化)并且基于我使用Java 8 Base64 MIME编码器/解码器的快速测试应该没问题。

我在找什么

  • 确认或反驳“直接替换”点OR的权威来源
  • 一个反例,显示了java.util.Base64具有与sun.misc.BASE64Encoder OpenJDK Java 8 implementation (8u40-b25)(BASE64Decoder)不同的行为的情况
  • 你认为无论如何回答上述问题肯定

以供参考

我的测试代码

public class Base64EncodingDecodingRoundTripTest {

    public static void main(String[] args) throws IOException {
        String test1 = " ~!@#$%^& *()_+=`| }{[]\\;: \"?><,./ ";
        String test2 = test1 + test1;

        encodeDecode(test1);
        encodeDecode(test2);
    }

    static void encodeDecode(final String testInputString) throws IOException {
        sun.misc.BASE64Encoder unsupportedEncoder = new sun.misc.BASE64Encoder();
        sun.misc.BASE64Decoder unsupportedDecoder = new sun.misc.BASE64Decoder();

        Base64.Encoder mimeEncoder = java.util.Base64.getMimeEncoder();
        Base64.Decoder mimeDecoder = java.util.Base64.getMimeDecoder();

        String sunEncoded = unsupportedEncoder.encode(testInputString.getBytes());
        System.out.println("sun.misc encoded: " + sunEncoded);

        String mimeEncoded = mimeEncoder.encodeToString(testInputString.getBytes());
        System.out.println("Java 8 Base64 MIME encoded: " + mimeEncoded);

        byte[] mimeDecoded = mimeDecoder.decode(sunEncoded);
        String mimeDecodedString = new String(mimeDecoded, Charset.forName("UTF-8"));

        byte[] sunDecoded = unsupportedDecoder.decodeBuffer(mimeEncoded); // throws IOException
        String sunDecodedString = new String(sunDecoded, Charset.forName("UTF-8"));

        System.out.println(String.format("sun.misc decoded: %s | Java 8 Base64 decoded:  %s", sunDecodedString, mimeDecodedString));

        System.out.println("Decoded results are both equal: " + Objects.equals(sunDecodedString, mimeDecodedString));
        System.out.println("Mime decoded result is equal to test input string: " + Objects.equals(testInputString, mimeDecodedString));
        System.out.println("\n");
    }
}
java encoding java-8 base64 mime
4个回答
43
投票

这是一个小型测试程序,用于说明编码字符串的不同之处:

byte[] bytes = new byte[57];
String enc1 = new sun.misc.BASE64Encoder().encode(bytes);
String enc2 = new String(java.util.Base64.getMimeEncoder().encode(bytes),
                         StandardCharsets.UTF_8);

System.out.println("enc1 = <" + enc1 + ">");
System.out.println("enc2 = <" + enc2 + ">");
System.out.println(enc1.equals(enc2));

它的输出是:

enc1 = <AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
>
enc2 = <AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA>
false

请注意,sun.misc.BASE64Encoder的编码输出最后有一个换行符。它并不总是附加换行符,但如果编码的字符串在其最后一行上恰好包含76个字符,则会发生这种情况。 (java.util.Base64的作者认为这是sun.misc.BASE64Encoder实现中的一个小错误 - 请参阅review thread)。

这可能看起来很简单,但是如果你有一个依赖于这种特定行为的程序,切换编码器可能会导致输出格式错误。因此,我得出结论,java.util.Base64不是sun.misc.BASE64Encoder的直接替代品。

当然,java.util.Base64的目的是它是一个功能等同的,符合RFC的,高性能,完全支持和指定的替代品,旨在支持从sun.misc.BASE64Encoder迁移代码。但是,在迁移时,您需要注意这种边缘情况。


3
投票

rfc1521和rfc2045之间的base64规范没有变化。

所有base64实现都可以被认为是彼此的直接替换,base64实现之间的唯一区别是:

  1. 使用的字母表。
  2. 提供的API(例如,某些可能只对完整的输入缓冲区起作用,而其他可能是有限状态机,允许您继续通过它们推送大量的输入,直到完成)。

MIME base64字母表在RFC版本之间保持不变(它必须或旧软件会破坏)并且是:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/

正如Wikipedia指出的那样,只有最后两个字符可能会在base64实现之间发生变化。

作为更改最后2个字符的base64实现的示例,IMAP MUTF-7规范使用以下base64字母:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+,

更改的原因是/字符通常用作路径分隔符,并且由于MUTF-7编码用于将非ASCII目录路径展平为ASCII,因此需要在编码段中避免使用/字符。


2
投票

我有同样的问题,当我从sun移动到java.util.base64,但是org.apache.commons.codec.binary.Base64这解决了问题


1
投票

假设两个编码器都没有错误,那么RFC要求对每0字节,1字节,2字节和3字节序列进行不同的编码。较长的序列根据需要分解为多个3字节序列,然后是最终序列。因此,如果两个实现正确处理所有16,843,009(1 + 256 + 65536 + 16777216)个可能的序列,那么这两个实现也是相同的。

这些测试只需几分钟即可运行。通过稍微更改您的测试代码,我已经完成了,我的Java 8安装通过了所有测试。因此,公共实现可用于安全地替换sun.misc实现。

这是我的测试代码:

import java.util.Base64;
import java.util.Arrays;
import java.io.IOException;

public class Base64EncodingDecodingRoundTripTest {

    public static void main(String[] args) throws IOException {
        System.out.println("Testing zero byte encoding");
        encodeDecode(new byte[0]);

        System.out.println("Testing single byte encodings");
        byte[] test = new byte[1];
        for(int i=0;i<256;i++) {
            test[0] = (byte) i;
            encodeDecode(test);
        }
        System.out.println("Testing double byte encodings");
        test = new byte[2];
        for(int i=0;i<65536;i++) {
            test[0] = (byte) i;
            test[1] = (byte) (i >>> 8);
            encodeDecode(test);
        }
        System.out.println("Testing triple byte encodings");
        test = new byte[3];
        for(int i=0;i<16777216;i++) {
            test[0] = (byte) i;
            test[1] = (byte) (i >>> 8);
            test[2] = (byte) (i >>> 16);
            encodeDecode(test);
        }
        System.out.println("All tests passed");
    }

    static void encodeDecode(final byte[] testInput) throws IOException {
        sun.misc.BASE64Encoder unsupportedEncoder = new sun.misc.BASE64Encoder();
        sun.misc.BASE64Decoder unsupportedDecoder = new sun.misc.BASE64Decoder();

        Base64.Encoder mimeEncoder = java.util.Base64.getMimeEncoder();
        Base64.Decoder mimeDecoder = java.util.Base64.getMimeDecoder();

        String sunEncoded = unsupportedEncoder.encode(testInput);
        String mimeEncoded = mimeEncoder.encodeToString(testInput);

        // check encodings equal
        if( ! sunEncoded.equals(mimeEncoded) ) {
            throw new IOException("Input "+Arrays.toString(testInput)+" produced different encodings (sun=\""+sunEncoded+"\", mime=\""+mimeEncoded+"\")");
        }

        // Check cross decodes are equal. Note encoded forms are identical
        byte[] mimeDecoded = mimeDecoder.decode(sunEncoded);
        byte[] sunDecoded = unsupportedDecoder.decodeBuffer(mimeEncoded); // throws IOException
        if(! Arrays.equals(mimeDecoded,sunDecoded) ) {
            throw new IOException("Input "+Arrays.toString(testInput)+" was encoded as \""+sunEncoded+"\", but decoded as sun="+Arrays.toString(sunDecoded)+" and mime="+Arrays.toString(mimeDecoded));
        }

    }
}
© www.soinside.com 2019 - 2024. All rights reserved.