文章目录
- Base64:让二进制数据变成纯文本
- 1. 引入:为什么需要Base64?
- 1.1 一个场景
- 1.2 Base64的解决方案
- 2. Base64编码原理
- 2.1 核心思想
- 2.2 如果数据不是3的倍数怎么办?
- 2.3 解码过程
- 3. Java中的Base64使用
- 3.1 标准库(Java 8+)
- 3.2 编码图片为Base64
- Base64 与图片互转 —— 来自聊天室的例子
- 1. 编码:图片文件 → Base64 字符串
- 关键点
- 2. 解码:Base64 字符串 → 图片文件
- 关键点
- 注意事项总结
- 4. 与其他编码的区别
- 5. 优劣势总结
- 6. 注意事项
Base64:让二进制数据变成纯文本
1. 引入:为什么需要Base64?
1.1 一个场景
你写了一段程序,要把一张图片从A设备传给B设备。图片是二进制数据,里面可能包含不可见的控制字符(null,换行,回车),很多文本协议(如HTTP头部、SMTP)把这些特定值当作分隔符或结束标志。如果你发送的图片数据里恰好有一个字节是0000 1010,接收方的文本解析器会误以为“这条命令结束了”,从而截断后续数据,导致文件损坏或协议报错。
核心问题:二进制数据(0和1)没法直接在文本协议里传输。
1.2 Base64的解决方案
Base64可以把二进制数据转换成纯文本字符(A-Z、a-z、0-9、+、/、=),这样就能安全地在JSON、XML、URL、邮件等文本场景中传输了。
| 场景 | 用途 |
|---|---|
| 邮件附件 | 将图片/文件编码后传输 |
| 图片转JSON | 拍照后以字符串形式传给服务器 |
| URL安全传输 | 避免特殊字符被转义 |
| 加密数据存储 | 加密后的字节转成文本存储 |
2. Base64编码原理
2.1 核心思想
Base64将3个字节(24位)的数据,转换成4个字符(每个字符6位),一共64种字符。
原始数据: 10111001 01010101 11001100 ↓ 拆分 6位一组: 101110 010101 011100 1100 ↓ 补0 101110 010101 011100 001100 ↓ 查表 5 21 28 12 ↓ 对应字符 5 V c M64个字符表:
2.2 如果数据不是3的倍数怎么办?
Base64使用填充符=来处理:
1个字节(8位)→ 2个Base64字符 + 2个"=" 2个字节(16位)→ 3个Base64字符 + 1个"=" 3个字节(24位)→ 4个Base64字符 + 0个"="例子:
"Man" → 3字节 → "TWFu"(无填充) "M" → 1字节 → "TQ=="(两个=) "Ma" → 2字节 → "TWE="(一个=)2.3 解码过程
解码就是编码的逆过程:
Base64字符 → 查表得到6位值 → 拼接成24位 → 拆分成3个字节3. Java中的Base64使用
3.1 标准库(Java 8+)
Java 8引入了java.util.Base64,使用非常简单:
importjava.util.Base64;publicclassBase64Example{publicstaticvoidmain(String[]args){Stringtext="Hello, 你好!";// 编码Base64.Encoderencoder=Base64.getEncoder();Stringencoded=encoder.encodeToString(text.getBytes(StandardCharsets.UTF_8));System.out.println("编码后:"+encoded);// 输出:SGVsbG8sIOS9oOWlv++8gQ==// 解码Base64.Decoderdecoder=Base64.getDecoder();byte[]decodedBytes=decoder.decode(encoded);Stringdecoded=newString(decodedBytes,StandardCharsets.UTF_8);System.out.println("解码后:"+decoded);// 输出:Hello, 你好!}}3.2 编码图片为Base64
importjava.io.FileInputStream;importjava.io.IOException;importjava.util.Base64;publicclassImageToBase64{publicstaticStringencodeImage(StringimagePath)throwsIOException{FileInputStreamfis=newFileInputStream(imagePath);byte[]imageBytes=fis.readAllBytes();fis.close();returnBase64.getEncoder().encodeToString(imageBytes);}publicstaticvoiddecodeBase64ToFile(Stringbase64String,StringoutputPath)throwsIOException{// 解码Base64字符串为字节数组byte[]imageBytes=Base64.getDecoder().decode(base64String);// 写入文件try(FileOutputStreamfos=newFileOutputStream(outputPath)){fos.write(imageBytes);fos.close();}}}Base64 与图片互转 —— 来自聊天室的例子
头像图片需要通过网络传输(Socket),因此涉及图片 → Base64 → JSON → Socket → Base64 → 图片的完整链路。以下是从项目中提取的三个核心方法。
1. 编码:图片文件 → Base64 字符串
这个方法把一个本地图片文件压缩后编码为 Base64 字符串,适合网络传输。先压缩再编码,避免 JSON 过大。
importandroid.graphics.Bitmap;importandroid.graphics.BitmapFactory;importandroid.util.Base64;importjava.io.ByteArrayOutputStream;importjava.io.File;/** * 图片文件 → 压缩到 256px → JPEG 质量 70 → Base64 字符串 * @return Base64 编码字符串,失败返回 null */privateStringfileToBase64(Filefile){try{// 第一步:只读尺寸,不加载整个图片到内存BitmapFactory.Optionsopts=newBitmapFactory.Options();opts.inJustDecodeBounds=true;BitmapFactory.decodeFile(file.getAbsolutePath(),opts);// 第二步:计算缩放比例,最大 256pxintmaxSize=256;intscale=1;while(opts.outWidth/scale>maxSize||opts.outHeight/scale>maxSize){scale*=2;}// 第三步:按缩放比例真正解码opts=newBitmapFactory.Options();opts.inSampleSize=scale;Bitmapbitmap=BitmapFactory.decodeFile(file.getAbsolutePath(),opts);if(bitmap==null)returnnull;// 第四步:压缩为 JPEG,质量 70ByteArrayOutputStreambaos=newByteArrayOutputStream();bitmap.compress(Bitmap.CompressFormat.JPEG,70,baos);bitmap.recycle();// 及时释放 Bitmap 内存// 第五步:Base64 编码returnBase64.encodeToString(baos.toByteArray(),Base64.NO_WRAP);}catch(Exceptione){e.printStackTrace();returnnull;}}关键点
| 步骤 | 说明 |
|---|---|
inJustDecodeBounds = true | 不加载像素数据,只读取宽高,内存消耗为 0 |
inSampleSize | 必须是 2 的幂(1, 2, 4, 8…),while循环自动计算 |
| 256px 上限 | 头像只需 40dp 显示,256px 足够清晰,体积约 5~15KB |
| JPEG 质量 70 | 肉眼几乎看不出区别,比 100 小 60%+ |
bitmap.recycle() | Android 上 Bitmap 占 native 内存,用完必须回收 |
Base64.NO_WRAP | 不加换行符,否则 JSON 中会断行导致解析失败 |
2. 解码:Base64 字符串 → 图片文件
接收方把 Base64 字符串还原为图片文件,保存到本地。之后显示头像时直接用文件路径加载。
importandroid.util.Base64;importjava.io.File;importjava.io.FileOutputStream;/** * Base64 字符串 → 图片文件 * * @param userName 用户名,用作文件名 * @param base64Data Base64 编码的图片数据 * @return 保存后的文件绝对路径,失败返回 null */privateStringsaveAvatarFromBase64(StringuserName,Stringbase64Data){try{// 第一步:Base64 解码为字节数组byte[]bytes=Base64.decode(base64Data,Base64.NO_WRAP);// 第二步:确保目录存在FileavatarDir=newFile(getFilesDir(),"avatars");if(!avatarDir.exists()){avatarDir.mkdirs();}// 第三步:写入文件(会覆盖同名旧文件)FiledestFile=newFile(avatarDir,userName+".jpg");FileOutputStreamfos=newFileOutputStream(destFile);fos.write(bytes);fos.flush();fos.close();returndestFile.getAbsolutePath();}catch(Exceptione){e.printStackTrace();returnnull;}}关键点
| 步骤 | 说明 |
|---|---|
Base64.NO_WRAP | 必须与编码时一致,否则解码失败 |
mkdirs() | 多级目录一次性创建,父目录不存在也能建 |
FileOutputStream(File) | 默认覆盖写入,同名旧文件自动替换 |
flush() + close() | 确保数据完全落盘 |
注意事项总结
- 编码和解码必须用同一个 flag:项目全程用
Base64.NO_WRAP,编码加了换行符(DEFAULT)会导致解码失败或 JSON 解析出错 - 先压缩再编码:原图可能是 4000×3000 几 MB,直接 Base64 会让 JSON 达到 5MB+,Socket 一行的 buffer 装不下。256px + JPEG Q70 压缩后约 5~15KB
inSampleSize必须是 2 的幂:BitmapFactory的硬性要求,如果不是 2 的幂会向下取整- 用完 Bitmap 记得
recycle():在 Android 上 Bitmap 不归 JVM GC 管,不及时回收会导致 OOM
4. 与其他编码的区别
| 编码 | 输出字符集 | 用途 |
|---|---|---|
| Base64 | A-Za-z0-9+/= | 二进制→文本 |
| Hex | 0-9A-F | 字节转十六进制(长度翻倍) |
| URL编码 | %XX格式 | 特殊字符转义 |
| Base32 | A-Z2-7= | 更易读但更慢 |
5. 优劣势总结
| 优点 | 缺点 |
|---|---|
| 将二进制转为纯文本,传输安全 | 数据膨胀约33%(3字节→4字符) |
| 无需特殊依赖,各语言都支持 | 不是加密,只是编码格式 |
| 适合嵌入JSON/XML/URL | 较大的文件编码后可能超长 |
6. 注意事项
- Base64不是加密:只是编码,任何拿到编码串的人都能解出来
- 大文件不建议转Base64:会增加1/3的大小,网络传输时更慢
- Android Studio插件:可以在窗口右侧的"Base64"插件快速编解码测试