You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将3GB大文件转为字节数组遇OOM问题,求最优解决方案

解决大文件处理的OutOfMemoryError问题

你的核心问题在于试图将3GB的完整文件加载到内存中(不管是MappedByteBuffer还是byte[]数组),这显然超出了Android应用的内存限制,必然会触发OOM。另外MappedByteBuffer.array()抛出ReadOnlyBufferException是因为只读映射的ByteBuffer不允许获取底层可修改的数组,这也是API的预期行为。

最优方案完全不需要把整个文件转成字节数组,而是采用流式分块处理:分块读取文件、分块加密、分块上传,每一步只处理一小段数据,全程不占用大量内存。

步骤1:移除全文件内存加载的逻辑

首先抛弃原来用MappedByteBuffer加载整个文件的代码,改用FileInputStream分块读取文件内容,每读取一块就加密一块,然后直接传递给上传流,不需要把所有加密后的数据存成byte[]。

步骤2:修改ProgressRequestBody,支持流式处理

重新实现ProgressRequestBody,让它直接读取原文件、分块加密并上传,全程流式操作:

import android.os.Looper;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.BufferedSink;

public class ProgressRequestBody extends RequestBody {
    private static final int DEFAULT_BUFFER_SIZE = 4096; // 4KB缓冲区,和磁盘IO块大小匹配更高效
    private final File mFile;
    private final String mFileType;
    private final int mItemIndex;
    private final UploadCallbacks mListener;
    // 假设你有一个加密工具类,负责分块加密
    private final EncryptionUtil mEncryptionUtil;

    public ProgressRequestBody(String fileType, File file, int itemIndex, UploadCallbacks listener, EncryptionUtil encryptionUtil) {
        this.mFile = file;
        this.mFileType = fileType;
        this.mItemIndex = itemIndex;
        this.mListener = listener;
        this.mEncryptionUtil = encryptionUtil;
    }

    @Override
    public MediaType contentType() {
        try {
            return MediaType.parse(mFileType);
        } catch (Exception ex) {
            return MediaType.parse("application/octet-stream");
        }
    }

    @Override
    public long contentLength() throws IOException {
        // 如果加密会改变文件长度,这里需要提前计算加密后的总长度;对称加密(如AES)通常会增加少量长度
        return mFile.length();
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        long fileLength = mFile.length();
        byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
        long uploaded = 0;
        FileInputStream fis = new FileInputStream(mFile);
        android.os.Handler handler = new android.os.Handler(Looper.getMainLooper());

        try {
            int read;
            while ((read = fis.read(buffer)) != -1) {
                // 分块加密当前读取的内容
                byte[] encryptedBlock = mEncryptionUtil.encrypt(buffer, 0, read);
                // 写入加密后的块到上传流
                sink.write(encryptedBlock);
                // 更新已上传长度(注意:如果加密后长度变化,这里要加encryptedBlock.length而非read)
                uploaded += encryptedBlock.length;
                // 在UI线程更新进度
                handler.post(new ProgressUpdater(uploaded, fileLength));
            }
        } finally {
            fis.close();
        }
    }

    public interface UploadCallbacks {
        void onProgressUpdate(int itemIndex, int percentage);
    }

    private class ProgressUpdater implements Runnable {
        private final long mUploaded;
        private final long mTotal;

        public ProgressUpdater(long uploaded, long total) {
            mUploaded = uploaded;
            mTotal = total;
        }

        @Override
        public void run() {
            if (mListener != null) {
                int percentage = (int) (100 * mUploaded / mTotal);
                // 确保百分比不超过100
                mListener.onProgressUpdate(mItemIndex, Math.min(percentage, 100));
            }
        }
    }
}

步骤3:修改上传请求代码

现在不需要提前生成整个文件的byte[],直接传入文件和加密工具即可:

File sourceLocation = new File("你的文件路径");
// 初始化你的加密工具实例
EncryptionUtil encryptionUtil = new EncryptionUtil();
ProgressRequestBody requestFile = new ProgressRequestBody(fileType, sourceLocation, itemIndex, this, encryptionUtil);
MultipartBody.Part body = MultipartBody.Part.createFormData("file", sourceLocation.getName(), requestFile);
Call<String> mediaCall = getRetrofit().addMedia("SOME STRING DATA", body);

关键注意事项

  • 加密块对齐:如果你的加密算法需要固定块大小(比如AES的128位块),要确保分块读取时对齐加密块大小,或者处理最后一块的填充问题,避免加密失败。
  • 进度准确性:如果加密后文件长度和原文件不同,contentLength()需要返回加密后的总长度,否则进度计算会有偏差。
  • 缓冲区大小:选择4KB~8KB的缓冲区大小是最优的,和磁盘IO的块大小匹配,能减少IO次数同时避免占用过多内存。

这种流式处理方式的内存占用仅取决于缓冲区大小(几KB),完全不会因为文件大小而触发OOM,是处理大文件的标准方案。

内容的提问来源于stack exchange,提问作者Ashish Pandya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:01:48