Java 8会因为将lambdas,流,新的日期/时间模型和Nashorn JavaScript引擎引入Java而被记住。有些人还会记得Java 8,因为它引入了各种小但有用的功能,例如Base64 API。什么是Base64以及如何使用此API?这篇文章回答了这些问题。
什么是Base64?
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编码和解码
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变种
已经设计了几种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 类及其嵌套 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 。关闭返回的输出流将关闭基础输出流。
你好,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.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_4AAQSkZJRgABAQEASABIAAD4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD
4QM6aHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu-...
image.jpg1.enc 和 image.jpg3.enc 之间的区别在于每个 / 都被替换为 _ 并且每个 + 都被替换为 - 。
您还应该在当前目录中观察 1image.jpg , 2image.jpg 和 3image.jpg 文件。这些文件中的每一个都包含相同的内容 image.jpg 。
结论
Base64 API是Java 8引入的各种小“宝石”之一。如果你必须使用Base64,你会发现这个API非常方便。我鼓励您尝试一下 Base64 ,从本文未涉及的方法开始。
转载于:https://blog.51cto.com/14226273/2370089