小工具大智慧----文件名批量转换工具
转换需求
1.支持大写转小写和小写转大写,前缀和后缀的添加或修改,字符串修改
2.转换实例
2.1大写转小写(可以处理中文中夹杂数字的情况)
第一讲 -> 第1讲
第十课 -> 第10课
十二章 -> 12章
三十 -> 30
一百四十二 -> 42
一百零二点二五 -> 102.25
2.2 小写转大写 (相对上面而言)
1 -> 一
10 -> 十
12 -> 十二
30 -> 三十
142 -> 一百四十二
102.25 -> 一百零二点二五
(主要十位数在我们习惯说十几而不是一十几,小数点后数字一一读出;上面大小写数字都可以使用字符串来处理,并且出现这些数字的时候,可能会出现其他的字符串,例如:第一讲)
2.3 前缀转换
xxx.rar -> [李顺利]xxx.rar(rule:"","[李顺利]")
[lishunli]xxx.rar -> [李顺利]xxx.rar(rule:"[lishunli]","[李顺利]")
[lishunli]xxx.rar -> xxx.rar(rule:"[lishunli]","")
2.4后缀转换
xxx.rar -> xxx[李顺利].rar(rule:"","[李顺利]")
xxx[lishunli].rar -> xxx[李顺利].rar(rule:"[lishunli]","[李顺利]")
xxx[lishunli].rar -> xxx.rar(rule:"[lishunli]","")
2.5字符串修改
123.rar ->李顺利.rar(rule:"123","李顺利")
?
开发环境
Eclipse + JDK 1.6 + Window Builder
小工具的优缺点亮点
0.功能就是亮点,找了好久,真的没有相应的软件或者工具可以使用,所以就自己写了个
1.使用了工厂和策略模式
2.批量转换是对路径所有的文件夹和文件,支持迭代递归功能(也可以选择不转换文件夹)
3.其它(期待您的发现,譬如说可以批量修改文件的后缀,显示目录里面的文件名字啊...)
缺点
不支持正则表达式的替换(实际上如果你有兴趣的话也可以在这个的基础上进心加工,代码开源,可以下载或者 checkout),正则这块是我的痛啊,太不熟悉了,有时间,好好 follow 一下。
是否支持单文件操作,目前是批处理文件夹及其子文件夹内的文件?
是否需要保存上一次的配置参数?
是否支持正则表达式的匹配和修改?
部分代码
Rule的工厂类
package org.usc.file.operater.rules;/*** 转换工厂** @author <a href="http://www.blogjava.net/lishunli/" target="_blank">ShunLi</a>* @notes Created on 2010-12-11<br>* Revision of last commit:$Revision: 767 $<br>* Author of last commit:$Author: nhjsjmz@gmail.com $<br>* Date of last commit:$Date: 2010-12-12 00:17:34 +0800 (周日, 12 十二月 2010) $<br>* <p>*/public class ConvertFactory {public static ConvertRule createConvertRule(Rule rule) {ConvertRule cr = new SmallToBigConvertRule(); // Defaultif (Rule.SmallToBig == rule) {cr = new SmallToBigConvertRule();} else if (Rule.BigToSmall == rule) {cr = new BigToSmallConvertRule();// cr = new SimpleBigToSmallConvertRule(); // simple 支持百一下的文件,快速一点} else if (Rule.Prefix == rule) {cr = new PrefixConvertRule();} else if (Rule.Suffix == rule) {cr = new SuffixConvertRule();} else if (Rule.Replace == rule) {cr = new ReplaceConvertRule();}return cr;}}
文件操作类
package org.usc.file.operater.utils;import java.io.File;import java.io.IOException;import org.apache.commons.io.FileUtils;import org.usc.file.operater.rules.ConvertFactory;import org.usc.file.operater.rules.ConvertRule;import org.usc.file.operater.rules.Rule;/*** 文件操作工具** @author <a href="http://www.blogjava.net/lishunli/" target="_blank">ShunLi</a>* @notes Created on 2010-12-11<br>* Revision of last commit:$Revision: 829 $<br>* Author of last commit:$Author: nhjsjmz@gmail.com $<br>* Date of last commit:$Date: 2011-04-17 16:58:13 +0800 (周日, 17 四月 2011) $<br>* <p>*/public class FileOperaterTool {private ConvertRule convertRule;private StatisticsInfo statisticsInfo;public FileOperaterTool() {}public FileOperaterTool(Rule rule) {this.init();this.convertRule = ConvertFactory.createConvertRule(rule);}public void init(){this.statisticsInfo = new StatisticsInfo();}public StringBuffer getStatistics(){StringBuffer sb= new StringBuffer();sb.append(this.statisticsInfo.toString());return sb;}/*** 修改path路径下所有的文件名** @param path*/public String fileRename(String path, Boolean isConvertFolder) {StringBuffer info = new StringBuffer();File file = new File(path);String[] tempList = file.list();if (tempList != null) {File temp = null;if (tempList.length == 0) {info.append("\"" + path + "\"路径下没有文件" + "\n");}for (int i = 0; i < tempList.length; i++) {if (path.endsWith(File.separator)) {temp = new File(path + tempList[i]);} else {temp = new File(path + File.separator + tempList[i]);}if (temp.isFile()) {this.statisticsInfo.addSumFileNum();info.append(fileRename(temp) + "\n");}if (temp.isDirectory()) {this.statisticsInfo.addSumFolderNum();String folderName = path + "\\" + tempList[i];info.append(fileRename(folderName, isConvertFolder));if (isConvertFolder) {info.append(folderRename(folderName) + "\n\n");}}}} else {info.append("\"" + path + "\"路径不存在" + "\n");}return info.toString();}/*** 修改path路径下所有的文件名** @param path*/public String fileRename(String path, String fix, String newFix, Boolean isConvertFolder) {StringBuffer info = new StringBuffer();File file = new File(path);String[] tempList = file.list();if (tempList != null) {File temp = null;if (tempList.length == 0) {info.append("\"" + path + "\"路径下没有文件" + "\n");}for (int i = 0; i < tempList.length; i++) {if (path.endsWith(File.separator)) {temp = new File(path + tempList[i]);} else {temp = new File(path + File.separator + tempList[i]);}if (temp.isFile()) {this.statisticsInfo.addSumFileNum();info.append(fileRename(temp, fix, newFix) + "\n");}if (temp.isDirectory()) {this.statisticsInfo.addSumFolderNum();String folderName = path + "\\" + tempList[i];info.append(fileRename(folderName, fix, newFix, isConvertFolder));if (isConvertFolder) {info.append(folderRename(folderName, fix, newFix, true) + "\n\n");}}}} else {info.append("\"" + path + "\"路径不存在" + "\n");}return info.toString();}/*** 修改文件名** @param file* 文件* @return N/A*/private String fileRename(File file) {String info = null;String oldName = file.getName();String newName = this.convertRule.reNameByRule(oldName);if (!oldName.equals(newName)) {Boolean result = file.renameTo(new File(file.getParent() + "\\" + newName));if (!result) {info = "文件\"" + file.getParent() + "\\" + oldName + "\"转换失败,请查看是否存在文件重名";} else {this.statisticsInfo.addConvertFileNum();info = "文件\"" + file.getParent() + "\\" + oldName + "\"转换为\"" + file.getParent() + "\\" + newName + "\"";}} else {info = "文件\"" + file.getParent() + "\\" + oldName + "\"不需要转换";}return info;}/*** 修改文件夹名** @param file* 文件夹* @return String msg*/private String folderRename(String folderName) {String info = null;String oldPath = folderName;String newPath = this.convertRule.reNameByRule(oldPath);if (!oldPath.equals(newPath)) {info = moveFolder(oldPath, newPath);} else {info = "文件夹\"" + oldPath + "\"不需要转换";}return info;}/*** 修改文件名** @param file* 文件* @return N/A*/private String fileRename(File file, String fix, String newFix) {String info = null;String oldName = file.getName();String newName = this.convertRule.reNameByRule(oldName, fix, newFix);if (!oldName.equals(newName)) {Boolean result = file.renameTo(new File(file.getParent() + "\\" + newName));if (!result) {info = "文件\"" + file.getParent() + "\\" + oldName + "\"转换失败,请查看是否存在文件重名";} else {this.statisticsInfo.addConvertFileNum();info = "文件\"" + file.getParent() + "\\" + oldName + "\"转换为\"" + file.getParent() + "\\" + newName + "\"";}} else {info = "文件\"" + file.getParent() + "\\" + oldName + "\"不需要转换";}return info;}/*** 修改文件夹名** @param file* 文件夹* @return String msg*/private String folderRename(String folderName, String fix, String newFix, Boolean isFolder) {String info = null;String oldPath = folderName;String newPath = this.convertRule.reNameByRule(oldPath, fix, newFix, isFolder);if (!oldPath.equals(newPath)) {info = moveFolder(oldPath, newPath);} else {info = "文件夹\"" + oldPath + "\"不需要转换";}return info;}// /////////////////// Internal use// ////////////////private String moveFolder(String oldPath, String newPath) {StringBuffer infos = new StringBuffer();try {FileUtils.moveDirectory(new File(oldPath), new File(newPath));this.statisticsInfo.addConvertFolderNum();infos.append("文件夹\"" + oldPath + "\"转换为\"" + newPath + "\"");} catch (IOException e) {infos.append("文件夹\"" + oldPath + "\"转换失败,请查看是否存在文件夹重名\n");infos.append(e.getMessage());}return infos.toString();}}
小写转换为大写,e.g. 101 -> 一百零一
package org.usc.file.operater.rules;import java.util.Arrays;import java.util.HashMap;import java.util.List;/*** 小写转大写规则** @author <a href="http://www.blogjava.net/lishunli/" target="_blank">ShunLi</a>* @notes Created on 2010-12-11<br>* Revision of last commit:$Revision: 829 $<br>* Author of last commit:$Author: nhjsjmz@gmail.com $<br>* Date of last commit:$Date: 2011-04-17 16:58:13 +0800 (周日, 17 四月 2011) $<br>* <p>*/public class SmallToBigConvertRule implements ConvertRule {private static java.util.Map<String, String> SmallToBigMap = new HashMap<String, String>();static {SmallToBigMap.put(String.valueOf(0), "零");SmallToBigMap.put(String.valueOf(1), "一");SmallToBigMap.put(String.valueOf(2), "二");SmallToBigMap.put(String.valueOf(3), "三");SmallToBigMap.put(String.valueOf(4), "四");SmallToBigMap.put(String.valueOf(5), "五");SmallToBigMap.put(String.valueOf(6), "六");SmallToBigMap.put(String.valueOf(7), "七");SmallToBigMap.put(String.valueOf(8), "八");SmallToBigMap.put(String.valueOf(9), "九");SmallToBigMap.put(String.valueOf(10), "十");SmallToBigMap.put(String.valueOf(100), "百");SmallToBigMap.put(String.valueOf(1000), "千");SmallToBigMap.put(String.valueOf(10000), "万");SmallToBigMap.put(String.valueOf(100000000), "亿");}public static String format(String num) {// 先将末尾的零去掉String numString = String.valueOf(num).replaceAll("[.][0]+$", "");// 分别获取整数部分和小数部分的数字String intValue;String decValue = "";if (".".equals(num.trim())) {return ".";} else if (numString.indexOf(".") != -1) {String[] intValueArray = String.valueOf(numString).split("\\.");String[] decVauleArray = String.valueOf(num).split("\\.");intValue = (intValueArray != null && intValueArray.length > 0 ? intValueArray[0] : "0");decValue = (decVauleArray != null && decVauleArray.length > 1 ? decVauleArray[1] : "0");} else {intValue = String.valueOf(numString);}// 翻译整数部分。intValue = formatLong(Long.parseLong(String.valueOf(intValue)));// 翻译小数部分decValue = formatDecnum(decValue);String resultString = intValue;if (!decValue.equals(""))resultString = resultString + "点" + decValue;return resultString.replaceAll("^一十", "十");}/*** 将阿拉伯整数数字翻译为汉语小写数字。 其核心思想是按照中文的读法,从后往前每四个数字为一组。每一组最后要加上对应的单位,分别为万、亿等。 每一组中从后往前每个数字后面加上对应的单位,分别为个十百千。 每一组中如果出现零千、零百、零十的情况下去掉单位。 每组中若出现多个连续的零,则通读为一个零。* 若每一组中若零位于开始或结尾的位置,则不读。** @param num* @return*/public static String formatLong(Long num) {Long unit = 10000L;Long perUnit = 10000L;String sb = new String();String unitHeadString = "";while (num > 0) {Long temp = num % perUnit;sb = formatLongLess10000(temp) + sb;// 判断是否以单位表示为字符串首位,如果是,则去掉,替换为零if (!"".equals(unitHeadString))sb = sb.replaceAll("^" + unitHeadString, "零");num = num / perUnit;if (num > 0) {// 如果大于当前单位,则追加对应的单位unitHeadString = SmallToBigMap.get(String.valueOf(unit));sb = unitHeadString + sb;}unit = unit * perUnit;}return sb == null || sb.trim().length() == 0 ? "零" : sb;}/*** 将小于一万的整数转换为中文汉语小写** @param num* @return*/public static String formatLongLess10000(Long num) {StringBuffer sb = new StringBuffer();for (Long unit = 1000L; unit > 0; unit = unit / 10) {Long _num = num / unit;// 追加数字翻译sb.append(SmallToBigMap.get(String.valueOf(_num)));if (unit > 1 && _num > 0)sb.append(SmallToBigMap.get(String.valueOf(unit)));num = num % unit;}// 先将连续的零联合为一个零,再去掉头部和末尾的零return sb.toString().replaceAll("[零]+", "零").replaceAll("^零", "").replaceAll("零$", "");}public static String formatDecnum(String num) {StringBuffer sBuffer = new StringBuffer();char[] chars = num.toCharArray();for (int i = 0; i < num.length(); i++) {sBuffer.append(SmallToBigMap.get(String.valueOf(chars[i])));}return sBuffer.toString();}public static String parseString(String oldName) {String[] str = new String[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "." };List<String> num = Arrays.asList(str);StringBuffer stringBuffer = new StringBuffer();int index = oldName.lastIndexOf("\\");if (index != -1) {stringBuffer.append(oldName.substring(0, index + 1));oldName = oldName.substring(index + 1);}StringBuffer numBuffer = new StringBuffer();for (int i = 0; i < oldName.length(); i++) {if (num.contains(oldName.substring(i, i + 1))) {numBuffer.append(oldName.substring(i, i + 1));} else {if (numBuffer != null && numBuffer.length() > 0) {String convertTemp = numBuffer.toString();// 去掉最后的小数点if(convertTemp.endsWith(".") && !".".equals(convertTemp)){convertTemp = convertTemp.substring(0,convertTemp.length()-1);stringBuffer.append(format(convertTemp)).append(".");}else{stringBuffer.append(format(convertTemp));}numBuffer.delete(0, numBuffer.length());} else {stringBuffer.append(numBuffer.toString());numBuffer.delete(0, numBuffer.length());}stringBuffer.append(oldName.substring(i, i + 1));}}if (numBuffer != null && numBuffer.length() > 0 && numBuffer.indexOf(".") == -1) {stringBuffer.append(format(numBuffer.toString()));} else {stringBuffer.append(numBuffer.toString());}return stringBuffer.toString();}@Overridepublic String reNameByRule(String oldName) {return parseString(oldName);}@Overridepublic String reNameByRule(String oldName, String fix, String newFix) {return reNameByRule(oldName);}@Overridepublic String reNameByRule(String oldName, String fix, String newFix, Boolean isFolder) {return reNameByRule(oldName);}}
大写转换成小写, e.g. 十 >10
package org.usc.file.operater.rules;import java.util.HashMap;import java.util.Map;/*** 大写转小写规则** @author <a href="http://www.blogjava.net/lishunli/" target="_blank">ShunLi</a>* @notes Created on 2010-12-11<br>* Revision of last commit:$Revision: 829 $<br>* Author of last commit:$Author: nhjsjmz@gmail.com $<br>* Date of last commit:$Date: 2011-04-17 16:58:13 +0800 (周日, 17 四月 2011) $<br>* <p>*/public class BigToSmallConvertRule implements ConvertRule {private static Map<String, Long> numberMap = new HashMap<String, Long>();private static Map<String, Long> unitMap = new HashMap<String, Long>();private static Map<String, Long> levelMap = new HashMap<String, Long>();private static Map<String, Long> upperLevelMap = new HashMap<String, Long>();static {numberMap.put("零", 0L);numberMap.put("一", 1L);numberMap.put("二", 2L);numberMap.put("三", 3L);numberMap.put("四", 4L);numberMap.put("五", 5L);numberMap.put("六", 6L);numberMap.put("七", 7L);numberMap.put("八", 8L);numberMap.put("九", 9L);numberMap.put("十", 10L);unitMap.put("十", 10L);unitMap.put("百", 100L);unitMap.put("千", 1000L);unitMap.put("万", 10000L);unitMap.put("亿", 100000000L);levelMap.put("万", 10000L);levelMap.put("亿", 100000000L);upperLevelMap.put("亿", 100000000L);}@Overridepublic String reNameByRule(String oldName) {Long sum = 0L;StringBuffer newName = new StringBuffer();int index = oldName.lastIndexOf("\\");if (index != -1) {newName.append(oldName.substring(0, index + 1));oldName = oldName.substring(index + 1);}int size = oldName.length();for (int i = 0; i < size; i++) {Boolean flag = false;if (numberMap.keySet().contains(oldName.substring(i, i + 1))) {Long small = numberMap.get(oldName.substring(i, i + 1));Long big = 1L;if ((i + 1 < size) && unitMap.keySet().contains(oldName.substring(i + 1, i + 2))) {big = unitMap.get(oldName.substring(i + 1, i + 2));if ("万".equals(oldName.substring(i + 1, i + 2)) || "亿".equals(oldName.substring(i + 1, i + 2))) {small += sum;sum = 0L;}if ((i + 2 < size) && levelMap.keySet().contains(oldName.substring(i + 2, i + 3))) {small = small * big;big = levelMap.get(oldName.substring(i + 2, i + 3));if ((i + 3 < size) && upperLevelMap.keySet().contains(oldName.substring(i + 3, i + 4))) {small = small * big;big = upperLevelMap.get(oldName.substring(i + 3, i + 4));i++;}i++;}i++;}sum = sum + small * big;flag = true;}if (!flag) {if (sum != 0) {newName.append(sum.toString());}newName.append(oldName.substring(i, i + 1));sum = 0L;} else {if (sum == 0 && "零".equals(oldName.substring(i, i + 1))) {newName.append(sum.toString());}}}if (sum != 0) {newName.append(sum.toString());}return newName.toString();}@Overridepublic String reNameByRule(String oldName, String fix, String newFix) {return reNameByRule(oldName);}@Overridepublic String reNameByRule(String oldName, String fix, String newFix, Boolean isFolder) {return reNameByRule(oldName);}}
运行环境
Jdk1.6(不知道支持1.5?用1.6写的),虽然提供的小工具为了方便打包成.exe 发布,但必须安装JDK了,非java 使用者就只能 say sorry 了。
建议分辨率为1280*1024
最低分辨率为1024*768
程序截图



?
下载和源码源码下载(包括测试样例和运行程序)
文 件 名:FileNameBatchConvert.zip
下载地址:http://usc.googlecode.com/files/FileNameBatchConvert.zip
源代码checkout(SVN,建议使用,保持最新的代码)
http://usc.googlecode.com/svn/FileNameBatchConvert/
工具下载
文 件 名:文件名批量转换V2.1.exe
下载地址:http://usc.googlecode.com/files/%E6%96%87%E4%BB%B6%E5%90%8D%E6%89%B9%E9%87%8F%E8%BD%AC%E6%8D%A2V2.1.exe
Bug跟踪
如果发现了问题,您如果没有兴趣或者没有时间不用管,顺利很抱歉为您带来了麻烦,不过您也可以尝试自己解决,当然非常欢迎告知在下。
尾声
实际上写这篇文章是练练手,熟悉下Java的一些基础知识,不要一来就是 SSH ,实际上真正地开发是业务知识第一,Java就是第二(真的吗?至少我目前认为不是,不过前人都是这么说,引用下,这里),后面才是学习新技术的能力(个人愚见)。
实际上,这个工具也没啥,就是调用一些File 类的API,进行封装而已,可能这个工具不太实用,不过也没什么,至少我用的很ok,那就行了。后续的想法是迁移到Maven项目,更多的使用 Apache common utils,比如 FileUtil,FileNameUtil,如果能支持正则就非常完美了。
如果有什么建议或意见可以通过微博 http://weibo.com/lishunli(左上侧直接加关注)或QQ:506817493(QQ白天经常不在线,建议微博交流,谢谢),大家一起交流学习。
最后弱弱地说一下,如果可以的话,转载请提供原URL。谢谢
顺利
2011年8月13日
?
?
?
版本更新信息
Version
V3.0
还没有完成的,大致的想法是迁移到Maven项目,更多的使用 Apache common utils,比如 FileUtil,FileNameUtil,如果能支持正则就非常完美了。
V2.1
1.性能优化,使用 commons-io 的 FileUtils 来进行move整个文件夹的操作;
2.封装计数的结果。
V2.0版本新特性
1.优化UI布局
2.该用WindowBuilder Pro(Google)设计UI
3.增加转换参数选项
4.增加导出转换结果信息为文本文件功能
5.增加打开转换结果信息文本文件功能
6.增加按钮可单击性控制
7.增加控件的提示信息
8.修改默认的程序图标
?
V2.0.0.1
Tuning UI:
1. 默认不选择任何转换规则 - Default Set don't check any rules.
2. 复制的路径也保存到注册表中 - Filepath by manual copy is also saved to the registry path.
?
V2.1.0.0
1.性能优化,使用 commons-io 的 FileUtils 来进行move整个文件夹的操作;
2.封装计数的结果。
-- EN --
1. Performance Tuning,use commons-io's File utils to move folder.
2. Encapsulation and abstraction.
?
版本更改其它Log
结果显示增加时间统计
初步完成V0.2版,增加文件夹浏览,清空结果等功能。
加入窗体图标;
制作Exe文件
v0.3:添加对前后缀文件名的批量修改
1.修改checkbox 选中与不选中时的处理;
2.V0.3完成;
3.准备完成V1.0版,增加字符串修改。
修正UI上面单击可能没有反应的Bug;
修正提示信息。
增加文件夹是否转换?功能;
修正对文件夹替换字符串的Bug.
虽然改动很小,但是解决了一个很实用的Bug
就是在字符串replace的时候是使用的正则表达式,传过来的字符串中可能存在正则表达式的特殊字符,例如'[','(' and so on
通过使用Pattern.quote来获得 a literal String.
出现了在windows中文件名不能输入一些特殊字符的Bug
文件名不能包含下列任何字符:
\/:*?"<>|
v1.0不处理,以出错信息结束;
v1.1做过滤处理。
创建V1.1,出现特殊字符的时候,过滤它们;
修正原串为“”的情况下出现的Bug.
记住上一次打开的路径;
修改显示的结果信息。
修改为版本V1.2;
默认不转换文件夹(真正的多层转换很少)
修改结果信息的显示
可以选择是否显示更多转换信息(默认不显示,更简洁)
增加转换和成功转换文件夹和文件统计数目信息
1 楼 evanzzy 2011-08-15 内容挺好,排版差了点儿