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 提出了几种用于编码字节序列的线程安全实例方法 将空引用传递给以下方法之一会导致 java.lang.NullPointerException :
Base64.Decoder 提出了几种解码字节序列的线程安全实例方法。将空引用传递给以下方法之一会导致 NullPointerException :
你好,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_4AAQSkZJRgABAQEASABIAAD_4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD_ 4QM6aHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu-...
image.jpg1.enc 和 image.jpg3.enc 之间的区别在于每个 / 都被替换为 _ 并且每个 + 都被替换为 - 。
您还应该在当前目录中观察 1image.jpg , 2image.jpg 和 3image.jpg 文件。这些文件中的每一个都包含相同的内容 image.jpg 。
结论
Base64 API是Java 8引入的各种小“宝石”之一。如果你必须使用Base64,你会发现这个API非常方便。我鼓励您尝试一下 Base64 ,从本文未涉及的方法开始。