Java 8会由于将lambdas,流,新的日期/时间模型和Nashorn JavaScript引擎引入Java而被记住。有些人还会记得Java 8,由于它引入了各类小但有用的功能,例如Base64 API。什么是Base64以及如何使用此API?这篇文章回答了这些问题。html
在RFC 1421中首次描述了Base64(但没有命名):Internet电子邮件的隐私加强:第一部分:消息加密和认证过程。后来,它在RFC 2045中正式呈现为Base64 :多用途Internet邮件扩展(MIME)第一部分:Internet消息体的格式,随后在RFC 4648:Base16,Base32和Base64数据编码中从新访问。java
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位二进制序列。这是字母表:bash
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编码的文本填充到整数大小,以下所述。app
编码算法接收8位字节的输入流。假定该流首先以最高有效位排序:第一位是第一个字节中的高位,第八位是该字节中的低位,依此类推。ui
从左到右,这些字节被组织成24位组。每组被视为四个链接的6位组。每一个6位组索引为64个可打印字符的数组; 输出结果字符。this
当在编码数据的末尾有少于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描述了一种称为
的Base64变体。此变体使用RFC 4648和RFC 2045的表1中所示的Base64字母表(并在本文前面所示)进行编码和解码。编码器将编码的输出流视为一行; 没有输出行分隔符。解码器拒绝包含Base64字母表以外的字符的编码。请注意,能够覆盖这些和其余规定。
RFC 2045描述了一种称为
的Base64变体。此变体使用RFC 2045的表1中提供的Base64字母表进行编码和解码。编码的输出流被组织成不超过76个字符的行; 每行(最后一行除外)经过行分隔符与下一行分隔。解码期间将忽略Base64字母表中未找到的全部行分隔符或其余字符。
RFC 4648描述了一种称为
的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展现了源代码。
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显示了应用程序的源代码。
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
,从本文未涉及的方法开始。
原文连接:www.javaworld.com/article/324…
更多文章欢迎访问: http://www.apexyun.com
公众号:银河系1号
联系邮箱:public@space-explore.com
(未经赞成,请勿转载)