项目需求
做文件管理相关项目有个需求需要对单个或多个文件进行重命名,这就可能会出现名称重复的情况;还有复制的时候,如果粘贴的地方已存在相同名称文件,也需要进行重命名。
相仿思想:
我们知道在电脑上复制粘贴同一文件(夹)到同一路径下的时候,系统会帮我们自动生成新的副本(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+)?”,……
如果你有任何问题,请留言告诉我!
版权声明:本文为博主原创文章,未经博主允许不得转载。