سؤال

لقد أزعجني دائمًا أن الطريقة الوحيدة لنسخ ملف في Java تتضمن فتح التدفقات، والإعلان عن مخزن مؤقت، والقراءة في ملف واحد، والتكرار خلاله، وكتابته على البخار الآخر.تمتلئ شبكة الويب بتطبيقات مماثلة، ولكنها لا تزال مختلفة قليلاً لهذا النوع من الحلول.

هل هناك طريقة أفضل تبقى ضمن حدود لغة Java (بمعنى لا تتضمن أوامر محددة لنظام التشغيل exec)؟ربما في بعض حزم المرافق مفتوحة المصدر الموثوقة، من شأنه أن يحجب على الأقل هذا التنفيذ الأساسي ويوفر حلاً من سطر واحد؟

هل كانت مفيدة؟

المحلول

كما ذكرت مجموعة الأدوات أعلاه، فإن Apache Commons IO هو الحل الأمثل، على وجه التحديد fileUtils.نسخة ارشيف();فهو يتعامل مع جميع الأحمال الثقيلة نيابةً عنك.

وكحاشية، لاحظ أن الإصدارات الحديثة من FileUtils (مثل الإصدار 2.0.1) أضافت استخدام NIO لنسخ الملفات؛ يمكن لـ NIO زيادة أداء نسخ الملفات بشكل ملحوظ, ، ويرجع ذلك إلى حد كبير إلى أن إجراءات NIO تؤجل النسخ مباشرة إلى نظام التشغيل/نظام الملفات بدلاً من التعامل معه عن طريق قراءة البايتات وكتابتها عبر طبقة Java.لذلك، إذا كنت تبحث عن الأداء، فقد يكون من المفيد التحقق من أنك تستخدم إصدارًا حديثًا من FileUtils.

نصائح أخرى

سأتجنب استخدام واجهة برمجة التطبيقات الضخمة مثل مشاعات Apache.هذه عملية مبسطة وهي مدمجة في JDK في حزمة NIO الجديدة.لقد تم ربطه بالفعل في إجابة سابقة، ولكن الطريقة الرئيسية في NIO api هي الوظيفتان الجديدتان "transferTo" و"transferFrom".

http://java.sun.com/javase/6/docs/api/java/nio/channels/FileChannel.html#transferTo(long,%20long,%20java.nio.channels.WritableByteChannel)

تعرض إحدى المقالات المرتبطة طريقة رائعة حول كيفية دمج هذه الوظيفة في التعليمات البرمجية الخاصة بك، باستخدام النقل من:

public static void copyFile(File sourceFile, File destFile) throws IOException {
    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();
        }
    }
}

يمكن أن يكون تعلم NIO أمرًا صعبًا بعض الشيء، لذا قد ترغب فقط في الوثوق بهذا الميكانيكي قبل الانطلاق ومحاولة تعلم NIO بين عشية وضحاها.من خلال التجربة الشخصية، قد يكون من الصعب جدًا معرفة ما إذا لم تكن لديك الخبرة وتم تعريفك بـ IO عبر تدفقات java.io.

الآن مع Java 7، يمكنك استخدام بناء الجملة التالي للتجربة مع الموارد:

public static void copyFile( File from, File to ) throws IOException {

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

    try (
        FileChannel in = new FileInputStream( from ).getChannel();
        FileChannel out = new FileOutputStream( to ).getChannel() ) {

        out.transferFrom( in, 0, in.size() );
    }
}

أو الأفضل من ذلك، يمكن أيضًا تحقيق ذلك باستخدام فئة الملفات الجديدة المقدمة في Java 7:

public static void copyFile( File from, File to ) throws IOException {
    Files.copy( from.toPath(), to.toPath() );
}

أنيق جداً، أليس كذلك؟

  • تم تصميم هذه الأساليب خصيصًا للأداء (فهي تتكامل مع الإدخال/الإخراج الأصلي لنظام التشغيل).
  • تعمل هذه الطرق مع الملفات والأدلة والروابط.
  • يمكن استبعاد كل خيار من الخيارات المتوفرة - فهي اختيارية.

فئة المنفعة

package com.yourcompany.nio;

class Files {

    static int copyRecursive(Path source, Path target, boolean prompt, CopyOptions options...) {
        CopyVisitor copyVisitor = new CopyVisitor(source, target, options).copy();
        EnumSet<FileVisitOption> fileVisitOpts;
        if (Arrays.toList(options).contains(java.nio.file.LinkOption.NOFOLLOW_LINKS) {
            fileVisitOpts = EnumSet.noneOf(FileVisitOption.class) 
        } else {
            fileVisitOpts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
        }
        Files.walkFileTree(source[i], fileVisitOpts, Integer.MAX_VALUE, copyVisitor);
    }

    private class CopyVisitor implements FileVisitor<Path>  {
        final Path source;
        final Path target;
        final CopyOptions[] options;

        CopyVisitor(Path source, Path target, CopyOptions options...) {
             this.source = source;  this.target = target;  this.options = options;
        };

        @Override
        FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
        // before visiting entries in a directory we copy the directory
        // (okay if directory already exists).
        Path newdir = target.resolve(source.relativize(dir));
        try {
            Files.copy(dir, newdir, options);
        } catch (FileAlreadyExistsException x) {
            // ignore
        } catch (IOException x) {
            System.err.format("Unable to create: %s: %s%n", newdir, x);
            return SKIP_SUBTREE;
        }
        return CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
        Path newfile= target.resolve(source.relativize(file));
        try {
            Files.copy(file, newfile, options);
        } catch (IOException x) {
            System.err.format("Unable to copy: %s: %s%n", source, x);
        }
        return CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
        // fix up modification time of directory when done
        if (exc == null && Arrays.toList(options).contains(COPY_ATTRIBUTES)) {
            Path newdir = target.resolve(source.relativize(dir));
            try {
                FileTime time = Files.getLastModifiedTime(dir);
                Files.setLastModifiedTime(newdir, time);
            } catch (IOException x) {
                System.err.format("Unable to copy all attributes to: %s: %s%n", newdir, x);
            }
        }
        return CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) {
        if (exc instanceof FileSystemLoopException) {
            System.err.println("cycle detected: " + file);
        } else {
            System.err.format("Unable to copy: %s: %s%n", file, exc);
        }
        return CONTINUE;
    }
}

نسخ دليل أو ملف

long bytes = java.nio.file.Files.copy( 
                 new java.io.File("<filepath1>").toPath(), 
                 new java.io.File("<filepath2>").toPath(),
                 java.nio.file.StandardCopyOption.REPLACE_EXISTING,
                 java.nio.file.StandardCopyOption.COPY_ATTRIBUTES,
                 java.nio.file.LinkOption.NOFOLLOW_LINKS);

نقل دليل أو ملف

long bytes = java.nio.file.Files.move( 
                 new java.io.File("<filepath1>").toPath(), 
                 new java.io.File("<filepath2>").toPath(),
                 java.nio.file.StandardCopyOption.ATOMIC_MOVE,
                 java.nio.file.StandardCopyOption.REPLACE_EXISTING);

نسخ دليل أو ملف بشكل متكرر

long bytes = com.yourcompany.nio.Files.copyRecursive( 
                 new java.io.File("<filepath1>").toPath(), 
                 new java.io.File("<filepath2>").toPath(),
                 java.nio.file.StandardCopyOption.REPLACE_EXISTING,
                 java.nio.file.StandardCopyOption.COPY_ATTRIBUTES
                 java.nio.file.LinkOption.NOFOLLOW_LINKS );

في Java 7 يكون الأمر سهلاً...

File src = new File("original.txt");
File target = new File("copy.txt");

Files.copy(src.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);

لنسخ ملف وحفظه في المسار الوجهة، يمكنك استخدام الطريقة الموضحة أدناه.

public 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();
    }
}

لاحظ أن كل هذه الآليات تقوم فقط بنسخ محتويات الملف، وليس البيانات الوصفية مثل الأذونات.لذا، إذا كنت تريد نسخ أو نقل ملف .sh قابل للتنفيذ على نظام التشغيل Linux، فلن يكون الملف الجديد قابلاً للتنفيذ.

من أجل نسخ ملف أو نقله بشكل حقيقي، أي الحصول على نفس نتيجة النسخ من سطر الأوامر، فإنك تحتاج بالفعل إلى استخدام أداة أصلية.إما برنامج نصي شل أو JNI.

على ما يبدو، قد يتم إصلاح هذا في Java 7 - http://today.java.net/pub/a/today/2008/07/03/jsr-203-new-file-apis.html.تشابك الاصابع!

تحتوي مكتبة Google Guava أيضًا على طريقة النسخ:

public static void ينسخ(ملف from,
                        ملف to)
                 throws IOEException
نسخ كافة البايتات من ملف إلى آخر.

تحذير: لو to يمثل ملفًا موجودًا ، وسيتم كتابة هذا الملف مع محتويات from.لو to و from الرجوع إلى نفس ملف ، سيتم حذف محتويات هذا الملف.

حدود:from - الملف المصدرto - الملف الوجهة

رميات: IOEException - في حالة حدوث خطأ في الإدخال/الإخراجغير الشرعيين استثناء حجة - لو from.equals(to)

متوفر بشكل قياسي في Java 7، path.copyTo:http://openjdk.java.net/projects/nio/javadoc/Java/nio/file/Path.html http://java.sun.com/docs/books/tutorial/essential/io/copy.html

لا أستطيع أن أصدق أن الأمر استغرق وقتًا طويلاً لتوحيد شيء شائع وبسيط مثل نسخ الملفات :(

ثلاث مشاكل محتملة في الكود أعلاه:

  1. إذا طرحت getChannel استثناءً، فقد يؤدي ذلك إلى تسريب دفق مفتوح.
  2. بالنسبة للملفات الكبيرة، قد تحاول نقل المزيد من الملفات مرة واحدة أكثر مما يستطيع نظام التشغيل التعامل معه.
  3. أنت تتجاهل القيمة المرجعة لـ TransferFrom، لذا قد يكون نسخ جزء فقط من الملف.

هذا هو السبب org.apache.tools.ant.util.ResourceUtils.copyResource معقدة للغاية.لاحظ أيضًا أنه على الرغم من أن TransferFrom على ما يرام، فإن TransferTo ينقطع عن JDK 1.4 على Linux (انظر معرف الخطأ: 5056395) - جيسي جليك جان

إذا كنت تستخدم تطبيق ويب يستخدم Spring بالفعل وإذا كنت لا تريد تضمين Apache Commons IO لنسخ الملفات البسيطة، فيمكنك استخدام FileCopyUtils من إطار الربيع.

فيما يلي ثلاث طرق يمكنك من خلالها نسخ الملفات بسهولة باستخدام سطر واحد من التعليمات البرمجية!

جافا7:

java.nio.file.Files#copy

private static void copyFileUsingJava7Files(File source, File dest) throws IOException {
    Files.copy(source.toPath(), dest.toPath());
}

أباتشي كومنز آي أو:

FileUtils#copyFile

private static void copyFileUsingApacheCommonsIO(File source, File dest) throws IOException {
    FileUtils.copyFile(source, dest);
}

الجوافة :

الملفات #نسخ

private static void copyFileUsingGuava(File source,File dest) throws IOException{
    Files.copy(source,dest);          
}
public static void copyFile(File src, File dst) throws IOException
{
    long p = 0, dp, size;
    FileChannel in = null, out = null;

    try
    {
        if (!dst.exists()) dst.createNewFile();

        in = new FileInputStream(src).getChannel();
        out = new FileOutputStream(dst).getChannel();
        size = in.size();

        while ((dp = out.transferFrom(in, p, size)) > 0)
        {
            p += dp;
        }
    }
    finally {
        try
        {
            if (out != null) out.close();
        }
        finally {
            if (in != null) in.close();
        }
    }
}

نسخة NIO مع المخزن المؤقت هي الأسرع وفقًا للاختبار الذي أجريته.انظر رمز العمل أدناه من مشروع اختباري في https://github.com/mhisoft/fastcopy

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.text.DecimalFormat;


public class test {

private static final int BUFFER = 4096*16;
static final DecimalFormat df = new DecimalFormat("#,###.##");
public static void nioBufferCopy(final File source, final File target )  {
    FileChannel in = null;
    FileChannel out = null;
    double  size=0;
    long overallT1 =  System.currentTimeMillis();

    try {
        in = new FileInputStream(source).getChannel();
        out = new FileOutputStream(target).getChannel();
        size = in.size();
        double size2InKB = size / 1024 ;
        ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER);

        while (in.read(buffer) != -1) {
            buffer.flip();

            while(buffer.hasRemaining()){
                out.write(buffer);
            }

            buffer.clear();
        }
        long overallT2 =  System.currentTimeMillis();
        System.out.println(String.format("Copied %s KB in %s millisecs", df.format(size2InKB),  (overallT2 - overallT1)));
    }
    catch (IOException e) {
        e.printStackTrace();
    }

    finally {
        close(in);
        close(out);
    }
}

private static void close(Closeable closable)  {
    if (closable != null) {
        try {
            closable.close();
        } catch (IOException e) {
            if (FastCopy.debug)
                e.printStackTrace();
        }    
    }
}

}

سريع ويعمل مع جميع اصدارات الجافا وكذلك الاندرويد:

private void copy(final File f1, final File f2) throws IOException {
    f2.createNewFile();

    final RandomAccessFile file1 = new RandomAccessFile(f1, "r");
    final RandomAccessFile file2 = new RandomAccessFile(f2, "rw");

    file2.getChannel().write(file1.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, f1.length()));

    file1.close();
    file2.close();
}

لقد تأخرت الحفلة قليلًا، ولكن فيما يلي مقارنة للوقت المستغرق لنسخ ملف باستخدام طرق نسخ الملفات المختلفة.لقد قمت بالتكرار عبر الطرق لمدة 10 مرات وحصلت على متوسط.يبدو أن نقل الملفات باستخدام تدفقات الإدخال والإخراج (IO) هو المرشح الأسوأ:

Comparison of file transfer using various methods

فيما يلي الطرق:

private static long fileCopyUsingFileStreams(File fileToCopy, File newFile) throws IOException {
    FileInputStream input = new FileInputStream(fileToCopy);
    FileOutputStream output = new FileOutputStream(newFile);
    byte[] buf = new byte[1024];
    int bytesRead;
    long start = System.currentTimeMillis();
    while ((bytesRead = input.read(buf)) > 0)
    {
        output.write(buf, 0, bytesRead);
    }
    long end = System.currentTimeMillis();

    input.close();
    output.close();

    return (end-start);
}

private static long fileCopyUsingNIOChannelClass(File fileToCopy, File newFile) throws IOException
{
    FileInputStream inputStream = new FileInputStream(fileToCopy);
    FileChannel inChannel = inputStream.getChannel();

    FileOutputStream outputStream = new FileOutputStream(newFile);
    FileChannel outChannel = outputStream.getChannel();

    long start = System.currentTimeMillis();
    inChannel.transferTo(0, fileToCopy.length(), outChannel);
    long end = System.currentTimeMillis();

    inputStream.close();
    outputStream.close();

    return (end-start);
}

private static long fileCopyUsingApacheCommons(File fileToCopy, File newFile) throws IOException
{
    long start = System.currentTimeMillis();
    FileUtils.copyFile(fileToCopy, newFile);
    long end = System.currentTimeMillis();
    return (end-start);
}

private static long fileCopyUsingNIOFilesClass(File fileToCopy, File newFile) throws IOException
{
    Path source = Paths.get(fileToCopy.getPath());
    Path destination = Paths.get(newFile.getPath());
    long start = System.currentTimeMillis();
    Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
    long end = System.currentTimeMillis();

    return (end-start);
}

العيب الوحيد الذي يمكنني رؤيته أثناء استخدام فئة قناة NIO هو أنني ما زلت لا أستطيع العثور على طريقة لإظهار تقدم نسخ الملفات المتوسطة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top