سؤال

أثناء البحث على Google، أرى أن استخدام java.io.File#length() يمكن أن تكون بطيئة.FileChannel لديه size() الطريقة المتوفرة كذلك.

هل هناك طريقة فعالة في جافا للحصول على حجم الملف؟

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

المحلول

حسنًا، لقد حاولت قياس الأمر باستخدام الكود أدناه:

بالنسبة لعمليات التشغيل = 1 والتكرارات = 1، تكون طريقة URL هي الأسرع في معظم الأوقات تليها القناة.أقوم بتشغيل هذا مع بعض التوقف المؤقت حوالي 10 مرات.لذا، للوصول لمرة واحدة، فإن استخدام عنوان URL هو أسرع طريقة يمكنني التفكير فيها:

LENGTH sum: 10626, per Iteration: 10626.0

CHANNEL sum: 5535, per Iteration: 5535.0

URL sum: 660, per Iteration: 660.0

بالنسبة لعمليات التشغيل = 5 والتكرارات = 50، ترسم الصورة بشكل مختلف.

LENGTH sum: 39496, per Iteration: 157.984

CHANNEL sum: 74261, per Iteration: 297.044

URL sum: 95534, per Iteration: 382.136

يجب أن يقوم الملف بالتخزين المؤقت للاستدعاءات إلى نظام الملفات، بينما تحتوي القنوات وعنوان URL على بعض الحمل.

شفرة:

import java.io.*;
import java.net.*;
import java.util.*;

public enum FileSizeBench {

    LENGTH {
        @Override
        public long getResult() throws Exception {
            File me = new File(FileSizeBench.class.getResource(
                    "FileSizeBench.class").getFile());
            return me.length();
        }
    },
    CHANNEL {
        @Override
        public long getResult() throws Exception {
            FileInputStream fis = null;
            try {
                File me = new File(FileSizeBench.class.getResource(
                        "FileSizeBench.class").getFile());
                fis = new FileInputStream(me);
                return fis.getChannel().size();
            } finally {
                fis.close();
            }
        }
    },
    URL {
        @Override
        public long getResult() throws Exception {
            InputStream stream = null;
            try {
                URL url = FileSizeBench.class
                        .getResource("FileSizeBench.class");
                stream = url.openStream();
                return stream.available();
            } finally {
                stream.close();
            }
        }
    };

    public abstract long getResult() throws Exception;

    public static void main(String[] args) throws Exception {
        int runs = 5;
        int iterations = 50;

        EnumMap<FileSizeBench, Long> durations = new EnumMap<FileSizeBench, Long>(FileSizeBench.class);

        for (int i = 0; i < runs; i++) {
            for (FileSizeBench test : values()) {
                if (!durations.containsKey(test)) {
                    durations.put(test, 0l);
                }
                long duration = testNow(test, iterations);
                durations.put(test, durations.get(test) + duration);
                // System.out.println(test + " took: " + duration + ", per iteration: " + ((double)duration / (double)iterations));
            }
        }

        for (Map.Entry<FileSizeBench, Long> entry : durations.entrySet()) {
            System.out.println();
            System.out.println(entry.getKey() + " sum: " + entry.getValue() + ", per Iteration: " + ((double)entry.getValue() / (double)(runs * iterations)));
        }

    }

    private static long testNow(FileSizeBench test, int iterations)
            throws Exception {
        long result = -1;
        long before = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            if (result == -1) {
                result = test.getResult();
                //System.out.println(result);
            } else if ((result = test.getResult()) != result) {
                 throw new Exception("variance detected!");
             }
        }
        return (System.nanoTime() - before) / 1000;
    }

}

نصائح أخرى

يقيس المعيار الذي قدمه Ghad الكثير من الأشياء الأخرى (مثل الانعكاس وإنشاء كائنات وما إلى ذلك) إلى جانب الحصول على الطول.إذا حاولنا التخلص من هذه الأشياء، ففي مكالمة واحدة أحصل على الأوقات التالية بالميكروثانية:

   file sum___19.0, per Iteration___19.0
    raf sum___16.0, per Iteration___16.0
channel sum__273.0, per Iteration__273.0

مقابل 100 عملية و10000 تكرار أحصل على:

   file sum__1767629.0, per Iteration__1.7676290000000001
    raf sum___881284.0, per Iteration__0.8812840000000001
channel sum___414286.0, per Iteration__0.414286

لقد قمت بتشغيل الكود المعدل التالي معطيًا اسم ملف بحجم 100 ميجابايت كوسيطة.

import java.io.*;
import java.nio.channels.*;
import java.net.*;
import java.util.*;

public class FileSizeBench {

  private static File file;
  private static FileChannel channel;
  private static RandomAccessFile raf;

  public static void main(String[] args) throws Exception {
    int runs = 1;
    int iterations = 1;

    file = new File(args[0]);
    channel = new FileInputStream(args[0]).getChannel();
    raf = new RandomAccessFile(args[0], "r");

    HashMap<String, Double> times = new HashMap<String, Double>();
    times.put("file", 0.0);
    times.put("channel", 0.0);
    times.put("raf", 0.0);

    long start;
    for (int i = 0; i < runs; ++i) {
      long l = file.length();

      start = System.nanoTime();
      for (int j = 0; j < iterations; ++j)
        if (l != file.length()) throw new Exception();
      times.put("file", times.get("file") + System.nanoTime() - start);

      start = System.nanoTime();
      for (int j = 0; j < iterations; ++j)
        if (l != channel.size()) throw new Exception();
      times.put("channel", times.get("channel") + System.nanoTime() - start);

      start = System.nanoTime();
      for (int j = 0; j < iterations; ++j)
        if (l != raf.length()) throw new Exception();
      times.put("raf", times.get("raf") + System.nanoTime() - start);
    }
    for (Map.Entry<String, Double> entry : times.entrySet()) {
        System.out.println(
            entry.getKey() + " sum: " + 1e-3 * entry.getValue() +
            ", per Iteration: " + (1e-3 * entry.getValue() / runs / iterations));
    }
  }
}

جميع حالات الاختبار في هذا المنشور معيبة لأنها تصل إلى نفس الملف لكل طريقة تم اختبارها.لذلك يبدأ التخزين المؤقت على القرص حيث يستفيد الاختباران 2 و 3.لإثبات وجهة نظري أخذت حالة الاختبار المقدمة من GHAD وقمت بتغيير ترتيب التعداد وفيما يلي النتائج.

بالنظر إلى النتيجة، أعتقد أن File.length() هو الفائز حقًا.

ترتيب الاختبار هو ترتيب الإخراج.يمكنك حتى أن ترى أن الوقت المستغرق على جهازي يختلف بين عمليات التنفيذ ولكن File.Length() عندما لا يكون الأول، ويفوز بالوصول الأول إلى القرص.

---
LENGTH sum: 1163351, per Iteration: 4653.404
CHANNEL sum: 1094598, per Iteration: 4378.392
URL sum: 739691, per Iteration: 2958.764

---
CHANNEL sum: 845804, per Iteration: 3383.216
URL sum: 531334, per Iteration: 2125.336
LENGTH sum: 318413, per Iteration: 1273.652

--- 
URL sum: 137368, per Iteration: 549.472
LENGTH sum: 18677, per Iteration: 74.708
CHANNEL sum: 142125, per Iteration: 568.5

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

مجموع الطول:33، لكل تكرار:33.0

مجموع القناة:3626، لكل تكرار:3626.0

مجموع عنوان URL:294، لكل تكرار:294.0

استجابةً لمعيار rgrig، يجب أيضًا أخذ الوقت المستغرق لفتح/إغلاق مثيلات FileChannel وRandomAccessFile في الاعتبار، حيث ستفتح هذه الفئات دفقًا لقراءة الملف.

بعد تعديل المعيار، حصلت على هذه النتائج لتكرار واحد على ملف بحجم 85 ميجابايت:

file totalTime: 48000 (48 us)
raf totalTime: 261000 (261 us)
channel totalTime: 7020000 (7 ms)

لـ 10000 تكرار على نفس الملف:

file totalTime: 80074000 (80 ms)
raf totalTime: 295417000 (295 ms)
channel totalTime: 368239000 (368 ms)

إذا كان كل ما تحتاجه هو حجم الملف، فإن file.length() هو أسرع طريقة للقيام بذلك.إذا كنت تخطط لاستخدام الملف لأغراض أخرى مثل القراءة/الكتابة، فيبدو أن RAF هو الرهان الأفضل.فقط لا تنس إغلاق اتصال الملف :-)

import java.io.File;
import java.io.FileInputStream;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;

public class FileSizeBench
{    
    public static void main(String[] args) throws Exception
    {
        int iterations = 1;
        String fileEntry = args[0];

        Map<String, Long> times = new HashMap<String, Long>();
        times.put("file", 0L);
        times.put("channel", 0L);
        times.put("raf", 0L);

        long fileSize;
        long start;
        long end;
        File f1;
        FileChannel channel;
        RandomAccessFile raf;

        for (int i = 0; i < iterations; i++)
        {
            // file.length()
            start = System.nanoTime();
            f1 = new File(fileEntry);
            fileSize = f1.length();
            end = System.nanoTime();
            times.put("file", times.get("file") + end - start);

            // channel.size()
            start = System.nanoTime();
            channel = new FileInputStream(fileEntry).getChannel();
            fileSize = channel.size();
            channel.close();
            end = System.nanoTime();
            times.put("channel", times.get("channel") + end - start);

            // raf.length()
            start = System.nanoTime();
            raf = new RandomAccessFile(fileEntry, "r");
            fileSize = raf.length();
            raf.close();
            end = System.nanoTime();
            times.put("raf", times.get("raf") + end - start);
        }

        for (Map.Entry<String, Long> entry : times.entrySet()) {
            System.out.println(entry.getKey() + " totalTime: " + entry.getValue() + " (" + getTime(entry.getValue()) + ")");
        }
    }

    public static String getTime(Long timeTaken)
    {
        if (timeTaken < 1000) {
            return timeTaken + " ns";
        } else if (timeTaken < (1000*1000)) {
            return timeTaken/1000 + " us"; 
        } else {
            return timeTaken/(1000*1000) + " ms";
        } 
    }
}

لقد واجهت نفس هذه المشكلة.كنت بحاجة للحصول على حجم الملف وتاريخ التعديل لـ 90.000 ملف على مشاركة الشبكة.باستخدام Java، وبأقل قدر ممكن من البساطة، سيستغرق الأمر وقتًا طويلاً جدًا.(كنت بحاجة للحصول على عنوان URL من الملف، ومسار الكائن أيضًا.لذلك يختلف الأمر إلى حد ما، ولكن أكثر من ساعة.) ثم استخدمت ملف Win32 الأصلي القابل للتنفيذ، وقمت بنفس المهمة، فقط أسقطت مسار الملف وتعديله وحجمه إلى وحدة التحكم، وقمت بتنفيذ ذلك من Java.كانت السرعة مذهلة.يمكن للعملية الأصلية ومعالجة السلسلة الخاصة بي لقراءة البيانات معالجة أكثر من 1000 عنصر في الثانية.

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

يبدو أيضًا أن المشكلة تتعلق بنظام التشغيل Windows.لم يكن لدى OS X نفس المشكلة ويمكنه الوصول إلى معلومات ملفات الشبكة بأسرع ما يمكن لنظام التشغيل القيام بذلك.

التعامل مع ملفات Java على نظام التشغيل Windows أمر فظيع.الوصول إلى القرص المحلي للملفات أمر جيد بالرغم من ذلك.لقد كانت مشاركات الشبكة فقط هي التي تسببت في الأداء الرهيب.يمكن لنظام التشغيل Windows الحصول على معلومات حول مشاركة الشبكة وحساب الحجم الإجمالي في أقل من دقيقة أيضًا.

--بن

إذا كنت تريد حجم الملف لملفات متعددة في الدليل، استخدم Files.walkFileTree.يمكنك الحصول على الحجم من BasicFileAttributes التي سوف تتلقى.

وهذا أسرع بكثير من الاتصال .length() على نتيجة File.listFiles() أو باستخدام Files.size() على نتيجة Files.newDirectoryStream().في حالات الاختبار الخاصة بي، كان الأمر أسرع بنحو 100 مرة.

في الواقع، أعتقد أن "ls" قد تكون أسرع.هناك بالتأكيد بعض المشكلات في Java التي تتعامل مع الحصول على معلومات الملف.لسوء الحظ لا توجد طريقة آمنة مكافئة لـ ls العودية لنظام التشغيل Windows.(يمكن الخلط بين DIR /S الخاص بـ cmd.exe وإنشاء أخطاء في حلقات لا نهائية)

في نظام التشغيل XP، يستغرق الوصول إلى خادم على الشبكة المحلية 5 ثوانٍ في نظام التشغيل Windows للحصول على عدد الملفات الموجودة في مجلد (33000)، والحجم الإجمالي.

عندما أكرر هذا بشكل متكرر في Java، يستغرق الأمر أكثر من 5 دقائق.لقد بدأت في قياس الوقت الذي يستغرقه تنفيذ file.length() وfile.lastModified() وfile.toURI() وما وجدته هو أن 99% من وقتي أستغرقه تلك المكالمات الثلاثة.المكالمات الثلاث التي يجب أن أقوم بها فعلاً...

الفرق بين 1000 ملف هو 15 مللي ثانية محليًا مقابل 1800 مللي ثانية على الخادم.يعد فحص مسار الخادم في Java بطيئًا بشكل يبعث على السخرية.إذا كان نظام التشغيل الأصلي سريعًا في فحص نفس المجلد، فلماذا لا تستطيع Java ذلك؟

كاختبار أكثر اكتمالًا، استخدمت WineMerge على نظام XP لمقارنة تاريخ التعديل وحجم الملفات الموجودة على الخادم مقابل الملفات المحلية.تم تكرار ذلك عبر شجرة الدليل بأكملها التي تضم 33000 ملف في كل مجلد.الوقت الإجمالي 7 ثواني.جافا:أكثر من 5 دقائق.

لذا فإن البيان والسؤال الأصليين من OP صحيحان وصالحان.إنه أقل وضوحًا عند التعامل مع نظام الملفات المحلي.يستغرق إجراء مقارنة محلية للمجلد الذي يحتوي على 33000 عنصر 3 ثوانٍ في WinMerge، ويستغرق 32 ثانية محليًا في Java.مرة أخرى، جافا مقابل الأصلي هو تباطؤ 10X في هذه الاختبارات البدائية.

Java 1.6.0_22 (الأحدث)، Gigabit LAN، واتصالات الشبكة، اختبار ping أقل من 1 مللي ثانية (كلاهما في نفس المحول)

جافا بطيئة.

من معيار Ghad، هناك بعض المشكلات التي ذكرها الأشخاص:

1> مثل BalusC المذكور:يتم تدفق الدفقstream.available() في هذه الحالة.

لأن متاح () يُرجع ملفًا تقدير عدد البايتات التي يمكن قراءتها (أو تخطيها) من دفق الإدخال هذا دون حظر بواسطة الاستدعاء التالي لطريقة دفق الإدخال هذا.

لذا عليك أولاً إزالة عنوان URL بهذه الطريقة.

2> كما ذكر ستيوارت - يؤدي ترتيب تشغيل الاختبار أيضًا إلى حدوث فرق في ذاكرة التخزين المؤقت، لذا يمكنك التخلص من ذلك عن طريق تشغيل الاختبار بشكل منفصل.


الآن ابدأ الاختبار:

عندما تعمل القناة الأولى بمفردها:

CHANNEL sum: 59691, per Iteration: 238.764

عندما يتم تشغيل LENGTH واحدًا بمفرده:

LENGTH sum: 48268, per Iteration: 193.072

لذا يبدو أن الطول هو الفائز هنا:

@Override
public long getResult() throws Exception {
    File me = new File(FileSizeBench.class.getResource(
            "FileSizeBench.class").getFile());
    return me.length();
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top