前言
Concise. Cross‑platform. Fun.
Kotlin 来到 1.8.20 版本, 又给开发者带来了很多更新, 今天关注下标准库中新增的 Base64
相关内容.
原理
Base64编码是一种将二进制数据转换为可打印ASCII字符的编码方式。它使用64个不同的字符(通常是A-Z、a-z、0-9和两个额外的字符 “+” 和 “/”)来表示二进制数据中的6位,从而将8位的二进制数据转换为6位的ASCII字符。
Base64编码的原理如下:
- 将输入的二进制数据分割成每6位一组的数据块。
- 每个6位的数据块表示一个整数值,范围从0到63(因为2^6=64)。
- 将这个整数值映射到Base64字符集中的对应字符。例如,整数值为0时对应字符集中的第一个字符(通常是"A"),整数值为1时对应字符集中的第二个字符(通常是"B"),以此类推。
- 对于不足6位的数据块,可以进行填充,通常使用字符 “=” 来填充。
- 将所有的Base64字符连接在一起,形成最终的Base64编码字符串。
解码时,按照相反的过程进行:
- 将Base64编码的字符串按照字符集映射关系,还原成对应的整数值。
- 将整数值转换为6位二进制数据。
- 将多个6位二进制数据组合成完整的二进制数据。
- 最后,根据具体的编码前的二进制数据类型,将二进制数据转换为相应的数据类型,如字符串、图片、音频等。
Base64编码常用于将二进制数据传输或储存于文本协议中,例如在电子邮件中传输二进制附件、在URL中传递二进制参数、在JSON或XML等文本格式中嵌入二进制数据等场景。
常见三种编码方案
Base64编码通常有几种不同的编码方案,也称为编码表或字符集。这些编码方案包括:
-
标准Base64:也称为RFC 4648中定义的Base64编码,它使用64个字符作为编码表,包括大小写字母(A-Z, a-z)、数字(0-9)以及两个特殊字符(+和/)。此外,还可能包含一个填充字符(=),用于将编码结果的长度补齐为4的倍数。
Table 1: The Base 64 Alphabet 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
-
URL安全Base64:这种编码方案在标准Base64的基础上进行了修改,用于在URL和文件名中传输数据,避免了一些特殊字符在URL中可能引起的问题。URL安全Base64使用大小写字母(A-Z, a-z)、数字(0-9)以及两个特殊字符(-和_)作为编码表,不使用+和/作为编码字符,也不使用=作为填充字符。
Table 2: The "URL and Filename safe" Base 64 Alphabet 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 - (minus) 12 M 29 d 46 u 63 _ 13 N 30 e 47 v (underline) 14 O 31 f 48 w 15 P 32 g 49 x 16 Q 33 h 50 y (pad) =
-
MIME Base64:这种编码方案通常用于在邮件中传输二进制数据。MIME Base64与标准Base64编码方式相同,但可能会在编码结果的行末添加换行符,以适应邮件传输的需求。
这些不同的Base64编码方案在编码表和字符集上有所不同,但基本的编码原理和解码方式都是相同的。在使用Base64编码时,需要根据具体的应用场景和需求选择合适的编码方案。例如,在传输数据时,如果数据将用于URL或文件名中,应使用URL安全Base64编码;如果数据将用于电子邮件中,应使用MIME Base64编码。
Kotlin 手写
根据规则先自行实现 Base64 编解码.
编码
val BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray()
// 以参数为"Hello, World!"为示例
fun base64encode(source: String): String {
val originalBytes = source.toByteArray()
// originalBytes = [72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33]
val binaryStrings = originalBytes.map { byte -> byte.toString(2).padStart(8, '0') }
// binaryStrings = ["01001000", "01100101", "01101100", "01101100", "01101111", "00101100", "00100000", "01010111", "01101111", "01110010", "01101100", "01100100", "00100001"]
val binaryString = binaryStrings.joinToString("")
// binaryString = "0100100001100101011011000110110001101100011011110010110000100000010101110110111101110010011011000110110001100100"
val chunks = binaryString.chunked(6) { chunk -> chunk.padEnd(6, '0') }
// chunks = ["010010", "000110", "010110", "110011", "000110", "110000", "110111", "001011", "000010", "101110", "110111", "001110", "010011", "011000", "110110", "001100"]
val encodedChars = chunks.map { chunk -> chunk.toString().toInt(2) }
.map { index -> BASE64_CHARS[index] }
.toCharArray()
// encodedChars = ['S', 'G', 'V', 's', 'b', 'i', 'B', '0', 'I', 'F', 'c', 'b', 'G', '9', 'v', '4', 'E', 'h', 'd', 'A']
return addPadding(String(encodedChars))
// encodedString = "SGVsbG8sIFdvcmxkIQ=="
}
// 补成4的倍数
fun addPadding(input: String): String {
val remainder = input.length % 4
return if (remainder == 0) {
input
} else {
val padding = "=".repeat(4 - remainder)
input + padding
}
}
解码
fun base64decode(encodedString: String): String {
val encodedChars = encodedString.toCharArray()
val chunks = encodedChars.map { char -> BASE64_CHARS.indexOf(char) }
.filter { index -> index != -1 }
.joinToString("") { index -> index.toString(2).padStart(6, '0') }
.chunked(8).filter { it.length == 8 }
val binaryBytes = chunks.map { chunk ->
chunk.toInt(2).toByte()
}.toByteArray()
return String(binaryBytes)
}
Kotlin 1.8.20 标准库
编码
val foBytes = "fo".map { it.code.toByte() }.toByteArray()
println(Base64.Default.encode(foBytes)) // "Zm8="
println(Base64.encode(foBytes)) // "Zm8="
val foobarBytes = "foobar".map { it.code.toByte() }.toByteArray()
println(Base64.UrlSafe.encode(foobarBytes)) // "Zm9vYmFy"
解码
println(String(Base64.Default.decode("Zm8=")))// foBytes
println(String(Base64.decode("Zm8=")))
println(String(Base64.UrlSafe.decode("Zm9vYmFy"))) // foobarBytes
后记
https://www.rfc-editor.org/rfc/rfc4648
除了 Base64 , 还有 Base32、Base16 等各种 Base-N 编码规则, 原理类似可举一反三自行贯通. 看官方文档的时候, 还顺便帮他们改了个小错误.