优化文件检索与MediaMetadataRetriever性能的方案探讨
哇,处理14000个文件耗时85秒确实有点拖后腿,你已经做的并行流+HashSet存扩展名这两步,已经抓住了性能优化的核心方向!我再给你几个能进一步压榨性能的实用建议:
1. 优化扩展名获取逻辑,避免不必要的字符串操作
你现在用file.toString()来处理路径,其实可以直接用file.getName()获取文件名,这样能减少大量路径字符串的处理开销,而且判断扩展名的逻辑可以更简洁:
// 替换原来的扩展名获取逻辑 String fileName = file.getName(); int dotIndex = fileName.lastIndexOf('.'); if (dotIndex > 0) { // 确保文件名不是以.开头的隐藏文件 String extension = fileName.substring(dotIndex + 1).toLowerCase(); // 统一转小写,兼容.MP3这类大写扩展名 if (extensions.contains(extension)) { // 后续逻辑 } }
另外,把扩展名集合改成小写的固定集合,这样转小写后直接匹配,能避免大小写不兼容的问题(比如用户有.MP3的文件会被漏掉)。
2. 复用MediaMetadataRetriever对象,减少创建销毁开销
每个线程都新建MediaMetadataRetriever其实挺耗资源的,这个类的创建和release()都有一定开销。可以用一个对象池来复用这些对象:
// 简单的对象池示例(可根据需求完善) private static final ObjectPool<MediaMetadataRetriever> mmrPool = new GenericObjectPool<>(new BasePooledObjectFactory<MediaMetadataRetriever>() { @Override public MediaMetadataRetriever create() { return new MediaMetadataRetriever(); } @Override public PooledObject<MediaMetadataRetriever> wrap(MediaMetadataRetriever mmr) { return new DefaultPooledObject<>(mmr); } @Override public void destroyObject(PooledObject<MediaMetadataRetriever> p) { p.getObject().release(); } }); // 使用时从池里取,用完归还 MediaMetadataRetriever mmr = mmrPool.borrowObject(); try { mmr.setDataSource(file.getAbsolutePath()); // 提取MIME类型逻辑 } catch (Exception e) { // 处理异常,比如文件损坏无法读取 } finally { mmrPool.returnObject(mmr); }
这样能避免频繁创建销毁对象带来的性能损耗。
3. 修正线程安全问题,避免隐性BUG
你用了ConcurrentHashMap.newKeySet()来存结果,但代码里还是往audioFiles(ArrayList)里add,这会导致线程安全问题!ArrayList不是线程安全的,并行流里直接add会出现数据丢失或者数组越界。应该把结果存入audioFilesSet,最后再转成ArrayList:
// 并行流处理时 if (mime != null && mime.startsWith("audio")) { audioFilesSet.add(file); } // 处理完后转成ArrayList ArrayList<File> audioFiles = new ArrayList<>(audioFilesSet);
4. 提前过滤非文件类型,减少无效处理
在处理前先判断file.isFile(),直接跳过文件夹,避免对目录做无用的扩展名判断和MIME提取:
Arrays.stream(allFiles) .filter(File::isFile) // 先过滤掉文件夹 .parallel() .forEach(file -> { // 后续逻辑 });
5. 调整并行流的线程池,适配IO密集型场景
默认的ForkJoinPool线程数是CPU核心数,但读取文件元数据属于IO密集型任务,线程大部分时间在等待IO,可以适当增加线程数:
// 自定义线程池,IO密集型任务可设为CPU核心数*2或者更高 ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); executor.submit(() -> { Arrays.stream(allFiles) .filter(File::isFile) .parallel() .forEach(file -> { // 处理逻辑 }); }); executor.shutdown();
6. 尝试用FileDescriptor替代路径字符串
MediaMetadataRetriever.setDataSource()支持传入FileDescriptor,理论上比路径字符串更高效,减少了一次路径解析的过程:
try (FileInputStream fis = new FileInputStream(file)) { mmr.setDataSource(fis.getFD()); // 提取MIME类型 } catch (IOException e) { // 处理异常 }
把这些优化点结合起来,应该能把耗时大幅降低,比如从85秒降到十几秒甚至更短!
内容的提问来源于stack exchange,提问作者qwerty

