【问题标题】:Is Java 8 java.util.Base64 a drop-in replacement for sun.misc.BASE64?Java 8 java.util.Base64 是 sun.misc.BASE64 的替代品吗?
【发布时间】:2016-05-19 23:46:34
【问题描述】:

问题

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

编辑(澄清):直接替换 我的意思是我可以将使用 sun.misc.BASE64Encodersun.misc.BASE64Decoder 的遗留代码透明地切换到 Java 8 MIME Base64 编码器/解码器,用于任何现有的其他客户端代码。

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

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

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

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

我在寻找什么

  • 权威来源确认或反驳“直接替换”点或
  • 一个反例,显示 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");
    }
}

【问题讨论】:

  • 直接替换是什么意思?您只是在谈论编码/解码行为吗?
  • @Cubic:我的意思是直接替换,我可以使用 sun.misc.BASE64Encoder 和 sun.misc.BASE64Decoder 将遗留代码切换到 Java 8 MIME Base64 Encoder/Decoder,用于任何现有的其他客户端代码透明地。情况似乎如此,但我希望有一个权威的参考来证实这一点,或者有一个“证明”证明情况并非如此。
  • 是的,您可以将旧代码切换到新的 Java 8 Base64 编码器/解码器。它们将始终产生相同的输出。
  • @Raedwald 我不这么认为。此问答记录了遗留代码使用非官方 Java 内部 API(假定永远不会被任何人使用)sun.misc.BASE64Encodersun.misc.BASE64Decoder 时的问题。这个问题/答案是关于将此类遗留代码迁移到官方 Java 8 Base64 API。 answer to which encoder class to use 已经建议使用 Java 8 Base64 API,并没有将您指向那些旧的 sun.misc API。

标签: java encoding java-8 base64 mime


【解决方案1】:

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

所有 base64 实现都可以被视为相互替代,base64 实现之间的唯一区别是:

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

在 RFC 版本之间,MIME base64 字母保持不变(它已经到或更旧的软件会损坏)并且是:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/

正如Wikipedia 所说,只有最后 2 个字符可能会在 base64 实现之间发生变化。

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

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

【讨论】:

  • 赞成你的解释,因为它完全有道理,而且它也与我从一开始就想出的一致。仍然希望获得一些“官方”参考 - 如果它甚至存在。我希望Java 8 Adaption GuideJEP 135 会明确指出Java 8 Base64 编码器/解码器替换了内部sun.misc.BASE64 实现。但是好吧,也许它太明显了......无论如何,这种 QA 格式成为那个“官方”参考。
  • 似乎不应该使用 sun.* 命名空间:oracle.com/technetwork/java/faq-sun-packages-142232.html 这表明添加了 java.util.* base64 类以安抚需要 base64 支持并且必须实现他们的开发人员自己的课程或使用3rd party solutions
  • 您也可以将rfc2045 明确声明它已废弃 rfc1521 作为官方证明。
【解决方案2】:

假设两个编码器都没有错误,那么 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));
        }

    }
}

【讨论】:

  • 我喜欢你的方法。然而,斯图尔特的回答包括一个反例,它显示了两个结果编码不相同的边缘情况。
【解决方案3】:

这是一个小测试程序,说明了编码字符串的差异:

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、高性能、完全受支持和指定的替代品,旨在支持从 @987654330 迁移代码@。不过,在迁移时您需要注意一些类似的边缘情况。

【讨论】:

  • 完美,你找到了反例!
  • 确实,如果这个和任何其他极端情况(如果存在)被正确记录,那就太好了。你知道任何其他极端情况吗?
  • @IvoMori 我不知道任何其他边缘情况,尽管可能有一些。我怀疑他们会被记录在案。问题是,这通常适用于sun.misc 的东西,它从未被正式指定,并且没有像java.* API 那样的一致性和回归测试套件。 sun.misc.BASE64 的东西只是一堆“做了它做了什么”的代码,因此很可能甚至很可能存在奇怪的边缘情况行为甚至潜伏在那里的错误。
  • 在 android 出现问题...Call requires API level 26 (current min is 21): java.util.Base64#getMimeEncoder more... (Ctrl+F1)
  • 我自己的观察是 sun.misc.BASE64Encoder 在 个 76 个字符之后插入一个换行符,而不仅仅是编码字符串长度正好为 76 个字符的特定情况。我在解决与 OpenSSL 库中 PEM_read_bio_RSAPublicKey() 函数不兼容的问题时发现了这一点,该函数期望包含这些换行符。
【解决方案4】:

当我从 sun 移动到 java.util.base64 时,我遇到了同样的问题,但后来 org.apache.commons.codec.binary.Base64 解决了我的问题

【讨论】:

  • 这个注释应该更明显,因为 org.apache.commons.codec.binary 比 java.util 库更通用
  • 很抱歉,我看不到您的回答如何回答我的问题。我特别询问了官方 Java 8 Base64 API 是否可以用作使用 sun.misc Base64 API 的遗留代码的直接替换(没有任何行为改变)。 Stuart Marks 已经给出了最终答案,给出了一个反例,证明情况并非如此。您迟到的答案应该/可以是不同问题的答案。相反,你迟到的答案似乎只是“劫持”了我的问题,以促进对第三方库的使用。
  • 您是否真的验证了org.apache.commons.codec.binary.Base64 API 是sun.misc API 的直接替代品?根据您的说法,任何sun.misc Base64 编码都可以使用org.apache.commons.codec.binary.Base64 API 进行复制——对吧?由于人们对 Apache Commons Codec 库感兴趣,我将跟进一个特定于该第三方库的新问题。
  • 为了跟进您的离题答案,我创建了一个单独的问题:Is Apache Commons Codec Base64 a drop-in replacement for sun.misc.BASE64?另请注意,我无法在此处确认您的答案是否正确。请通过最终回答 Is Apache Commons Codec Base64 a drop-in replacement for sun.misc.BASE64? 来提供您的证明或代码示例。
  • Base64.encodeBase64String(passwdstring.getBytes()) 成功替换了我的new sun.misc.BASE64Encoder().encode(passwdstring.getBytes())
猜你喜欢
  • 2020-09-03
  • 2010-11-20
  • 2011-03-17
  • 1970-01-01
  • 1970-01-01
  • 2012-03-04
  • 1970-01-01
  • 2015-09-03
  • 2010-11-17
相关资源
最近更新 更多