17 FileNameMatcher
2016-08-07 21:20
232 查看
前言
这个工具主要是用于校验给定的字符串, 是否匹配给定的pattern[不是正则, 比正则简单一点], 这个工具 最初我是准备使用在另外的一个工具上面的, 这个工具主要的作用在于将给定的项目打成jar包, 每次 配置一下路径, 然后 run 一下就能够生成给定的包了, 在该工具中FileNameMatcher主要充当的角色在于匹配各级路径然后 最近的时候, 有用到了这个FileNameMatcher, 主要是最近的和一个朋友做的项目中又刚好需要用到这个
就把这个工具分享一下吧, 因为个人的水平有限,必然可能出现一些bug, 所以请大家指出!
问题描述
现在给定一个fileName, 和一个pattern, fileName为需要匹配的文件名, pattern为匹配的规则, pattern中允许使用通配符, ‘?’ 可以表示一个任意字符, ‘*’ 表示多个任意字符然后 现在的程序需要做的事情, 就是 实现具体的逻辑
思路
思路 : 请详见代码吧, 注释 写的算是比较详细难点 : 哈哈哈 难点便是思路
参考代码
/** * file name : FileNameMatcher.java * created at : 5:02:38 PM Nov 29, 2015 * created by 970655147 */ package com.hx.log.util; // FileNameMatcher public class FileNameMatcher { // 所支持的通配符, '?' 可以表示一个任意字符, '*' 表示任意个任意字符[具体的匹配由isGreedy进行约束] // 因为普通场景下面, 没有'?', '*' 的文件名, 因此这里便没有写'?', '*'本身的表示[如果 要写的话, 模拟转义吧][在搜索wildCard的时候, 不能搜索转义的'?', '*', 在匹配的时候, 将'\?', '\*'替换为真实的字符串表示的'?', '*' ] // 一个pattern中各个子pattern的分隔符, 各个子pattern之间的关系为 "短路或" // 这里 并没有约定"短路与"的场景[优先级问题 会导出很多问题], 请自行使用match进行实现吧 public static final Character MATCH_ONE = '?'; public static final Character MATCH_MULTI = '*'; public static final char[] WILDCARDS = new char[]{MATCH_ONE, MATCH_MULTI }; public static final int MATCH_ONE_IDX = 0; public static final int MATCH_MULTI_IDX = 1; public static final String PATTERN_SEP = "\\|"; /** * @Name: match * @Description: 判断给定的fileName是否匹配给定的pattern * @param fileName * @param pattern * @param isGreedy 表示是否采用贪婪匹配的模式[也就是通配符'*'是否贪婪] * @return * @Create at 2016年8月6日 下午3:15:28 by '970655147' */ public static boolean match(String fileName, String pattern, boolean isGreedy) { // add params' verify at 2016.08.28 if (fileName == null && pattern == null) { return true; } if (fileName == null || pattern == null) { return false; } String[] subPatterns = pattern.split(PATTERN_SEP); for(int i=0; i<subPatterns.length; i++) { if(match0(fileName, subPatterns[i], isGreedy) ) { return true; } } return false; } /** * @Name: match * @Description: isGreedy 默认为false * @param fileName * @param pattern * @return * @Create at 2016年8月6日 下午3:16:51 by '970655147' */ public static boolean match(String fileName, String pattern) { return match(fileName, pattern, false); } /** * @Name: equalsIgnoreCase * @Description: 判定str01, str02是否相等 * @param str01 * @param str02 * @return * @Create at 2016年8月6日 下午3:20:51 by '970655147' */ public static boolean equalsInRange(String str01, int start01, String str02, int start02, int len) { // return str01.equalsIgnoreCase(str02); for(int i=0; i<len; i++) { if(str01.charAt(start01+i) != str02.charAt(start02+i) ) { return false; } } return true; } /** * @Name: isEmpty * @Description: 判定给定的字符串是否为空字符串 * @param str * @return * @Create at 2016年8月6日 下午3:21:01 by '970655147' */ public static boolean isEmpty(String str) { return (str == null) || "".equals(str.trim()); } /** * @Name: match0 * @Description: match的核心业务方法 * @param fileName * @param pattern * @param isGreedy * @return * @Create at 2016年8月6日 下午3:28:45 by '970655147' */ private static boolean match0(String fileName, String pattern, boolean isGreedy) { pattern = preparePattern(pattern); int[] nextWildCards = newWildCards(); initWildCards(pattern, nextWildCards); WildCardAndIdx wildCardAndIdx = new WildCardAndIdx(); int fileNameIdx = 0, patternIdx = 0; while(hasNextWildCards(nextWildCards) ) { // 如果没有匹配的pattern的字符串, 直接返回false if((fileNameIdx < 0) || (fileNameIdx >= fileName.length()) ) { return false; } nextWildCard(pattern, nextWildCards, wildCardAndIdx); int len = wildCardAndIdx.pos - patternIdx; if(len != 0) { // 如果fileName不够长, 或者到下一个通配符之间的字符串和pattern不匹配, 直接返回false if(fileNameIdx+len >= fileName.length()) { return false; } if(! equalsInRange(fileName, fileNameIdx, pattern, patternIdx, len) ) { return false; } } // 处理各个通配符的场景 switch(wildCardAndIdx.wildCardIdx) { // 对于'?', 索引增加 : 精确匹配的字符串的长度+1 case MATCH_ONE_IDX : { fileNameIdx += (len + 1); patternIdx += (len + 1); break ; } // 对于'*', 分为贪婪 和非贪婪进行处理 case MATCH_MULTI_IDX : { int curPos = wildCardAndIdx.pos; peekNextWildCard(fileName, nextWildCards, wildCardAndIdx); String strBetweenNextWildCard = null; // 如果下一个字符也为通配符, 则strBetweenNextWildCard为"", isEmpty(strBetweenNextWildCard), 直接返回了true, 以及后面的fileNameIdx的更新造成影响 构成错误 // 所以 需要预处理pattern, 防止类似的情况发生 "**", "*?" if(wildCardAndIdx.pos != -1) { strBetweenNextWildCard = pattern.substring(curPos+1, wildCardAndIdx.pos); } else { strBetweenNextWildCard = pattern.substring(curPos+1); } // 处理pattern的最后一个字符为*的场景 if(isEmpty(strBetweenNextWildCard) ) { return true; } if(isGreedy) { int prevFileNameIdx = fileNameIdx; fileNameIdx = fileName.lastIndexOf(strBetweenNextWildCard); // have no match with 'strBetweenNextWildCard' after fileNameIdx, cut off for next loop // updated at 2016.08.29 if(fileNameIdx <= prevFileNameIdx) { fileNameIdx = -1; } } else { // if have no match with 'strBetweenNextWildCard' after fileNameIdx, 'fileNameIdx' will be '-1' fileNameIdx = fileName.indexOf(strBetweenNextWildCard, fileNameIdx+1); } patternIdx += (len + 1); break ; } // Other ?? can't be there in normal case default : throw new RuntimeException("unsupported wildcard !"); } } return equalsInRange(fileName, fileNameIdx, pattern, patternIdx, Math.min(fileName.length()-fileNameIdx, pattern.length()-patternIdx) ); } // 预处理pattern // 1. 防止类似的情况发生 "**", "*?" private static String preparePattern(String pattern) { StringBuilder sb = new StringBuilder(); for(int i=0, len=pattern.length(); i<len; i++) { char ch = pattern.charAt(i); sb.append(ch); // '*XX' if(ch == MATCH_MULTI) { int nextI = i+1; while((nextI < len) && (Tools.contains(WILDCARDS, pattern.charAt(nextI))) ) { nextI ++; } i = nextI - 1; } } return sb.toString(); } // 创建一个通配符的索引的数组 private static int[] newWildCards() { return new int[WILDCARDS.length]; } // 初始化通配符的位置 private static void initWildCards(String pattern, int[] nextWildCards) { for(int i=0; i<nextWildCards.length; i++) { nextWildCards[i] = pattern.indexOf(WILDCARDS[i] ); } } // 判断是否还有下一个通配符 private static boolean hasNextWildCards(int[] nextWildCards) { for(int i=0; i<nextWildCards.length; i++) { if(nextWildCards[i] >= 0) { return true; } } return false; } // 获取pattern中下一个通配符的位置, 并更新该通配符的下一个位置 private static void nextWildCard(String pattern, int[] nextWildCards, WildCardAndIdx wildCardAndIdx) { peekNextWildCard(pattern, nextWildCards, wildCardAndIdx); nextWildCards[wildCardAndIdx.wildCardIdx] = pattern.indexOf(WILDCARDS[wildCardAndIdx.wildCardIdx], wildCardAndIdx.pos+1); } // 获取pattern中下一个通配符的数据, 放到wildCardAndIdx中 private static void peekNextWildCard(String fileName, int[] nextWildCards, WildCardAndIdx wildCardAndIdx) { int next = getMinIdx(nextWildCards); wildCardAndIdx.wildCardIdx = next; if(next != -1) { wildCardAndIdx.pos = nextWildCards[next]; } else { wildCardAndIdx.pos = -1; } } // 获取pattern中下一个的通配符的索引 private static int getMinIdx(int[] nextWildCards) { int min = Integer.MAX_VALUE, idx = -1; for(int i=0; i<nextWildCards.length; i++) { // '>= 0' for check valid if((nextWildCards[i] >= 0) && (nextWildCards[i] < min) ) { idx = i; min = nextWildCards[i]; } } return idx; } // 通配符的索引, 以及其当前位置 static class WildCardAndIdx { public int wildCardIdx; public int pos; public String toString() { return WILDCARDS[wildCardIdx] + " -> " + pos; } } }
======================= add at 2016.08.28 =======================
今天 无意间在一篇帖子中看到了原来apache的commons.io包下面有一个FilenameUtils[org.apache.commons.io.FilenameUtils]实现了这样的功能[还有很多其他的功能]
然后 对比了一下, 发现对于这样的工具类的相关工具方法, 我又忘记了对于参数的校验逻辑…[加上]
其次简单的比较了一下逻辑[因为没有详细看, 因此 也不敢说一定怎么怎么样]
1 对于”wildCard”的处理逻辑, FilenameUtils是首先根据给定的通配符进行分词[token], 而我这里是在校验过程中去寻找下一个wildCard
2 对于”*”的处理, FilenameUtils是直接让fileName的索引跳到末尾[匹配了fileName剩余的所有字符], 而我这里是尽量匹配”*”之后的部分 和fileName之后的数据[greedy or not]
其他的部分还有待于仔细阅读, 这里就不说了
======================= add at 2016.08.29 =======================
1 发现了一个更新fileNameIdx的问题, 并解决
if(isGreedy) { fileNameIdx = fileName.lastIndexOf(strBetweenNextWildCard); } else { fileNameIdx = fileName.indexOf(strBetweenNextWildCard, fileNameIdx+1); }
||
\ /
if(isGreedy) { int prevFileNameIdx = fileNameIdx; fileNameIdx = fileName.lastIndexOf(strBetweenNextWildCard); // have no match with 'strBetweenNextWildCard' after fileNameIdx, cut off for next loop // updated at 2016.08.29 if(fileNameIdx <= prevFileNameIdx) { fileNameIdx = -1; } } else { // if have no match with 'strBetweenNextWildCard' after fileNameIdx, 'fileNameIdx' will be '-1' fileNameIdx = fileName.indexOf(strBetweenNextWildCard, fileNameIdx+1); }
2 更新了关于一个区间的字符串的比较
public static boolean equalsIgnoreCase(String str01, String str02) { // return str01.equalsIgnoreCase(str02); for(int i=0; i<len; i++) { if(str01.charAt(start01+i) != str02.charAt(start02+i) ) { return false; } } return true; }
||
\ /
public static boolean equalsInRange(String str01, int start01, String str02, int start02, int len) { for(int i=0; i<len; i++) { if(str01.charAt(start01+i) != str02.charAt(start02+i) ) { return false; } } return true; }
======================= add at 2016.09.09 =======================
今天 在看”Files. newDirectoryStream(Path dir, String glob)” 的时候无意间看到了一个glob表达式, 然后之后 了解了一下, 我这里的FileNameMatcher, 也算是glob表达式的功能的一个子集吧
glob表达式参考 : http://blog.csdn.net/chszs/article/details/46482571
效果截图
总结
小工具, 扯扯淡, 正则比这个功能强大多啦 !注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!
相关文章推荐
- 安装Linux Mint 17后要做的配置
- Java菜鸟学习日记17
- linux mint17 中文输入法 配置教程
- 数据库——(17)存储过程
- Linux系统编程(17)——正则表达式进阶
- 17 多校 - 2 - 1011 - Regular polygon (HDU - 6055)
- ruby中__FILE__,$FILENAME,$PROGRAM_NAME,$0等类似变量的含义
- web 学习笔记17-Servlet侦听、过滤器、全站中文乱码
- Android学习笔记17——TCP/IP socket编程
- __FILE__ 与 $_SERVER['SCRIPT_FILENAME']的区别
- JavaScript学习 jquery17 数组,对象操作
- 【每日一题-17】线索化二叉树与单例模式
- nyist oj 17 单调递增最长子序列 (动态规划经典题)
- 17 多校 3 - 1003 - Kanade's sum (HDU 6058)
- Linux运维系统工程师系列---17
- int a[] = {12,13,12,13,19,18,15,12,15,16,17},要求对数组a进行排序,要求时间复杂度为O(N)
- cocos2dx基础篇(17)——列表视图CCTableView
- C# Note17: 使用Ionic.Zip.dll实现解压缩文件
- JavaSe基础XX17——常用对象API-集合框架_3
- Web学习日记17--------继续angularjs实现京东购物车