Kotlin中实现分块缓冲将二进制InputStream转为Base64字符串(解决内存溢出问题)
Kotlin中实现分块缓冲将二进制InputStream转为Base64字符串(解决内存溢出问题)
嗨,我完全懂你现在的困扰——一次性把大文件(比如超20MB)的全部字节读进内存再转Base64,内存直接就爆了,毕竟Base64编码后还会比原文件大30%左右,俩大对象占着内存,不崩才怪。你想的分块处理思路绝对是对的,Base64确实得按它的编码规则分块处理,不能随便切了就拼,我来给你一个能直接用的Kotlin实现,顺便帮你理清楚之前尝试的问题出在哪。
先说说你原代码的核心问题
你之前的stream?.readBytes()会把整个InputStream的内容一次性加载到内存里,生成一个超大的字节数组,紧接着又生成一个更大的Base64字符串,俩大对象同时占着内存——20MB的原文件转完就是26MB左右的字符串,加起来快50MB,直接触发OOM太正常了。
正确的分块实现思路
Base64是每3个字节编码成4个字符,所以我们的缓冲区大小最好设成3的倍数(比如24576=8192*3,这个大小既符合编码规则,又能保证IO效率),这样分块的时候不会出现中间截断的问题。另外,绝对不要手动处理padding,Java/Kotlin的Base64流式编码器会自动处理最后的等号,你之前手动加padding的逻辑是错的,这就是你之前编码结果无效的原因。
修正后的完整可用代码
private fun pushAttachmentToJS(uri: Uri) { try { contentResolver.openInputStream(uri)?.use { inputStream -> val stringWriter = StringWriter() // 包装成Base64编码输出流,withoutLineSeparator对应原代码的NO_WRAP(不换行) Base64.getEncoder().withoutLineSeparator().wrap(OutputStreamWriter(stringWriter)).use { base64Out -> val buffer = ByteArray(24576) // 3的倍数,高效缓冲区大小 var bytesRead: Int // 分块读取输入流,写到Base64编码输出流 while (inputStream.read(buffer).also { bytesRead = it } != -1) { base64Out.write(buffer, 0, bytesRead) } } val fB64 = stringWriter.toString() // 这里继续处理你的fB64字符串 } } catch (e: IOException) { e.printStackTrace() } }
关键部分解释
- 流式编码:用
Base64.getEncoder().wrap()把普通的字符输出流包装成Base64编码流,这样我们只需要分块把输入流的字节写到这个包装流里,它会自动帮我们做Base64编码,完全不用手动处理编码逻辑。 - 缓冲区选择:24576是Android推荐的默认缓冲区大小(8192)的3倍,完美契合Base64“3字节转4字符”的规则,避免分块时的中间截断问题,IO效率也最高。
- 内存优化:整个过程中,内存里只会存在24KB的缓冲区,以及逐步构建的Base64字符串,不会同时持有原文件的全部字节,内存压力直接降了几个量级,处理40MB甚至更大的文件都没问题。
- 和原代码兼容:
withoutLineSeparator()对应你原代码里的Base64.NO_WRAP,保证编码后的字符串不会自动添加换行符,和你原来的输出格式完全一致;如果你的场景不需要Base64的padding(即原代码用Base64.NO_PADDING),只需要在编码器后面加.withoutPadding()即可。
为啥你之前的尝试编码结果不对?
你之前手动计算padding的方式是错误的:Base64的padding规则是最后剩余1个字节时加2个等号,剩余2个字节时加1个等号,而不是按“模4”来计算。流式编码器会自动判断最后剩余的字节数并添加正确的padding,完全不需要手动干预,这就是你之前编码结果无效的核心原因。
备注:内容来源于stack exchange,提问作者Volker
相关产品推荐
相关产品推荐

