Android文件重命名File.renameTo()以及定义副本名方法(自定义规则)

项目需求

做文件管理相关项目有个需求需要对单个或多个文件进行重命名,这就可能会出现名称重复的情况;还有复制的时候,如果粘贴的地方已存在相同名称文件,也需要进行重命名。

相仿思想:

我们知道在电脑上复制粘贴同一文件(夹)到同一路径下的时候,系统会帮我们自动生成新的副本(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+)?”,……


如果你有任何问题,请留言告诉我!

你可能感兴趣的:(*,Android)