如何在安卓系统中复制文件?

214

在我的应用程序中,我想用用户提供的不同名称保存某个文件的副本。

我真的需要打开文件内容并将其写入另一个文件吗?

有什么最好的方法来实现这一点?


在Java7之前,我认为答案是肯定的。 https://dev59.com/8nVD5IYBdhLWcg3wDHDm. - Paul Grime
http://www.java2s.com/Code/Java/File-Input-Output/MoveaFile.htm - run
请查看早期的帖子:链接 - Deepak
12个回答

363

要复制文件并保存到目标路径,您可以使用以下方法。

public static void copy(File src, File dst) throws IOException {
    InputStream in = new FileInputStream(src);
    try {
        OutputStream out = new FileOutputStream(dst);
        try {
            // Transfer bytes from in to out
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
        } finally {
            out.close();
        }
    } finally {
        in.close();
    }
}

在 API 19+ 上,你可以使用 Java 自动资源管理:

public static void copy(File src, File dst) throws IOException {
    try (InputStream in = new FileInputStream(src)) {
        try (OutputStream out = new FileOutputStream(dst)) {
            // Transfer bytes from in to out
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
        }
    }
}

8
谢谢。在我撞了一下头之后,我发现问题是缺少写入外部存储的权限,现在它已经可以正常工作了。 - A S
8
如果文件复制失败,则会抛出异常。调用该方法时,请使用try-catch块。 - Nima GT
10
如果抛出异常,直到垃圾收集时流才会关闭,这不好。请考虑在 finally 中关闭它们。 - Pang
1
@Pang。你说得对。更糟的是,必须调用in/out.close()来避免底层操作系统的资源泄漏,因为垃圾回收器永远不会关闭打开的文件描述符。GC无法关闭JVM外部的操作系统资源,例如文件描述符或套接字。这些资源必须始终在finally子句中通过编程方式关闭,或使用try-with-resource新语法:https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html。 - earizon
4
请在 finally 块中关闭两个流,如果出现异常,您的流内存将不会被回收。 - pozuelog
显示剩余11条评论

137

或者,您可以使用FileChannel来复制文件。在复制大文件时,这种方法可能比字节复制方法更快。如果要复制的文件大于2GB,则无法使用该方法

public void copy(File src, File dst) throws IOException {
    FileInputStream inStream = new FileInputStream(src);
    FileOutputStream outStream = new FileOutputStream(dst);
    FileChannel inChannel = inStream.getChannel();
    FileChannel outChannel = outStream.getChannel();
    inChannel.transferTo(0, inChannel.size(), outChannel);
    inStream.close();
    outStream.close();
}

3
如果 transferTo 抛出异常,那么你就会离开流(stream)处于打开状态。就像 Pang 和 Nima 在被接受的回答中所评论的那样。 - Viktor Brešan
我尝试了你的解决方案,但在FileOutputStream outStream = new FileOutputStream(dst);步骤中出现了异常java.io.FileNotFoundException: /sdcard/AppProj/IMG_20150626_214946.jpg: open failed: ENOENT (No such file or directory)。根据文本,我意识到文件不存在,所以我检查并调用dst.mkdir();(如果需要),但仍然没有帮助。我还尝试检查dst.canWrite();,它返回了false。这可能是问题的源吗?是的,我有<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> - Mike
1
@ViktorBrešan 在API 19之后,你可以通过在try块的开头定义输入和输出流来使用Java的自动资源管理。例如:try ( FileInputStream inStream = new FileInputStream(src); FileOutputStream outStream = new FileOutputStream(dst) ) { - AlbertMarkovski
1
有没有办法让这个解决方案将其进度发布到 onProgressUpdate,以便我可以在 ProgressBar 中显示它?在被接受的解决方案中,我可以在 while 循环中计算进度,但我不知道如何在这里做到这一点。 - George Bezerra
@MikeB。如果您在API 23或更高版本上运行它,请尝试检查运行时权限。 - Ariq
显示剩余2条评论

64

Kotlin 的扩展

fun File.copyTo(file: File) {
    inputStream().use { input ->
        file.outputStream().use { output ->
            input.copyTo(output)
        }
    }
}

3
这是最简明灵活的回答。更简单的回答无法考虑通过contentResolver.openInputStream(uri)打开的URI。 - charles-allen
嗨,我想克隆并重命名它的文件,你可以帮忙吗?先谢谢了。 - famfamfam
@famfamfam 在创建文件参数时,只需传递不同的名称即可。 - Dmytro Rostopira
我找到了copy方法,但是没有看到变量名sir:public fun InputStream.copyTo(out: OutputStream, bufferSize: Int = DEFAULT_BUFFER_SIZE): Long - famfamfam

22

在安卓O(API 26)上,这很简单,正如您所看到的:

  @RequiresApi(api = Build.VERSION_CODES.O)
  public static void copy(File origin, File dest) throws IOException {
    Files.copy(origin.toPath(), dest.toPath());
  }

20

这些对我来说效果很好

public static void copyFileOrDirectory(String srcDir, String dstDir) {

    try {
        File src = new File(srcDir);
        File dst = new File(dstDir, src.getName());

        if (src.isDirectory()) {

            String files[] = src.list();
            int filesLength = files.length;
            for (int i = 0; i < filesLength; i++) {
                String src1 = (new File(src, files[i]).getPath());
                String dst1 = dst.getPath();
                copyFileOrDirectory(src1, dst1);

            }
        } else {
            copyFile(src, dst);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static void copyFile(File sourceFile, File destFile) throws IOException {
    if (!destFile.getParentFile().exists())
        destFile.getParentFile().mkdirs();

    if (!destFile.exists()) {
        destFile.createNewFile();
    }

    FileChannel source = null;
    FileChannel destination = null;

    try {
        source = new FileInputStream(sourceFile).getChannel();
        destination = new FileOutputStream(destFile).getChannel();
        destination.transferFrom(source, 0, source.size());
    } finally {
        if (source != null) {
            source.close();
        }
        if (destination != null) {
            destination.close();
        }
    }
}

19

如果您的文件来自画廊意图 Uri,则不是那么简单。 - charles-allen
1
阅读下面那个回答下面的评论:“这个答案实际上是有害的,不应该得到这些赞。如果Uri是content://或其他非文件Uri,则会失败。”(我已经点了赞 - 我只是想澄清这不是万能解决方案) - charles-allen

11

可能已经为答案太晚了,但最方便的方法是使用

FileUtils

static void copyFile(File srcFile, File destFile)

例如,以下是我所做的:

`

private String copy(String original, int copyNumber){
    String copy_path = path + "_copy" + copyNumber;
        try {
            FileUtils.copyFile(new File(path), new File(copy_path));
            return copy_path;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

26
在Android中,FileUtils不是原生存在的。 - The Berga
2
但是有一个由Apache制作的库可以做到这一点甚至更多:https://commons.apache.org/proper/commons-io/javadocs/api-2.5/org/apache/commons/io/FileUtils.html - Alessandro Muzzi
@TheBerga,您能详细说明一下吗?我找到了这些复制方法,但无法使用它们。这些方法的注释声称存在某些优化,所以我很想使用它们。 - Minh Nghĩa
我认为FileUtils需要InputStream。https://developer.android.com/reference/android/os/FileUtils - CrackerKSR

8

在 Kotlin 中,只需:

val fileSrc : File = File("srcPath")
val fileDest : File = File("destPath")

fileSrc.copyTo(fileDest)

5

这里有一个解决方案,如果在复制过程中发生错误,它实际上会关闭输入/输出流。此解决方案利用了Apache Commons IO IOUtils方法来进行复制和处理流的关闭。

    public void copyFile(File src, File dst)  {
        InputStream in = null;
        OutputStream out = null;
        try {
            in = new FileInputStream(src);
            out = new FileOutputStream(dst);
            IOUtils.copy(in, out);
        } catch (IOException ioe) {
            Log.e(LOGTAG, "IOException occurred.", ioe);
        } finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(in);
        }
    }

看起来很简单。 - Serg Burlaka
你应该使用 copyStream 而不是 copy - Borzh
为了让代码更简洁,我们可以使用try-with语句块,它会自动关闭流。 - CrackerKSR

1
在 Kotlin 中:一种简短的方式。
// fromPath : Path the file you want to copy 
// toPath :   The path where you want to save the file
// fileName : name of the file that you want to copy
// newFileName: New name for the copied file (you can put the fileName too instead of put a new name)    

val toPathF = File(toPath)
if (!toPathF.exists()) {
   path.mkdir()
}

File(fromPath, fileName).copyTo(File(toPath, fileName), replace)

这适用于任何像图片和视频这样的文件


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接