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

Android中ObjectInputStream读取对象列表抛出EOFException问题

Android 反序列化临时文件时出现EOFException的解决方案

问题根源

从报错堆栈可以看到,异常发生在CopyOnWriteArrayList的反序列化过程中,说明读取的临时文件数据不完整,导致反序列化到文件末尾时无法读取完整的对象结构。常见诱因包括:

  • 写入流未正确关闭,数据未完全flush到磁盘
  • 读写线程并发执行,读取到正在写入的不完整文件
  • 文件因系统异常(如App被后台杀死)意外截断,导致数据损坏

针对性修复方案

1. 用try-with-resources确保流正确关闭

传统手动关闭流的方式,一旦中间抛出异常,后续的close语句不会执行,导致数据未完全写入。改用Java 7+的try-with-resources自动管理流,确保无论是否异常,流都会被正确关闭并flush:

public static <T> void logListIntoFile(List<T> output, Context context) {
    try (FileOutputStream fos = context.openFileOutput(getCurrentDateMinutes() + ".tmp", Context.MODE_PRIVATE);
         ObjectOutputStream os = new ObjectOutputStream(fos)) {
        os.writeObject(output);
        os.flush(); // 显式触发磁盘写入
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

2. 加同步锁避免读写并发冲突

如果logListIntoFile和fileToLogList可能被多线程调用,必须加锁确保同一时间只有一个线程在操作文件,避免读取到半写入的文件:

// 全局锁对象,确保读写互斥
private static final Object FILE_OP_LOCK = new Object();

public static <T> List<T> fileToLogList(Context context) {
    synchronized (FILE_OP_LOCK) {
        String path = context.getFilesDir().getAbsolutePath();
        List<T> list = new ArrayList<>();
        File directory = new File(path);
        File[] files = directory.listFiles();
        
        if (files == null) return list;
        
        for (File file : files) {
            // 只处理.tmp临时文件,避免误操作其他文件
            if (!file.getName().endsWith(".tmp")) continue;
            // 跳过空文件
            if (file.length() == 0) {
                context.deleteFile(file.getName());
                continue;
            }
            
            try (FileInputStream fis = new FileInputStream(file);
                 ObjectInputStream ois = new ObjectInputStream(fis)) {
                Object obj = ois.readObject();
                if (obj instanceof List) {
                    list.addAll((List<T>) obj);
                }
                // 读取成功后再删除文件
                context.deleteFile(file.getName());
            } catch (EOFException e) {
                // 遇到损坏文件直接删除,避免下次重复报错
                context.deleteFile(file.getName());
                Log.e("Utils", "删除损坏的临时文件: " + file.getName(), e);
            } catch (IOException | ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
        return list;
    }
}

// 写入方法同样加锁
public static <T> void logListIntoFile(List<T> output, Context context) {
    synchronized (FILE_OP_LOCK) {
        try (FileOutputStream fos = context.openFileOutput(getCurrentDateMinutes() + ".tmp", Context.MODE_PRIVATE);
             ObjectOutputStream os = new ObjectOutputStream(fos)) {
            os.writeObject(output);
            os.flush();
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

3. 增加文件完整性校验(可选)

写入时在文件末尾添加一个校验标记,读取时先验证标记,确保文件是完整写入的:

// 写入时追加校验标记
public static <T> void logListIntoFile(List<T> output, Context context) {
    synchronized (FILE_OP_LOCK) {
        try (FileOutputStream fos = context.openFileOutput(getCurrentDateMinutes() + ".tmp", Context.MODE_PRIVATE);
             ObjectOutputStream os = new ObjectOutputStream(fos)) {
            os.writeObject(output);
            os.writeUTF("LOG_FILE_COMPLETE"); // 自定义校验字符串
            os.flush();
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

// 读取时验证标记
public static <T> List<T> fileToLogList(Context context) {
    synchronized (FILE_OP_LOCK) {
        String path = context.getFilesDir().getAbsolutePath();
        List<T> list = new ArrayList<>();
        File directory = new File(path);
        File[] files = directory.listFiles();
        
        if (files == null) return list;
        
        for (File file : files) {
            if (!file.getName().endsWith(".tmp") || file.length() == 0) {
                context.deleteFile(file.getName());
                continue;
            }
            
            try (FileInputStream fis = new FileInputStream(file);
                 ObjectInputStream ois = new ObjectInputStream(fis)) {
                Object obj = ois.readObject();
                String checkFlag = ois.readUTF();
                // 验证校验标记
                if ("LOG_FILE_COMPLETE".equals(checkFlag) && obj instanceof List) {
                    list.addAll((List<T>) obj);
                }
                context.deleteFile(file.getName());
            } catch (EOFException | IOException e) {
                context.deleteFile(file.getName());
                Log.e("Utils", "删除损坏的临时文件: " + file.getName(), e);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
        return list;
    }
}

内容的提问来源于stack exchange,提问作者Daniel Hernández

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 08:37:07