小工具大智慧
—— 文件名批量轉換工具
順利
關鍵字
李順利,文件,名,文件夾,工廠模式,策略模式,數字,大小寫,轉換,大寫轉小寫,小寫轉大寫,批量,前綴,后綴,替換,Window Builder
正文
說大智慧是不是有點自大,今天就當一次標題D了。
有的時候,會碰到類似的文件或文件夾,但是它的名字是中文的數字,這在window 系統中難于排序(至少我沒找到),就有這樣的需求,把數字大寫轉換成小寫,這樣方便排序好閱讀。
或者有的時候,你需要批量在文件夾或文件加上(修改)前綴或者后綴文字,來顯示版權信息什么的,或者有的時候,需要批量修改文件的類型,基于其它某某種種的需求,在空閑時間內寫了這個小工具,希望大家喜歡。
轉換需求
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://m.tkk7.com/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://m.tkk7.com/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://m.tkk7.com/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://m.tkk7.com/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;
默認不轉換文件夾(真正的多層轉換很少)
修改結果信息的顯示
可以選擇是否顯示更多轉換信息(默認不顯示,更簡潔)
增加轉換和成功轉換文件夾和文件統計數目信息
博客中的一些下載已經放到了百度云了,請根據需要下載。【點我去百度云下載】
最后弱弱地說一下,如果可以的話,轉載請提供出處( ),謝謝。
