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
相关产品推荐
相关产品推荐

