做文件管理相关项目有个需求需要对单个或多个文件进行重命名,这就可能会出现名称重复的情况;还有复制的时候,如果粘贴的地方已存在相同名称文件,也需要进行重命名。
我们知道在电脑上复制粘贴同一文件(夹)到同一路径下的时候,系统会帮我们自动生成新的副本(Copy)名
比如: 我.png
MAC是文件名 + ” ” + 数字递增 + 后缀 (我 1.png)
Windows是文件名 + ” - 副本”+ ” (” + 数字递增 + “)” + 后缀 (我 -副本(2).png)
而在电脑上重命名时,如果该路径下已有该名称文件,则会提示(警告)用户并不允许进行重命名。
本节将讲解的内容有:
1.如何调用File固有方法去进行本地文件重命名
2.需要新建副本名时,该怎么用自定义规则进行重命名
根据需求,我们可以定义接口重命名
public boolean rename(FileInfo fileInfo, String newName);
其中FileInfo是自定义数据结构,里面包含fileName(文件名),filePath(文件路径),isDir(是否是目录)等信息。
参数newName是新的文件名(包含后缀)
/**
* @Description 对文件进行重新命名
* @param fileInfo 原始文件信息
* @param newName 新名称(有后缀)
* @param admitCopyName 当命名冲突时,是否允许生成副本名(如果是多选重命名的话,是需要允许的;单个文件重命名则设置为不允许)
* @return 是否修改成功
*/
public boolean rename(FileInfo fileInfo, String newName, boolean admitCopyName) {
//1.判断参数阈值
if (fileInfo == null || newName == null) {
Log.e(LOG_TAG, "Rename: null parameter");
return false;
}
//2.得到原文件全路径
String oldPath = fileInfo.getFilePath();
Log.d(LOG_TAG, "Rename---original path = " + oldPath));
//3-1.得到文件所在路径
String rootPath = Util.getPathFromFilepath(oldPath); //Util.getPathFromFilepath(String)-自定义方法:得到文件所在路径(即全路径去掉完整文件名)
//3-2.得到新全路径
String newPath = Util.makePath(rootPath, newName); //Util.makePath(String, String)-自定义方法:根据根路径和文件名形成新的完整路径
Log.d(LOG_TAG, "Rename---new Path = " + newPath);
//4.比较是否变更了名称
if (oldPath.endsWith(newPath)) { //和原来名称一样,不需要改变
return true;
}
try {
//5.根据新路径得到File类型数据
File newFile = new File(newPath);
//6.判断是否已经存在同样名称的文件(即出现重名)
if (newFile.exists() && !admitCopyName) { //出现重命名且不允许生成副本名
return false; //重命名失败
}
//7.循环判断直到生成可用的副本名
while (newFile.exists()) {
Log.w(LOG_TAG, "Rename---新文件路径名称已存在文件 ---" + newPath);
//重命名重名定义规范--Util.getCopyNameFromOriginal(String)-自定义方法:根据自定义规则生成原名称副本
newPath = Util.getCopyNameFromOriginal(newPath);
newFile = new File(newPath);
Log.i(LOG_TAG, "Rename---new copy Path = " + newPath);
}
//8.得到原文件File类型数据
File file = new File(oldPath);
//9.调用固有方法去重命名
boolean ret = file.renameTo(newFile);
Log.i(LOG_TAG, "Rename---改名成功? " + ((ret) ? "yes!" : "no!"));
if (ret) {
//FIXME:这里通过更改形参来改变实参,是不好的写法,不建议这样写!
fileInfo.setFileName(Util.getNameFromFilepath(newPath)); //更新文件名
fileInfo.setmFilePath(newPath); //更新新路径
}
return ret;
} catch (SecurityException e) {
Log.e(LOG_TAG, "Fail to rename file," + e.toString());
}
return false;
}
代码量看起来有点长,其实中心就一句,就是第9步中的file.renameTo(newFile);
这是调用File的固有重命名方法,其用法如下
//oldPath like "mnt/sda/sda1/我.png"
File file = new File(oldPath);
//newPath like "mnt/sda/sda1/我的照片.png"
file.renameTo(new File(newPath));
需要注意的是:
第一,如果是Android的SD卡上的文件重命名,那么必须添加权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
第二,oldPath和newPath必须是新旧文件的绝对路径
第三,如果newPath已经存在文件,不做任何处理的话,调用此方法会把已存在的文件覆盖掉。
意思是:比如我在”mnt/sda/sda1/”路径下有两张照片,一个叫”我.png”,一个叫”我的照片.png”,这个时候我用file.renameTo(new File(newPath));
把”我.png”重命名为”我的照片.png”,那么原来存在的”我的照片.png”会被重命名后的”我.png”覆盖。
注:第三点确实是我实践后总结出来的,但是没有进行过大量的验证,若有不对的地方,还望指教。
第四,在网上看到一些人为了测试renameTo的用法,直接在没有实体文件的情况下,就创建路径,然后进行重命名。
File t1 = new File("D:" + File.separatorChar + "final.java");
File t2 = new File("D:" + File.separatorChar + "finalaa.java");
System.out.println("是否重命名成功:" + t1.renameTo(t2)); //输出false
需注意的是,new File(filePath)得到的是一个路径,并不是一个实体文件!当你文件都没有创建的时候,怎么可能对一个不存在的文件进行重命名呢?
如下解释我觉得更好理解
我的理解是new File(String path)并没有创建文件本身,只是说我可能要在这个路径创建文件。
t1被创建以后(调用了createNewFile),文件就实实在在地在那里了,重命名为finalaaa.java以后,文件还是t1这个文件,只是路径改变了,从而使名字变了。
由始至终只有一个文件(也就是说t2只是把地方标出来了,并没有得到创建,当t1想占用t2的地盘的时候,t2让给了t1)
可以看到重命名方法里调用了许多我自定义的辅助方法,
如3-1步
Util.getPathFromFilepath(oldPath); //得到文件所在路径(即全路径去掉完整文件名)
3-2步
Util.makePath(rootPath, newName); //根据根路径和文件名形成新的完整路径
第7步
Util.getCopyNameFromOriginal(newPath); //根据自定义规则生成原名称副本
这些方法的实现也不难,都放在Util工具包里是为了复用。
/**
* @Description 得到文件所在路径(即全路径去掉完整文件名)
* @param filepath 文件全路径名称,like mnt/sda/XX.xx
* @return 根路径,like mnt/sda
*/
public static String getPathFromFilepath(final String filepath) {
int pos = filepath.lastIndexOf('/');
if (pos != -1) {
return filepath.substring(0, pos);
}
return "";
}
/**
* @Description 重新整合路径,将路径一和路径二通过'/'连接起来得到新路径
* @param path1 路径一
* @param path2 路径二
* @return 新路径
*/
public static String makePath(final String path1, final String path2) {
if (path1.endsWith(File.separator)) {
return path1 + path2;
}
return path1 + File.separator + path2;
}
上面两个确实比较简单,第三个方法getCopyNameFromOriginal(String)
是取得文件的副本名称,里面定义了定义副本名称的规则(空格 + 数字递增),该规则是仿着Mac的重命名规则来定义的。
/**
* @Description 得到文件副本名称,可供粘贴及多选重命名方法使用
* 命名规则为:普通文件后加“ 1”,若文件末尾已有“ 数字”,则数字递增。
* 比如,有个文件叫“我.jpg”,使用本方法后得到了“我 1.jpg”,再次使用本方法后得到“我 2.jpg”
* @param originalName 原本的名字,XXX.xx 或者完整路径 xx/xx/XXX.xx , 也可以没有后缀.xx
* @return 副本名称
*/
public static String getCopyNameFromOriginal(final String originalName) {
//1.判断阈值
if (originalName == null || originalName.isEmpty()) {
return null;
}
String copyName = null;
//2.得到文件名和后缀名
String[] nameAndExt = getNameAndExtFromOriginal(originalName);
if (nameAndExt == null) {
return null;
}
String fileName = nameAndExt[0];
String fileExt = nameAndExt[1];
//3.判断文件名是否包含我们定义副本规范的标记字符(空格)
if (fileName.contains(" ")) { //如果文件名包涵空格,进行判断是否已经为副本名称
//4-1.得到end
String[] array = fileName.split(" ");
String end = array[array.length - 1]; //得到标记字符后面的值
//4-2.确保end得到的是最后面的值(防止出现类似路径中的目录也有标记字符的情况,如:"mnt/sda/wo de/zhao pian/我的 照片 1.png")
while(end.contains(" ")) {
array = fileName.split(" ");
end = array[array.length - 1];
}
//5.判断标记字符后的字符串是否复合规范(是否是数字)
boolean isDigit = end.matches("[0-9]+"); //用正则表达式判断是否是正整数
if (isDigit) {
try {
int index = Integer.parseInt(end) + 1; //递增副本记数
int position = fileName.lastIndexOf(" "); //得到最后的空格的位置,用于截取前面的字符串
if (position != -1) {
//6-1.构造新的副本名(数字递增)
copyName = fileName.substring(0, position + 1) + String.valueOf(index);
}
} catch (Exception e) { //转化成整形错误
e.printStackTrace();
return null;
}
} else { //如果空格后不是纯数字,即不为我们定义副本的规范
//6-2.构造新的副本名(数字初始为1)
copyName = fileName + " 1";
}
} else { //如果没有,则变为副本名称格式
//6-3.构造新的副本名(数字初始为1)
copyName = fileName + " 1";
}
Log.d(TAG, "new copy name is " + copyName + fileExt);
//6.返回副本名+后缀名
return copyName + fileExt;
}
需要注意的有
第一点,可以把所有的标记字符(” “)提出来,用常量保存,COPY_NAME_TAG = " ";
,这样我们想更改标记字符的时候(比如想改成”-“),只需要改常量就可以了。
第二点,我想讲的是第5步boolean isDigit = end.matches("[0-9]+"); //用正则表达式判断是否是正整数
,关于如何判断一个字符串是否是数字。其实有很多种方法。
1.使用Character.isDigit(char)判断
思路:首先把String转换成char[],然后单个判断是否是数字,是就根据位数算出值,不是则输出错误的结果(-1),最后根据值进行判断使用。
char num[] = end.toCharArray();//把字符串转换为字符数组
int result = 0;
for (int i = 0; i < num.length; i++) {
if (Character.isDigit(num[i])) {
result += Integer.parseInt(Character.toString(num[i])) * Math.pow(10, length - 1); //当前数字乘以所在位数
} else { //一旦有一个不是字符,转换失败
result = -1;
break;
}
}
if (result > 0) {
//...
}
第一种方法比较简单也比较麻烦,适合初学者使用。
2.使用类型强制转换
boolean isDigit;
try {
int num=Integer.valueOf(end);//把字符串强制转换为数字
isDigit = true;//如果是数字,返回True
} catch (Exception e) {
isDigit = false;//如果抛出异常,返回False
}
if (isDigit) {
//...
}
这种用异常来判断是否是数字的方法比较怪异,我个人是不建议使用。
3.使用正则表达式判断
也就是我第5步boolean isDigit = end.matches("[0-9]+"); //用正则表达式判断是否是正整数
正则表达式[]表示可有可没有,0-9表示0-9之间(包含0和9)的的所有数字任选一个,+表示至少有一次,所以这样写就是表示正整数。
以下引用自网络,没有验证正确性。
用正则表达式,看看str.matches(regex)是不是返回true就行。其中regex的部分,对非负整型用”\d+”,有可选的负号的用”-?\d+”,可选正负号的用”(?:+|-)?\d+”,非负的浮点数用”\d+(?:\.\d+)?”,……
如果你有任何问题,请留言告诉我!