Java 8中的Base64编码和解码

时间:2023-12-17 10:24:38

转自:https://juejin.im/post/5c99b2976fb9a070e76376cc

Java 8会因为将lambdas,流,新的日期/时间模型和Nashorn JavaScript引擎引入Java而被记住。有些人还会记得Java 8,因为它引入了各种小但有用的功能,例如Base64 API。什么是Base64以及如何使用此API?这篇文章回答了这些问题。

什么是Base64?

Base64

是一种二进制到文本编码方案,通过将二进制数据转换为基数-64表示,以可打印的ASCII字符串格式表示二进制数据。每个Base64数字恰好代表6位二进制数据。

Base64请求评论文件

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编码和解码

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位组。然后,可以输出一个或两个=填充字符。有两种情况需要考虑:

  • 一个剩余字节:将四个零位附加到该字节以形成两个6位组。每个组索引数组并输出结果字符。在这两个字符之后,输出两个=填充字符。
  • 剩下的两个字节:两个零位附加到第二个字节,形成三个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变种

已经设计了几种Base64变体。一些变体要求编码的输出流被分成多行固定长度,每行不超过一定的长度限制,并且(最后一行除外)通过行分隔符与下一行分开(回车\r后跟一行换行\n)。我描述了Java 8的Base64 API支持的三种变体。查看Wikipedia的Base64条目以获取完整的变体列表。

Basic

RFC 4648描述了一种称为

Basic

的Base64变体。此变体使用RFC 4648和RFC 2045的表1中所示的Base64字母表(并在本文前面所示)进行编码和解码。编码器将编码的输出流视为一行; 没有输出行分隔符。解码器拒绝包含Base64字母表之外的字符的编码。请注意,可以覆盖这些和其他规定。

MIME

RFC 2045描述了一种称为

MIME

的Base64变体。此变体使用RFC 2045的表1中提供的Base64字母表进行编码和解码。编码的输出流被组织成不超过76个字符的行; 每行(最后一行除外)通过行分隔符与下一行分隔。解码期间将忽略Base64字母表中未找到的所有行分隔符或其他字符。

URL and Filename Safe

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的Base64 API

Java 8引入一个Base64 API,包括java.util.Base64类及其嵌套staticEncoderDecoderBase64有几种获取编码器和解码器的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.IllegalArgumentExceptiongetMimeEncoder()方法返回的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。关闭返回的输出流将关闭基础输出流。

你好,Base64

Java的Base64 API易于使用。考虑一个“Hello,World”式程序,使用Basic编码器对Base64进行编码,然后使用Basic解码器对编码文本进行Base64解码。清单1展示了源代码。

清单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显示了应用程序的源代码。

清单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.encimage.jpg2.encimage.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.encimage.jpg3.enc之间的区别在于每个/都被替换为 _并且每个+都被替换为 -

您还应该在当前目录中观察1image.jpg2image.jpg3image.jpg文件。这些文件中的每一个都包含相同的内容image.jpg

结论

Base64 API是Java 8引入的各种小“宝石”之一。如果你必须使用Base64,你会发现这个API非常方便。我鼓励您尝试一下Base64,从本文未涉及的方法开始。