Android扫描文件并统计各类文件数目
来源:互联网 发布:上海java工资一般多少 编辑:程序博客网 时间:2024/05/16 10:03
最近在模仿小米的文件管理器写一个自己的文件管理器,其中有一个功能是全盘扫描文件并显示每类文件的数目。刚开始使用单一线程,扫描速度简直惨不忍睹,换成多线程扫描以后,速度有较明显的提升,便封装了一个工具类,分享出来。
一、遇到的问题
首先描述一下遇到的问题:
1 . Android端全盘扫描文件
2 . 开一个子线程扫描太慢,使用多线程扫描
3 . 统计每一类文件的数目(比如:视频文件,图片文件,音频文件的数目)
二、解决思路
接下来描述一下几个点的解决思路:
1 . 首先目录的存储结构是树状结构,这里就设计到了树的遍历,这里我使用树的层次遍历
,使用非递归方法实现,具体的遍历思路后面会有代码,这里只说明是借助于队列完成树的层次遍历。
2 . 第二个思路便是我们需要传入的参数,这里其实涉及到的是数据的存储结构问题,这里我使用的数据结构如下:
Map<String, Set<String>>
解释一下这个数据结构,map的key表示种类,value是个Set这个Set里面包含该种类的文件的后缀名。如下:
Map<String, Set<String>> CATEGORY_SUFFIX = new HashMap<>();Set<String> set = new HashSet<>();set.add("mp4");set.add("avi");set.add("wmv");set.add("flv");CATEGORY_SUFFIX.put("video", set);set.add("txt");set.add("pdf");set.add("doc");set.add("docx");set.add("xls");set.add("xlsx");CATEGORY_SUFFIX.put("document", set);set = new HashSet<>();set.add("jpg");set.add("jpeg");set.add("png");set.add("bmp");set.add("gif");CATEGORY_SUFFIX.put("picture", set);set = new HashSet<>();set.add("mp3");set.add("ogg");CATEGORY_SUFFIX.put("music", set);set = new HashSet<>();set.add("apk");CATEGORY_SUFFIX.put("apk", set);set = new HashSet<>();set.add("zip");set.add("rar");set.add("7z");CATEGORY_SUFFIX.put("zip", set);
这里的后缀为什么使用Set来存储呢,主要是考虑到后面需要涉及到查找(获得一个文件的后缀,需要在查找属于哪个类别),Set的查找效率比较高
3 . 前面说了目录的遍历需要借助于队列进行层次遍历,又因为是多线程环境下,所以我们选用线程安全的队列ConcurrentLinkedQueue
ConcurrentLinkedQueue<File> mFileConcurrentLinkedQueue;
4 . 还有需要将统计结果进行存储,这里我也选用了线程安全的HashMap
private ConcurrentHashMap<String, Integer> mCountResult;
这个Map的key表示文件种类,value表示该类文件的数目,由于涉及到多线程访问,所以选用了线程安全的ConcurrentHashMap
5 . 多线程问题,这里我选用了固定线程数目的线程池,最大线程数目是CPU核心数
final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
6 . 数据传递问题,由于Android不能在子线程更新UI,所以这里需要传入Handler,将最终统计结果传递到UI线程并显示
三、实战编码
首先放上代码
/** * Created by 尚振鸿 on 17-12-16. 14:26 * mail:szh@codekong.cn * 扫描文件并统计工具类 */public class ScanFileCountUtil { //扫描目录路径 private String mFilePath; //各个分类所对应的文件后缀 private Map<String, Set<String>> mCategorySuffix; //最终的统计结果 private ConcurrentHashMap<String, Integer> mCountResult; //用于存储文件目录便于层次遍历 private ConcurrentLinkedQueue<File> mFileConcurrentLinkedQueue; private Handler mHandler = null; public void scanCountFile() { if (mFilePath == null) { return; } final File file = new File(mFilePath); //非目录或者目录不存在直接返回 if (!file.exists() || file.isFile()) { return; } //初始化每个类别的数目为0 for (String category : mCategorySuffix.keySet()) { //将最后统计结果的key设置为类别 mCountResult.put(category, 0); } //获取到根目录下的文件和文件夹 final File[] files = file.listFiles(new FilenameFilter() { @Override public boolean accept(File file, String s) { //过滤掉隐藏文件 return !file.getName().startsWith("."); } }); //临时存储任务,便于后面全部投递到线程池 List<Runnable> runnableList = new ArrayList<>(); //创建信号量(最多同时有10个线程可以访问) final Semaphore semaphore = new Semaphore(100); for (File f : files) { if (f.isDirectory()) { //把目录添加进队列 mFileConcurrentLinkedQueue.offer(f); //创建的线程的数目是根目录下文件夹的数目 Runnable runnable = new Runnable() { @Override public void run() { countFile(); } }; runnableList.add(runnable); } else { //找到该文件所属的类别 for (Map.Entry<String, Set<String>> entry : mCategorySuffix.entrySet()) { //获取文件后缀 String suffix = f.getName().substring(f.getName().indexOf(".") + 1).toLowerCase(); //找到了 if (entry.getValue().contains(suffix)) { mCountResult.put(entry.getKey(), mCountResult.get(entry.getKey()) + 1); break; } } } } //固定数目线程池(最大线程数目为cpu核心数,多余线程放在等待队列中) final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); for (Runnable runnable : runnableList) { executorService.submit(runnable); } //不允许再添加线程 executorService.shutdown(); //等待线程池中的所有线程运行完成 while (true) { if (executorService.isTerminated()) { break; } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } //传递统计数据给UI界面 Message msg = Message.obtain(); msg.obj = mCountResult; mHandler.sendMessage(msg); } /** * 统计各类型文件数目 */ private void countFile() { //对目录进行层次遍历 while (!mFileConcurrentLinkedQueue.isEmpty()) { //队头出队列 final File tmpFile = mFileConcurrentLinkedQueue.poll(); final File[] fileArray = tmpFile.listFiles(new FilenameFilter() { @Override public boolean accept(File file, String s) { //过滤掉隐藏文件 return !file.getName().startsWith("."); } }); for (File f : fileArray) { if (f.isDirectory()) { //把目录添加进队列 mFileConcurrentLinkedQueue.offer(f); } else { //找到该文件所属的类别 for (Map.Entry<String, Set<String>> entry : mCategorySuffix.entrySet()) { //获取文件后缀 String suffix = f.getName().substring(f.getName().indexOf(".") + 1).toLowerCase(); //找到了 if (entry.getValue().contains(suffix)) { mCountResult.put(entry.getKey(), mCountResult.get(entry.getKey()) + 1); //跳出循环,不再查找 break; } } } } } } public static class Builder { private Handler mHandler; private String mFilePath; //各个分类所对应的文件后缀 private Map<String, Set<String>> mCategorySuffix; public Builder(Handler handler) { this.mHandler = handler; } public Builder setFilePath(String filePath) { this.mFilePath = filePath; return this; } public Builder setCategorySuffix(Map<String, Set<String>> categorySuffix) { this.mCategorySuffix = categorySuffix; return this; } private void applyConfig(ScanFileCountUtil scanFileCountUtil) { scanFileCountUtil.mFilePath = mFilePath; scanFileCountUtil.mCategorySuffix = mCategorySuffix; scanFileCountUtil.mHandler = mHandler; scanFileCountUtil.mCountResult = new ConcurrentHashMap<String, Integer>(mCategorySuffix.size()); scanFileCountUtil.mFileConcurrentLinkedQueue = new ConcurrentLinkedQueue<>(); } public ScanFileCountUtil create() { ScanFileCountUtil scanFileCountUtil = new ScanFileCountUtil(); applyConfig(scanFileCountUtil); return scanFileCountUtil; } }}
上面代码中关键的点都有注释或者是前面已经讲到了,下面说几点补充:
1 . 必须要等所有线程运行结束才能向UI线程发送消息,这里使用了轮询的方式
while (true) { if (executorService.isTerminated()) { break; } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }}
2 . 由于上面的轮询会阻塞调用线程,所以调用应该放在子线程中
3 . 上面工具类实例的创建使用到了建造者模式,不懂的可以看我的另一篇博客
http://blog.csdn.net/bingjianit/article/details/53607856
4 . 上面我创建的线程的数目是根目录下文件夹的数目,大家可以根据自己的需要调整
四、方便调用
下面简单说一下如何调用上面的代码
private Handler mHandler = new Handler(Looper.getMainLooper()){ @Override public void handleMessage(Message msg) { //接收结果 Map<String, Integer> countRes = (Map<String, Integer>) msg.obj; //后续显示处理 }};
/** * 扫描文件 */private void scanFile(){ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ return; } final String path = Environment.getExternalStorageDirectory().getAbsolutePath(); final Map<String, Set<String>> CATEGORY_SUFFIX = new HashMap<>(FILE_CATEGORY_ICON.length); Set<String> set = new HashSet<>(); set.add("mp4"); set.add("avi"); set.add("wmv"); set.add("flv"); CATEGORY_SUFFIX.put("video", set); set.add("txt"); set.add("pdf"); set.add("doc"); set.add("docx"); set.add("xls"); set.add("xlsx"); CATEGORY_SUFFIX.put("document", set); set = new HashSet<>(); set.add("jpg"); set.add("jpeg"); set.add("png"); set.add("bmp"); set.add("gif"); CATEGORY_SUFFIX.put("picture", set); set = new HashSet<>(); set.add("mp3"); set.add("ogg"); CATEGORY_SUFFIX.put("music", set); set = new HashSet<>(); set.add("apk"); CATEGORY_SUFFIX.put("apk", set); set = new HashSet<>(); set.add("zip"); set.add("rar"); set.add("7z"); CATEGORY_SUFFIX.put("zip", set); //单一线程线程池 ExecutorService singleExecutorService = Executors.newSingleThreadExecutor(); singleExecutorService.submit(new Runnable() { @Override public void run() { //构建对象 ScanFileCountUtil scanFileCountUtil = new ScanFileCountUtil .Builder(mHandler) .setFilePath(path) .setCategorySuffix(CATEGORY_SUFFIX) .create(); scanFileCountUtil.scanCountFile(); } });}
五、后记
刚开始我是采用单线程扫描,扫描时间差不多是3分钟,经过使用多线程以后,扫描时间缩短到30-40秒。对了,上面的程序要想在Android中顺利运行还需要添加访问SD卡的权限和注意Android6.0的动态权限申请。
如果觉得不错,可以关注我,也可以去GitHub看看我的文件管理器,正在不断完善中,地址:
https://github.com/codekongs/FileExplorer/
- Android扫描文件并统计各类文件数目
- Linux统计文件数目
- mysql 统计一个表中各类别的数目,并保存
- Linux下如何统计文件数目
- Linux下统计某个目录文件数目
- python 统计文件中单词数目
- linux shell 统计不同类型文件数目
- Linux 统计当前文件夹、文件的数目
- Linux统计文件夹下的文件数目
- 【python】统计文件中的字符串数目
- Python实现“各类型文件统计”功能
- 各类常用文件的所有格式统计
- 练习2-3:编一个程序用来打开文件并统计文件中以空格隔开的单词数目
- VBS解压zip文件并且递归统计文件数目
- Android扫描SDcard文件
- Android文件扫描
- Android MediaScannerConnection扫描文件
- Android中的文件扫描
- Linux---(1)常用命令
- HDOJ 1000 A+B Problem
- GDB
- 彻底搞定C指针
- 简述 GFS & Chubby & Bigtable & Mapreduce
- Android扫描文件并统计各类文件数目
- C++ 经常用到的基本或复合字符类型之一CString
- 程序设计与算法(一)C语言程序设计CAP 第三周编程作业
- Radmax,真正的五轴激光切割高效编程
- AutoCAD .Net 创建圆弧Arc
- 客户关系管理系统5
- android端富文本编辑器HRichEditor
- Spring Cloud构建微服务架构(二)服务消费者(rest+ribbon)
- CentOS7使用docker安装Mysql