在Java中解码Base64数据

537

我有一个使用Base64编码的图片。在Java中,最好的解码方式是什么?希望只使用Sun Java 6自带的库。


1
无论您使用的是哪种类型的应用程序(实验或非实验),都可以通过在utils包中创建一个Base64.java文件并使用此处的代码:http://migbase64.sourceforge.net/来完成。查看性能图表并注意差异:速度提高了4-5倍。 - javacoder
FYI:JEP 135建议在Java平台中引入标准的可发现API。 - Jesse Glick
进展正在发生:http://mail.openjdk.java.net/pipermail/core-libs-dev/2012-October/011722.html - JodaStephen
2
这是我最近组装的另一种实现方式:https://github.com/n1hility/playground/blob/master/src/main/java/org/jboss/FlexBase64.java - Jason Greene
3
请注意,如果您正在开发 Android 应用程序,Google 已经完成了这项工作:http://developer.android.com/reference/android/util/Base64.html - Raphael Oliveira
21个回答

463

Java 8开始,官方支持Base64编码和解码API。随着时间的推移,这可能会成为默认选择。

该API包括类java.util.Base64及其嵌套类。它支持三种不同的编码类型:基本、URL安全和MIME。

使用“基本”编码的示例代码:

import java.util.Base64;

byte[] bytes = "Hello, World!".getBytes("UTF-8");
String encoded = Base64.getEncoder().encodeToString(bytes);
byte[] decoded = Base64.getDecoder().decode(encoded);
String decodedStr = new String(decoded, StandardCharsets.UTF_8);

java.util.Base64的文档中包括更多配置编码器和解码器的方法,以及使用不同类作为输入输出(字节数组、字符串、ByteBuffers、java.io流)的方法。


1
我正在使用Java 8。如果使用Java 8,这是推荐的方法吗? - JohnMerlino
4
如果不需要兼容旧版本的Java,我建议使用这个API,因为JRE的兼容性政策比大多数库要强。此外,由于该API已包含在JRE中,因此不会对您的依赖关系造成任何限制。 - Andrea
4
Java 7已经停止更新,Java 9即将到来,这对我来说是正确的答案! - eskatos
1
几乎好了:这只接受原始的base64流,而不是base64文件。我不得不使用final byte[] decoded = Base64.getMimeDecoder().decode(encoded);代替。但还是谢谢!(使用commons-io的FileUtils.readFileToByteArrayFileUtils.writeByteArrayToFile很好 - 特别是当你意识到encoded也可以是一个byte[]时。) - mirabilos

426

从v6版开始,Java SE附带了JAXB。 javax.xml.bind.DatatypeConverter 具有使此过程变得简单的静态方法。请参见parseBase64Binary()printBase64Binary()

更新: 自Java 11以后,Java不再附带JAXB。 如果您的项目需要JAXB,则需要通过依赖管理系统(例如Maven)配置相关的库。如果您需要编译器(xjc.exe),还需要单独下载它。


20
然而,似乎 printBase64Binary(..) 方法没有执行 Base64 的 MIME 版本(http://en.wikipedia.org/wiki/Base64#MIME),而私有的 Sun 和 Commons 实现使用了这个版本。具体来说,对于长度超过76个字符的字符串,会添加换行符。我没有找到如何配置JAXB的实现来实现此行为... :-( - KLE
7
然而,Sun实现将忽略换行符,因此它们是兼容的。 - Esben Skov Pedersen
9
注意!parseBase64Binary将默默地跳过无效字符,并且不会检查base64的有效性。最好使用Commons Codec或Guava Base64。请注意,Guava会拒绝换行符和空格字符,因此需要省略带有空格的字符串进行解析:BaseEncoding.base64().decode(s.replaceAll("\s", ""))。 - Martin Vysny
10
请注意,此功能不适用于超过 65000 的数据。(Java 版本 1.6) - Huseyin Yagli
7
不要使用它,因为在jdk 9中会出现问题:java.lang.NoClassDefFoundError(javax/xml/bind/DatatypeConverter)。 - rupashka
显示剩余5条评论

104

不需要使用commons库--Sun公司在Java中附带了一个base64编码器。你可以按如下方式导入它:

import sun.misc.BASE64Decoder;

然后像这样使用它:

BASE64Decoder decoder = new BASE64Decoder();
byte[] decodedBytes = decoder.decodeBuffer(encodedBytes);

其中encodedBytes可以是java.lang.Stringjava.io.InputStream。请注意,sun.*类不被Sun官方支持。

编辑:谁知道这会成为我发布的最具争议性的答案?我知道sun.*包没有得到支持或保证会继续存在,并且我也知道Commons并经常使用它。然而,提问者要求一个“随Sun Java 6一起提供”的类,这就是我试图回答的问题。总的来说,我同意Commons是最好的选择。

编辑2: 正如amir75在下面指出的那样,Java 6+附带了JAXB,其中包含编码/解码Base64的受支持代码。请参见下面Jeremy Ross的答案


198
-1- 这是 Sun 公司内部的代码,不属于 J2SE(即不可移植),而且随时可能消失 —— Sun 明确表示请勿在用户代码中使用他们的内部库。 - kdgregory
60
明白了,因此我在结尾处加上了免责声明。 - MattK
21
这是一个短期项目,只是一次试验,不想经过新图书馆审批的流程。因此,这是对这个问题的正确答案。 - Ryan P
45
在专业环境中,使用不支持、未记录的功能从来都不是正确的决策。在企业环境中,“实验”往往会变成“生产代码”,而且无法修复这些不合规的代码。 - kdgregory
29
在一个研究部门中,如果一段代码被标记为实验性质,且每次被标记后都被废弃,那么这是正确的决定。 - Ryan P
显示剩余18条评论

60

在具体实现中,使用Commons Codec库:通过类Base64decode(byte[] array)encode(byte[] array)方法进行编解码。


7
您可以将文本“Commons Codec”链接到项目页面。这样,这个答案就比Kevin的更好了 :) - mmutilva
1
我知道这是一个老问题,但为什么这不是被接受的答案呢?commons codec不是已经包含在大多数Java安装中了吗?使用它比自己编写代码要少得多。 - Li Haoyi
2
@LiHaoyi 这个问题要求的是Sun JDK附带的库,其中不包括Commons中的任何内容。 - Ti Strga
1
错误的轨迹。这些方法不存在! - Nicolas Barbulesco

37

Guava现在内置Base64解码。

使用BaseEncoding.base64().decode()

对于处理可能输入中的空格,请使用

BaseEncoding.base64().decode(CharMatcher.WHITESPACE.removeFrom(...));

有关更多信息,请参见此讨论


1
Guava 14仍然是一个发布候选版本,但我仍然支持它 - 等到它达到任何体面的位置时,它应该是完美的 :-) - Peter Becker
1
Guava的base64解码器会拒绝换行符和空格字符,因此您需要事先将它们删除。 - Martin Vysny

36

我的解决方案是最快和最简单的。

public class MyBase64 {

    private final static char[] ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();

    private static int[]  toInt   = new int[128];

    static {
        for(int i=0; i< ALPHABET.length; i++){
            toInt[ALPHABET[i]]= i;
        }
    }

    /**
     * Translates the specified byte array into Base64 string.
     *
     * @param buf the byte array (not null)
     * @return the translated Base64 string (not null)
     */
    public static String encode(byte[] buf){
        int size = buf.length;
        char[] ar = new char[((size + 2) / 3) * 4];
        int a = 0;
        int i=0;
        while(i < size){
            byte b0 = buf[i++];
            byte b1 = (i < size) ? buf[i++] : 0;
            byte b2 = (i < size) ? buf[i++] : 0;

            int mask = 0x3F;
            ar[a++] = ALPHABET[(b0 >> 2) & mask];
            ar[a++] = ALPHABET[((b0 << 4) | ((b1 & 0xFF) >> 4)) & mask];
            ar[a++] = ALPHABET[((b1 << 2) | ((b2 & 0xFF) >> 6)) & mask];
            ar[a++] = ALPHABET[b2 & mask];
        }
        switch(size % 3){
            case 1: ar[--a]  = '=';
            case 2: ar[--a]  = '=';
        }
        return new String(ar);
    }

    /**
     * Translates the specified Base64 string into a byte array.
     *
     * @param s the Base64 string (not null)
     * @return the byte array (not null)
     */
    public static byte[] decode(String s){
        int delta = s.endsWith( "==" ) ? 2 : s.endsWith( "=" ) ? 1 : 0;
        byte[] buffer = new byte[s.length()*3/4 - delta];
        int mask = 0xFF;
        int index = 0;
        for(int i=0; i< s.length(); i+=4){
            int c0 = toInt[s.charAt( i )];
            int c1 = toInt[s.charAt( i + 1)];
            buffer[index++]= (byte)(((c0 << 2) | (c1 >> 4)) & mask);
            if(index >= buffer.length){
                return buffer;
            }
            int c2 = toInt[s.charAt( i + 2)];
            buffer[index++]= (byte)(((c1 << 4) | (c2 >> 2)) & mask);
            if(index >= buffer.length){
                return buffer;
            }
            int c3 = toInt[s.charAt( i + 3 )];
            buffer[index++]= (byte)(((c2 << 6) | c3) & mask);
        }
        return buffer;
    } 

}

15
这并不是个错误!- 请阅读Javadoc注释... decode(..)方法的参数是base64字符串,而不仅仅是任何字符串。byte[] b1 = {1,2,3}; byte[] b2 = decode(encode(b1)); System.out.println(Arrays.equals( b1, b2 )); // => true - GeorgeK
10
最快最简单的方法?要重新发明轮子吗?! - Nicolas Barbulesco
7
我进行了一些测试,将这个类与commons-codec进行比较,似乎可以正常工作。我需要像这样简单的东西,因为我只需要base64编码,不想要commons-codec提供的所有额外内容,谢谢。 - Michael
2
这个可靠吗?如果你不想导入外部库,这似乎是最简单的方法。 - Felipe
2
它无法处理从AES算法获取的字节。 - shontauro
显示剩余2条评论

12

以下是我自己的实现,如果对某人有用:

public class Base64Coder {

    // The line separator string of the operating system.
    private static final String systemLineSeparator = System.getProperty("line.separator");

    // Mapping table from 6-bit nibbles to Base64 characters.
    private static final char[] map1 = new char[64];
       static {
          int i=0;
          for (char c='A'; c<='Z'; c++) map1[i++] = c;
          for (char c='a'; c<='z'; c++) map1[i++] = c;
          for (char c='0'; c<='9'; c++) map1[i++] = c;
          map1[i++] = '+'; map1[i++] = '/'; }

    // Mapping table from Base64 characters to 6-bit nibbles.
    private static final byte[] map2 = new byte[128];
       static {
          for (int i=0; i<map2.length; i++) map2[i] = -1;
          for (int i=0; i<64; i++) map2[map1[i]] = (byte)i; }

    /**
    * Encodes a string into Base64 format.
    * No blanks or line breaks are inserted.
    * @param s  A String to be encoded.
    * @return   A String containing the Base64 encoded data.
    */
    public static String encodeString (String s) {
       return new String(encode(s.getBytes())); }

    /**
    * Encodes a byte array into Base 64 format and breaks the output into lines of 76 characters.
    * This method is compatible with <code>sun.misc.BASE64Encoder.encodeBuffer(byte[])</code>.
    * @param in  An array containing the data bytes to be encoded.
    * @return    A String containing the Base64 encoded data, broken into lines.
    */
    public static String encodeLines (byte[] in) {
       return encodeLines(in, 0, in.length, 76, systemLineSeparator); }

    /**
    * Encodes a byte array into Base 64 format and breaks the output into lines.
    * @param in            An array containing the data bytes to be encoded.
    * @param iOff          Offset of the first byte in <code>in</code> to be processed.
    * @param iLen          Number of bytes to be processed in <code>in</code>, starting at <code>iOff</code>.
    * @param lineLen       Line length for the output data. Should be a multiple of 4.
    * @param lineSeparator The line separator to be used to separate the output lines.
    * @return              A String containing the Base64 encoded data, broken into lines.
    */
    public static String encodeLines (byte[] in, int iOff, int iLen, int lineLen, String lineSeparator) {
       int blockLen = (lineLen*3) / 4;
       if (blockLen <= 0) throw new IllegalArgumentException();
       int lines = (iLen+blockLen-1) / blockLen;
       int bufLen = ((iLen+2)/3)*4 + lines*lineSeparator.length();
       StringBuilder buf = new StringBuilder(bufLen);
       int ip = 0;
       while (ip < iLen) {
          int l = Math.min(iLen-ip, blockLen);
          buf.append (encode(in, iOff+ip, l));
          buf.append (lineSeparator);
          ip += l; }
       return buf.toString(); }

    /**
    * Encodes a byte array into Base64 format.
    * No blanks or line breaks are inserted in the output.
    * @param in  An array containing the data bytes to be encoded.
    * @return    A character array containing the Base64 encoded data.
    */
    public static char[] encode (byte[] in) {
       return encode(in, 0, in.length); }

    /**
    * Encodes a byte array into Base64 format.
    * No blanks or line breaks are inserted in the output.
    * @param in    An array containing the data bytes to be encoded.
    * @param iLen  Number of bytes to process in <code>in</code>.
    * @return      A character array containing the Base64 encoded data.
    */
    public static char[] encode (byte[] in, int iLen) {
       return encode(in, 0, iLen); }

    /**
    * Encodes a byte array into Base64 format.
    * No blanks or line breaks are inserted in the output.
    * @param in    An array containing the data bytes to be encoded.
    * @param iOff  Offset of the first byte in <code>in</code> to be processed.
    * @param iLen  Number of bytes to process in <code>in</code>, starting at <code>iOff</code>.
    * @return      A character array containing the Base64 encoded data.
    */
    public static char[] encode (byte[] in, int iOff, int iLen) {
       int oDataLen = (iLen*4+2)/3;       // output length without padding
       int oLen = ((iLen+2)/3)*4;         // output length including padding
       char[] out = new char[oLen];
       int ip = iOff;
       int iEnd = iOff + iLen;
       int op = 0;
       while (ip < iEnd) {
          int i0 = in[ip++] & 0xff;
          int i1 = ip < iEnd ? in[ip++] & 0xff : 0;
          int i2 = ip < iEnd ? in[ip++] & 0xff : 0;
          int o0 = i0 >>> 2;
          int o1 = ((i0 &   3) << 4) | (i1 >>> 4);
          int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
          int o3 = i2 & 0x3F;
          out[op++] = map1[o0];
          out[op++] = map1[o1];
          out[op] = op < oDataLen ? map1[o2] : '='; op++;
          out[op] = op < oDataLen ? map1[o3] : '='; op++; }
       return out; }

    /**
    * Decodes a string from Base64 format.
    * No blanks or line breaks are allowed within the Base64 encoded input data.
    * @param s  A Base64 String to be decoded.
    * @return   A String containing the decoded data.
    * @throws   IllegalArgumentException If the input is not valid Base64 encoded data.
    */
    public static String decodeString (String s) {
       return new String(decode(s)); }

    /**
    * Decodes a byte array from Base64 format and ignores line separators, tabs and blanks.
    * CR, LF, Tab and Space characters are ignored in the input data.
    * This method is compatible with <code>sun.misc.BASE64Decoder.decodeBuffer(String)</code>.
    * @param s  A Base64 String to be decoded.
    * @return   An array containing the decoded data bytes.
    * @throws   IllegalArgumentException If the input is not valid Base64 encoded data.
    */
    public static byte[] decodeLines (String s) {
       char[] buf = new char[s.length()];
       int p = 0;
       for (int ip = 0; ip < s.length(); ip++) {
          char c = s.charAt(ip);
          if (c != ' ' && c != '\r' && c != '\n' && c != '\t')
             buf[p++] = c; }
       return decode(buf, 0, p); }

    /**
    * Decodes a byte array from Base64 format.
    * No blanks or line breaks are allowed within the Base64 encoded input data.
    * @param s  A Base64 String to be decoded.
    * @return   An array containing the decoded data bytes.
    * @throws   IllegalArgumentException If the input is not valid Base64 encoded data.
    */
    public static byte[] decode (String s) {
       return decode(s.toCharArray()); }

    /**
    * Decodes a byte array from Base64 format.
    * No blanks or line breaks are allowed within the Base64 encoded input data.
    * @param in  A character array containing the Base64 encoded data.
    * @return    An array containing the decoded data bytes.
    * @throws    IllegalArgumentException If the input is not valid Base64 encoded data.
    */
    public static byte[] decode (char[] in) {
       return decode(in, 0, in.length); }

    /**
    * Decodes a byte array from Base64 format.
    * No blanks or line breaks are allowed within the Base64 encoded input data.
    * @param in    A character array containing the Base64 encoded data.
    * @param iOff  Offset of the first character in <code>in</code> to be processed.
    * @param iLen  Number of characters to process in <code>in</code>, starting at <code>iOff</code>.
    * @return      An array containing the decoded data bytes.
    * @throws      IllegalArgumentException If the input is not valid Base64 encoded data.
    */
    public static byte[] decode (char[] in, int iOff, int iLen) {
       if (iLen%4 != 0) throw new IllegalArgumentException ("Length of Base64 encoded input string is not a multiple of 4.");
       while (iLen > 0 && in[iOff+iLen-1] == '=') iLen--;
       int oLen = (iLen*3) / 4;
       byte[] out = new byte[oLen];
       int ip = iOff;
       int iEnd = iOff + iLen;
       int op = 0;
       while (ip < iEnd) {
          int i0 = in[ip++];
          int i1 = in[ip++];
          int i2 = ip < iEnd ? in[ip++] : 'A';
          int i3 = ip < iEnd ? in[ip++] : 'A';
          if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127)
             throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
          int b0 = map2[i0];
          int b1 = map2[i1];
          int b2 = map2[i2];
          int b3 = map2[i3];
          if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0)
             throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
          int o0 = ( b0       <<2) | (b1>>>4);
          int o1 = ((b1 & 0xf)<<4) | (b2>>>2);
          int o2 = ((b2 &   3)<<6) |  b3;
          out[op++] = (byte)o0;
          if (op<oLen) out[op++] = (byte)o1;
          if (op<oLen) out[op++] = (byte)o2; }
       return out; }

    // Dummy constructor.
    private Base64Coder() {}
}

11
作为替代方案,可以查看javax.mail.internet.MimeUtility.decode(),而不是使用sun.misc.BASE64Decoder或非核心库。
public static byte[] encode(byte[] b) throws Exception {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    OutputStream b64os = MimeUtility.encode(baos, "base64");
    b64os.write(b);
    b64os.close();
    return baos.toByteArray();
}
public static byte[] decode(byte[] b) throws Exception {
    ByteArrayInputStream bais = new ByteArrayInputStream(b);
    InputStream b64is = MimeUtility.decode(bais, "base64");
    byte[] tmp = new byte[b.length];
    int n = b64is.read(tmp);
    byte[] res = new byte[n];
    System.arraycopy(tmp, 0, res, 0, n);
    return res;
}

3
javax.mail不是核心的一部分。 - Adam Goode
javax.mail.internet.MimeUtility 包含在 appengine-api.jar 中。 - diyism

10

假设有一个测试的编码/解码示例,使用javax.xml.bind.DatatypeConverter中的方法parseBase64Binary()printBase64Binary(),参考@jeremy-ross和@nightfirecat的答案。

@Test
public void EncodeDecode() {
    //ENCODE
    String hello = "Hello World";
    byte[] helloBytes = hello.getBytes(StandardCharsets.UTF_8);
    String encodedHello = DatatypeConverter.printBase64Binary(helloBytes);
    LOGGER.info(hello + " encoded=> " + encodedHello);

    //DECODE
    byte[] encodedHelloBytes = DatatypeConverter.parseBase64Binary(encodedHello);
    String helloAgain = new String(encodedHelloBytes, StandardCharsets.UTF_8) ;
    LOGGER.info(encodedHello + " decoded=> " + helloAgain);

    Assert.assertEquals(hello, helloAgain);
}

结果:

INFO - Hello World encoded=> SGVsbG8gV29ybGQ=
INFO - SGVsbG8gV29ybGQ= decoded=> Hello World

10

虽然回答有些晚,但我的基准测试显示Jetty的Base64编码实现相当快。速度不及MiGBase64,但比iHarder Base64更快。

import org.eclipse.jetty.util.B64Code;

final String decoded = B64Code.decode(encoded, "UTF-8");

我也进行了一些基准测试:

      library     |    encode    |    decode   
------------------+--------------+-------------
 'MiGBase64'      |  10146001.00 |  6426446.00
 'Jetty B64Code'  |   8846191.00 |  3101361.75
 'iHarder Base64' |   3259590.50 |  2505280.00
 'Commons-Codec'  |    241318.04 |   255179.96

这些是以runs/sec为单位的,所以值越高越好。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接