Java 8会因为将lambdas,流,新的日期/时间模型和Nashorn JavaScript引擎引入Java而被记住。有些人还会记得Java 8,因为它引入了各种小但有用的功能,例如Base64 API。什么是Base64以及如何使用此API?这篇文章回答了这些问题。
Base64 是一种二进制到文本编码方案,通过将二进制数据转换为基数-64表示,以可打印的 ASCII 字符串格式表示二进制数据。每个Base64数字恰好代表6位二进制数据。
在 RFC 1421 中首次描述了Base64(但没有命名) :Internet电子邮件的隐私增强:第一部分:消息加密和认证过程 。后来,它在 RFC 2045中 正式呈现为Base64 :多用途Internet邮件扩展(MIME)第一部分:Internet消息体的格式 ,随后在 RFC 4648:Base16,Base32和Base64数据编码中 重新访问。
Base64用于防止数据在传输过程中通过信息系统(例如电子邮件)进行修改,这些信息系统可能不是8-bit clean(它们可能是8位值)。例如,您将图像附加到电子邮件消息,并希望图像到达另一端而不会出现乱码。您的电子邮件软件对图像进行Base64编码并将等效文本插入到邮件中,如下图所示:
Content-Disposition: inline; filename=IMG_0006.JPG Content-Transfer-Encoding: base64 /9j/4R/+RXhpZgAATU0AKgAAAAgACgEPAAIAAAAGAAAAhgEQAAIAAAAKAAAAjAESAAMAAAABAAYA AAEaAAUAAAABAAAAlgEbAAUAAAABAAAAngEoAAMAAAABAAIAAAExAAIAAAAHAAAApgEyAAIAAAAU AAAArgITAAMAAAABAAEAAIdpAAQAAAABAAAAwgAABCRBcHBsZQBpUGhvbmUgNnMAAAAASAAAAAEA ... NOMbnDUk2bGh26x2yiJcsoBIrvtPe3muBbTRGMdeufmH+Nct4chUXpwSPk/qK9GtJRMWWVFbZ0JH I4rf2dkZSbOjt7hhEzwcujA4I7Gust75pYVwAPpXn+kzNLOVYD7xFegWEKPkHsM/pU1F0NKbNS32 o24sSCOlaaFYLUhjky4x9PSsKL5bJsdWkAz3xirH2dZLy1DM2C44zx1FZqL2PTXY/9k=
插图显示此编码图像以 /
开头和结尾 =
。在 ...
表明未展示的文字。请注意,此示例或任何其他示例的整个编码比原始二进制数据大大约33%。
收件人的电子邮件软件将对编码的文本图像进行Base64解码,以恢复原始二进制图像。对于此示例,图像将与消息的其余部分一起显示。
Base64依赖于简单的编码和解码算法。它们使用65个字符的US-ASCII子集,其中前64个字符中的每一个都映射到等效的6位二进制序列。这是字母表:
Value Encoding Value Encoding Value Encoding Value Encoding 0 A 17 R 34 i 51 z 1 B 18 S 35 j 52 0 2 C 19 T 36 k 53 1 3 D 20 U 37 l 54 2 4 E 21 V 38 m 55 3 5 F 22 W 39 n 56 4 6 G 23 X 40 o 57 5 7 H 24 Y 41 p 58 6 8 I 25 Z 42 q 59 7 9 J 26 a 43 r 60 8 10 K 27 b 44 s 61 9 11 L 28 c 45 t 62 + 12 M 29 d 46 u 63 / 13 N 30 e 47 v 14 O 31 f 48 w (pad) = 15 P 32 g 49 x 16 Q 33 h 50 y
第65个字符( =
)用于将Base64编码的文本填充到整数大小,如下所述。
编码算法接收8位字节的输入流。假定该流首先以最高有效位排序:第一位是第一个字节中的高位,第八位是该字节中的低位,依此类推。
从左到右,这些字节被组织成24位组。每组被视为四个连接的6位组。每个6位组索引为64个可打印字符的数组; 输出结果字符。
当在编码数据的末尾有少于24位可用时,添加零位(在右侧)以形成整数个6位组。然后,可以输出一个或两个 =
填充字符。有两种情况需要考虑:
= =
让我们考虑三个例子来了解编码算法的工作原理。首先,假设我们希望编码 @!*
:
Source ASCII bit sequences with prepended 0 bits to form 8-bit bytes: @ ! * 01000000 00100001 00101010 Dividing this 24-bit group into four 6-bit groups yields the following: 010000 | 000010 | 000100 | 101010 These bit patterns equate to the following indexes: 16 2 4 42 Indexing into the Base64 alphabet shown earlier yields the following encoding: QCEq
我们将继续将输入序列缩短为 @!
:
Source ASCII bit sequences with prepended 0 bits to form 8-bit bytes: @ ! 01000000 00100001 Two zero bits are appended to make three 6-bit groups: 010000 | 000010 | 000100 These bit patterns equate to the following indexes: 16 2 4 Indexing into the Base64 alphabet shown earlier yields the following encoding: QCE An = pad character is output, yielding the following final encoding: QCE=
最后一个示例将输入序列缩短为 @
:
Source ASCII bit sequence with prepended 0 bits to form 8-bit byte: @ 01000000 Four zero bits are appended to make two 6-bit groups: 010000 | 000000 These bit patterns equate to the following indexes: 16 0 Indexing into the Base64 alphabet shown earlier yields the following encoding: QA Two = pad characters are output, yielding the following final encoding: QA==
解码算法是编码算法的逆。但是,检测到不在Base64字母表中的字符或填充字符数不正确时,可以自由采取适当的措施。
已经设计了几种Base64变体。一些变体要求编码的输出流被分成多行固定长度,每行不超过一定的长度限制,并且(最后一行除外)通过行分隔符与下一行分开(回车 /r
后跟一行换行 /n
)。我描述了Java 8的Base64 API支持的三种变体。查看Wikipedia的 Base64
条目以获取完整的变体列表。
RFC 4648描述了一种称为 Basic 的Base64变体。此变体使用RFC 4648和RFC 2045的表1中所示的Base64字母表(并在本文前面所示)进行编码和解码。编码器将编码的输出流视为一行; 没有输出行分隔符。解码器拒绝包含Base64字母表之外的字符的编码。请注意,可以覆盖这些和其他规定。
RFC 2045描述了一种称为 MIME 的Base64变体。此变体使用RFC 2045的表1中提供的Base64字母表进行编码和解码。编码的输出流被组织成不超过76个字符的行; 每行(最后一行除外)通过行分隔符与下一行分隔。解码期间将忽略Base64字母表中未找到的所有行分隔符或其他字符。
RFC 4648描述了一种称为 URL和文件名安全
的Base64变体。此变体使用RFC 4648的表2中提供的Base64字母表进行编码和解码。字母表与前面显示的字母相同,只是 -
替换 +
和 _
替换 /
。不输出行分隔符。解码器拒绝包含Base64字母表之外的字符的编码。
Base64编码在冗长的二进制数据和HTTP GET请求的上下文中很有用。我们的想法是对这些数据进行编码,然后将其附加到HTTP GET URL。如果使用Basic或MIME变体,则编码数据中的任何 +
或 /
字符必须被URL编码为十六进制序列( +
变为 %2B
和 /
变为 %2F
)。生成的URL字符串会稍长一些。通过更换 +
同 -
和 /
同 _
,URL和文件名安全消除了对URL编码器/解码器(和它们的编码值的长度影响)的需要。此外,当编码数据用于文件名时,此变体很有用,因为Unix和Windows文件名不能包含 /
。
Java 8引入一个Base64 API,包括 java.util.Base64
类及其嵌套 static
类 Encoder
和 Decoder
。 Base64
有几种获取编码器和解码器的 static
方法:
Base64.Encoder getEncoder()
:返回Basic变体的编码器。 Base64.Decoder getDecoder()
:返回Basic变体的解码器。 Base64.Encoder getMimeEncoder()
:返回MIME变体的编码器。 Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator)
:返回具有给定 lineLength
的已修改MIME变体的编码器(向下舍入到最接近的4的倍数 - 输出在 lineLength
<= 0 时不分成行)和 lineSeparator
。当 lineSeparator
包含RFC 2045的表1中列出的任何Base64字母字符时,它会抛出 java.lang.IllegalArgumentException
。 getMimeEncoder()
方法返回的RFC 2045编码器是相当严格的。例如,该编码器创建具有76个字符的固定行长度(最后一行除外)的编码文本。如果您希望编码器支持RFC 1421,它指定固定行长度为64个字符,则需要使用 getMimeEncoder(int lineLength, byte[] lineSeparator)
。 Base64.Decoder getMimeDecoder()
:返回MIME变体的解码器。 Base64.Encoder getUrlEncoder()
:返回URL和Filename Safe变体的编码器。 Base64.Decoder getUrlDecoder()
:返回URL和Filename Safe变体的解码器。
Base64.Encoder
提出了几种用于编码字节序列的线程安全实例方法 将空引用传递给以下方法之一会导致 java.lang.NullPointerException
:
byte[] encode(byte[] src)
:将 src
所有字节编码到新分配的字节数组中,然后返回结果。 int encode(byte[] src, byte[] dst)
:编码 src
所有字节到 dst
(开始于偏移0)。如果 dst
不足以保存编码,则抛出 IllegalArgumentException
。否则,返回写入 dst
的字节数。 ByteBuffer encode(ByteBuffer buffer)
:将 buffer
所有剩余字节编码到新分配的 java.nio.ByteBuffer
对象中。返回后, buffer
的position将更新到它的limit; 它的limit不会改变。返回的输出缓冲区的position将为零,其limit将是结果编码字节的数量。 String encodeToString(byte[] src)
:将 src
所有字节编码为一个字符串,并返回该字符串。调用此方法等同于执行 new String(encode(src), StandardCharsets.ISO_8859_1)
。 Base64.Encoder withoutPadding()
:返回与此编码器等效编码的编码器,但不在编码字节数据的末尾添加任何填充字符。 OutputStream wrap(OutputStream os)
:包装输出流以编码字节数据。建议在使用后立即关闭返回的输出流,在此期间它会将所有可能的剩余字节刷新到底层输出流。关闭返回的输出流将关闭基础输出流。
Base64.Decoder
提出了几种解码字节序列的线程安全实例方法。将空引用传递给以下方法之一会导致 NullPointerException
:
byte[] decode(byte[] src)
:将 src
所有字节解码为新分配的字节数组,然后返回。当Base64无效时抛出 IllegalArgumentException
。 int decode(byte[] src, byte[] dst)
:解码 src
所有字节到 dst
(从偏移量0开始)。如果 dst
不足以保存解码,或者当Base64无效的时,抛出 IllegalArgumentException
。否则,返回写入 dst
的字节数。 byte[] decode(String src)
:将 src
所有字节解码为新分配的字节数组,并返回该字节数组。调用此方法相当于调用 decode(src.getBytes(StandardCharsets.ISO_8859_1))
。当Base64无效时抛出 IllegalArgumentException
。 ByteBuffer decode(ByteBuffer buffer)
:将 buffer
所有字节解码为新分配的 java.nio.ByteBuffer
对象。返回后, buffer
其position将更新为它的limit; 它的limit不会改变。返回的输出缓冲区的position将为零,其limit将是生成的解码字节数。当Base64无效时抛出 IllegalArgumentException
。在这种情况下, buffer
位置不会更新。 InputStream wrap(InputStream is)
:包装输入流以解码字节数据。当输入Base64无效时, is
对象的 read()
方法抛出 java.io.IOException
。关闭返回的输出流将关闭基础输出流。 Java的Base64 API易于使用。考虑一个“Hello,World”式程序,使用Basic编码器对Base64进行编码,然后使用Basic解码器对编码文本进行Base64解码。清单1展示了源代码。
HelloBase64.java
import java.util.Base64; public class HelloBase64 { public static void main(String[] args) { String msg = "Hello, Base64!"; Base64.Encoder enc = Base64.getEncoder(); byte[] encbytes = enc.encode(msg.getBytes()); for (int i = 0; i < encbytes.length; i++) { System.out.printf("%c", (char) encbytes[i]); if (i != 0 && i % 4 == 0) System.out.print(' '); } System.out.println(); Base64.Decoder dec = Base64.getDecoder(); byte[] decbytes = dec.decode(encbytes); System.out.println(new String(decbytes)); } }
编译清单1如下:
javac HelloBase64.java
运行生成的应用程序如下:
java HelloBase64
您应该观察以下输出:
SGVsb G8sI EJhc 2U2N CE= Hello, Base64!
Base64对编码文件更有用。我已经创建了第二个应用程序,它演示了这个有用性以及更多的Base64 API。清单2显示了应用程序的源代码。
FileEncDec.java
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Base64; public class FileEncDec { public static void main(String[] args) { if (args.length != 1) { System.err.println("usage: java FileEncDec filename"); return; } try (FileInputStream fis = new FileInputStream(args[0])) { Base64.Encoder enc1 = Base64.getEncoder(); Base64.Encoder enc2 = Base64.getMimeEncoder(); Base64.Encoder enc3 = Base64.getUrlEncoder(); OutputStream os1 = enc1.wrap(new FileOutputStream(args[0] + "1.enc")); OutputStream os2 = enc2.wrap(new FileOutputStream(args[0] + "2.enc")); OutputStream os3 = enc3.wrap(new FileOutputStream(args[0] + "3.enc")); int _byte; while ((_byte = fis.read()) != -1) { os1.write(_byte); os2.write(_byte); os3.write(_byte); } os1.close(); os2.close(); os3.close(); } catch (IOException ioe) { System.err.printf("I/O error: %s%n", ioe.getMessage()); } try (FileOutputStream fos1 = new FileOutputStream("1" + args[0]); FileOutputStream fos2 = new FileOutputStream("2" + args[0]); FileOutputStream fos3 = new FileOutputStream("3" + args[0])) { Base64.Decoder dec1 = Base64.getDecoder(); Base64.Decoder dec2 = Base64.getMimeDecoder(); Base64.Decoder dec3 = Base64.getUrlDecoder(); InputStream is1 = dec1.wrap(new FileInputStream(args[0] + "1.enc")); InputStream is2 = dec2.wrap(new FileInputStream(args[0] + "2.enc")); InputStream is3 = dec3.wrap(new FileInputStream(args[0] + "3.enc")); int _byte; while ((_byte = is1.read()) != -1) fos1.write(_byte); while ((_byte = is2.read()) != -1) fos2.write(_byte); while ((_byte = is3.read()) != -1) fos3.write(_byte); is1.close(); is2.close(); is3.close(); } catch (IOException ioe) { System.err.printf("I/O error: %s%n", ioe.getMessage()); } } }
该 FileEncDec
应用程序需要一个文件作为其孤立命令行参数的名称。它继续打开此文件并读取其内容。每个读取字节通过不同的编码器和包装的输出流写入另一个文件。之后,这些文件通过不同的解码器和包装的输入流打开和读取。结果存储在三个单独的文件中。
编译清单2如下:
javac FileEncDec.java
运行生成的应用程序如下(假设一个名为JPEG的文件 image.jpg
- 请参阅帖子的代码存档):
java FileEncDec image.jpg
您应该在当前目录中观察 image.jpg1.enc
, image.jpg2.enc
和 image.jpg3.enc
文件。
image.jpg1.enc
将Basic编码存储在一个长行上。下面是输出的前缀,为了便于阅读,分为两行( ...
序列表示内容未显示):
/9j/4AAQSkZJRgABAQEASABIAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/ 4QM6aHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+...
image.jpg2.enc
将MIME编码存储在多行中:
/9j/4AAQSkZJRgABAQEASABIAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/ 4QM6aHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9 Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pg0KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpu czptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEw LzAyLzEyLTE3OjMyOjAwICAgICAgICAiPg0KCTxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3 dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+DQoJCTxyZGY6RGVzY3JpcHRpb24g cmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1s bnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJo dHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRv clRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzUgV2luZG93cyIgeG1wTU06SW5zdGFuY2VJRD0ieG1w LmlpZDoyMzlCQTU3RjY3RDMxMUUzODg3OEFGOTg0RUUzMkVBOSIgeG1wTU06RG9jdW1lbnRJRD0i eG1wLmRpZDoyMzlCQTU4MDY3RDMxMUUzODg3OEFGOTg0RUUzMkVBOSI+DQoJCQk8eG1wTU06RGVy aXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyMzlCQTU3RDY3RDMxMUUzODg3OEFG OTg0RUUzMkVBOSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyMzlCQTU3RTY3RDMxMUUzODg3 OEFGOTg0RUUzMkVBOSIvPg0KCQk8L3JkZjpEZXNjcmlwdGlvbj4NCgk8L3JkZjpSREY+DQo8L3g6 eG1wbWV0YT4NCjw/eHBhY2tldCBlbmQ9J3cnPz7/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYE BAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYM CAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAAR CAAeADADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgED AwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRol JicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWW l5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3 +Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3 AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5 OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaan qKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIR AxEAPwD4y1DwrZ3epxppsxS3cfeu+HUDO5yqAnbweEDkY6k11fw4+GWqeI9JhSVdBt9Ljma6Ek9r FctNIiqTEzxgybSDGChYL+9XALMM/JegyRfEgSTb9bsWRDKRNEbhlD7WXaU5IYuMMABkkkdSOh8R RSJcxx3etaha2cajCTJ5URXLFXljPzu3mbThlYZ6Z5A7KvFNFv2cZpPzvf8AJK/3k0chqW9pKLa8 rW/Vn3t8QvFPjb4x67pOm+NNQ0O60nTLb93YWV9Nplpdg4HkzSAFlCRFkHUgBc8FiPBde8IyWuma pfXnhW6sYdPVXeaK6WO0hSRhHENzZ8xtxUfKxZuSc5BHz78NfjD4m8D+L9S1nSbfxprV5q0TLqd3 aRzNLewuypyXyZkO9sIE2rsU4GMjd+HXizVPhemq3Xh61+L1zHrbLpl3LcaVt8uzYoGkBKs8bqD8 skfzFN4Kxh+eT/Wilhnytp7bJ2v1Wj0b8/yR1f2DOurxut92vlvul5H0xofxB8Eaf8HZobj4c6dp uqarp8unwa3DK97I43qXxHK5SKbB/wBafmCnCAKeOB8Pado91dTxpazpBJC6nMMV3M4xyFZ02xnq fMVdy449R4V8X/E0PhT4kRtZ2Xju+tlhMUE0OoJHLDKjgMojKbGiMSbxlVGSoyONvK3H7Rnia90O 2l0OG8WeztVW5kJE7pN5SLvXAHLSuxbhjwNuxQRV4XiqlVp+0hCXvaq7Xyvdt+W2+yM8Vw/UpT5J zj7ujsn+iS8/zZ+h91qGi3t7Lpsmi3CR+SLi3+y3kkIjTgZYBDhlG7bxkZPLFhWh47+EmgzeDdQ8 UeEdYbTdPUL9p0xtUuJyJtuxWEsbxnBOThwuArAsVznFstCaLxjpuk7bdYLi5+ybQhYIpcxEg8cj jHsPUkn16Pwf/wAIHqL2d5cR6ppdx+7Wze2CQuihTyqsNh28gglg3RgBz+FYOpDBzi5RUodU0ndd d1v1Wq1P0DETnibtVGpdNWrfd/wT520DRfEV1eyWt3cXek/2ZCF3Tt9str8RxKCVZozI0rKFwC6Y L84GBUC+GfE2sQ6nNc3Gl6T/AGezSodT0mOX7RIxxGEEbqFkxuHqSVXJJBEn7YNvJ8Dvit9ttbi+ uNH1WITC3FwI7i33gSbFYJt2j7v3eRk4ycjwo/F6bTPOjT7bI0gJLtPy+75lDDoccE+9fquG4Syv F044mnTSUkmrJrR+V/0PjqnE2OoN0ZTbs3e7PbdHbXtGvlSbS/Ccmm3Nq7/aLN73TWluFXhgD5q5 BZeuMbQAVANN8feJYfBuoLqFj4L1jUrW1t45ZpV8SIJbNiqyNuD5Dqm6TDA/KcggEYrF8M/FTQ9c +HurfZLfV7jVNJ0sag320oIBH5yqyoUbcrfMT6HnIGa53Qvi9b+WGkspp47hWQo7jKj2YflyDkVn /qPgJXlFyT23dvuTX5ov/WrGxsna3or/AHtM/9k=
image.jpg3.enc
将URL和文件名安全编码存储在一个长行上。下面是输出的前缀,分为两行以便于阅读:
_9j_4AAQSkZJRgABAQEASABIAAD_4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD_ 4QM6aHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu-...
image.jpg1.enc
和 image.jpg3.enc
之间的区别在于每个 /
都被替换为 _
并且每个 +
都被替换为 -
。
您还应该在当前目录中观察 1image.jpg
, 2image.jpg
和 3image.jpg
文件。这些文件中的每一个都包含相同的内容 image.jpg
。
Base64 API是Java 8引入的各种小“宝石”之一。如果你必须使用Base64,你会发现这个API非常方便。我鼓励您尝试一下 Base64
,从本文未涉及的方法开始。
原文链接: https://www.javaworld.com/article/3240006/base64-encoding-and-decoding-in-java-8.html