जावा को फ़ाइल का आकार कुशलता से मिलता है


166

गुगली करते समय, मैं देखता हूं कि प्रयोग java.io.File#length()धीमा हो सकता है। FileChannelएक size()तरीका है जो उपलब्ध भी है।

क्या फ़ाइल आकार प्राप्त करने के लिए जावा में एक कुशल तरीका है?


7
क्या आप यह कहते हुए लिंक प्रदान कर सकते हैं कि File.length () "धीमी हो सकती है"?
b पर मैट बी

1
क्षमा करें, यहाँ लिंक javaperformancetuning.com/tips/rawtips.shtml है "फ़ाइल जानकारी जैसे कि File.length () के लिए एक सिस्टम कॉल की आवश्यकता है और यह धीमा हो सकता है।" यह वास्तव में एक भ्रमित करने वाला कथन है, ऐसा लगता है कि यह सिस्टम कॉल होगा।
joshjdevl

25
फ़ाइल की लंबाई प्राप्त करने के लिए सिस्टम कॉल की आवश्यकता होगी चाहे आप इसे कैसे भी करें। यह धीमा हो सकता है यदि इसका नेटवर्क या कुछ अन्य बहुत धीमी गति से फाइल सिस्टम है। File.length () की तुलना में इसे प्राप्त करने का कोई तेज़ तरीका नहीं है, और यहां "धीमी" की परिभाषा का अर्थ है कि इसे अनावश्यक रूप से कॉल न करें।
२०:२० पर १२

मुझे लगता है कि यही गाद नीचे परीक्षण करने की कोशिश कर रहा था। मेरे परिणाम हैं (ubuntu 8.04 पर): केवल एक एक्सेस URL सबसे तेज़ है। 5 रन, 50 पुनरावृत्तियों चैनल अभी तक सबसे तेजी से भ्रमित है? :) हालांकि मेरे उद्देश्यों के लिए, मैं सिर्फ एक ही काम करूंगा। हालांकि यह अजीब है? कि हमें अलग-अलग परिणाम मिले
joshjdevl

1
यदि कैश की बजाय डिस्क पर जानकारी है, तो यह ऑपरेशन बहुत धीमा हो सकता है। (जैसे 1000x धीमे) हालांकि, ऐसा बहुत कम होता है, जिसके बारे में आपको यह जानकारी सुनिश्चित करने के अलावा अन्य काम करने की आवश्यकता हो, जो हमेशा कैश में हो (जैसे कि इसे पहले से लोड करना और पर्याप्त मेमोरी होना इसलिए यह मेमोरी में रहता है)
पीटर लॉरी ऑक्ट

जवाबों:


102

खैर, मैंने इसे नीचे दिए गए कोड से मापने की कोशिश की:

रन = 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;
    }

}

1
लगता है कि URL की तरह ही सिंगल एक्सेस के लिए सबसे अच्छा है चाहे उसका XP हो या लिनक्स। ग्रीटज गाहड़
गाहद

73
stream.available()फ़ाइल की लंबाई वापस नहीं करता है। यह बाइट्स की मात्रा लौटाता है जो अन्य धाराओं को अवरुद्ध किए बिना पढ़ने के लिए उपलब्ध हैं। यह जरूरी नहीं कि फ़ाइल की लंबाई के समान बाइट्स की एक ही राशि हो। एक स्ट्रीम से वास्तविक लंबाई प्राप्त करने के लिए, आपको वास्तव में इसे पढ़ने की जरूरत है (और इस बीच रीड बाइट्स की गणना करें)।
बालुसक

11
यह बेंचमार्क है या इसकी व्याख्या सही नहीं है। कम पुनरावृत्ति में बाद के परीक्षणों की गणना ऑपरेटिंग सिस्टम की फ़ाइल कैशिंग का लाभ उठाती है। उच्च पुनरावृत्तियों में परीक्षण रैंकिंग सही है, लेकिन इसलिए नहीं कि File.length () कुछ कैशिंग कर रही है, लेकिन सिर्फ इसलिए कि अन्य 2 विकल्प एक ही विधि पर आधारित हैं, लेकिन अतिरिक्त काम करते हैं जो उन्हें धीमा कर देता है।
x4u

2
@ पाओलो, कैशिंग और ऑप्टिमाइज़िंग फाइल सिस्टम एक्सेस एक ओएस की प्रमुख जिम्मेदारियों में से एक है। faqs.org/docs/linux_admin/buffer-cache.html अच्छे बेंचमार्किंग परिणाम प्राप्त करने के लिए, प्रत्येक रन से पहले कैश को साफ़ करना चाहिए।
z0r

3
InputStream.available () के लिए javadoc क्या कहता है, से परे, तथ्य यह है कि उपलब्ध () विधि एक int देता है, URL दृष्टिकोण के खिलाफ एक लाल झंडा होना चाहिए। इसे 3GB फ़ाइल के साथ आज़माएँ और यह स्पष्ट होगा कि यह फ़ाइल की लंबाई निर्धारित करने का एक वैध तरीका नहीं है।
स्क्रबबी

32

जीएचएडी द्वारा दिया गया बेंचमार्क लंबाई पाने के अलावा बहुत से अन्य सामान (जैसे प्रतिबिंब, तात्कालिक वस्तुएं आदि) को मापता है। यदि हम इन चीजों से छुटकारा पाने की कोशिश करते हैं, तो एक कॉल के लिए मुझे माइक्रोसेकंड में निम्नलिखित समय मिलते हैं:

   फ़ाइल राशि ___ 19.0, प्रति Iteration ___ 19.0
    रफ राशि ___ 16.0, प्रति Iteration ___ 16.0
चैनल sum__273.0, प्रति Iteration__273.0

100 रन और 10000 पुनरावृत्तियों के लिए:

   फ़ाइल sum__1767629.0, प्रति Iteration__1.7676290000000001
    रफ राशि ___ 881284.0, प्रति Iteration__0.8812840000000001
चैनल राशि ___ 414286.0, प्रति Iteration__0.414286

मैंने एक तर्क के रूप में एक 100MB फ़ाइल का नाम देते हुए निम्न संशोधित कोड चलाया।

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

3
वास्तव में, जबकि आप यह कहने में सही हैं कि यह अन्य पहलुओं को मापता है, मुझे अपने प्रश्न में अधिक स्पष्ट होना चाहिए। मैं कई फ़ाइलों का फ़ाइल आकार प्राप्त करना चाह रहा हूं, और मैं सबसे तेज संभव तरीका चाहता हूं। इसलिए मुझे वास्तव में ऑब्जेक्ट निर्माण और ओवरहेड को ध्यान में रखने की आवश्यकता है, क्योंकि यह एक वास्तविक परिदृश्य है
joshjdevl

3
लगभग 90% समय उस getResource चीज़ में व्यतीत होता है। मुझे संदेह है कि आपको किसी फ़ाइल का नाम प्राप्त करने के लिए प्रतिबिंब का उपयोग करने की आवश्यकता है जिसमें कुछ जावा बाइटकोड है।

20

इस पद के सभी परीक्षण मामले त्रुटिपूर्ण हैं क्योंकि वे परीक्षण की गई प्रत्येक विधि के लिए एक ही फाइल का उपयोग करते हैं। इसलिए डिस्क कैशिंग किक जिसमें परीक्षण 2 और 3 से लाभ होता है। अपनी बात को साबित करने के लिए मैंने जीएचएडी द्वारा प्रदान किया गया परीक्षण मामला लिया और गणना के क्रम को बदल दिया और नीचे परिणाम हैं।

परिणाम को देखते हुए मुझे लगता है कि 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

9

जब मैं संसाधन के बजाय किसी पूर्ण पथ द्वारा एक्सेस की गई फ़ाइल का उपयोग करने के लिए आपके कोड को संशोधित करता हूं, तो मुझे एक अलग परिणाम मिलता है (1 रन, 1 पुनरावृत्ति, और 100,000 बाइट फ़ाइल के लिए - 10 बाइट फ़ाइल के लिए समय 100,000 बाइट्स के समान होता है )

LENGTH राशि: 33, प्रति Iteration: 33.0

चैनल राशि: 3626, प्रति Iteration: 3626.0

URL राशि: 294, प्रति Iteration: 294.0


9

रीग्रिग के बेंचमार्क के जवाब में, FileChannel & RandomAccessFile इंस्टेंस को खोलने / बंद करने में लगने वाले समय को भी ध्यान में रखना होगा, क्योंकि ये क्लास फाइल पढ़ने के लिए एक स्ट्रीम खोलेंगे।

बेंचमार्क संशोधित करने के बाद, मुझे 85 एमबी फ़ाइल पर 1 पुनरावृत्तियों के लिए ये परिणाम मिले:

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 () यह करने का सबसे तेज़ तरीका है। यदि आप पढ़ने / लिखने जैसे अन्य उद्देश्यों के लिए फ़ाइल का उपयोग करने की योजना बनाते हैं, तो आरएएफ एक बेहतर दांव लगता है। बस फ़ाइल कनेक्शन बंद करने के लिए मत भूलना :-)

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";
        } 
    }
}

8

मैं इसी मुद्दे में भाग गया। मुझे एक नेटवर्क शेयर पर फ़ाइल का आकार और 90,000 फ़ाइलों की संशोधित तिथि प्राप्त करने की आवश्यकता थी। जावा का उपयोग करना, और जितना संभव हो उतना न्यूनतम होना, इसमें बहुत लंबा समय लगेगा। (मुझे फ़ाइल से URL प्राप्त करने की आवश्यकता है, और साथ ही ऑब्जेक्ट का पथ। इसलिए इसकी विविधता कुछ हद तक, लेकिन एक घंटे से अधिक।) फिर मैंने एक देशी Win32 निष्पादन योग्य का उपयोग किया, और एक ही कार्य किया, बस फ़ाइल को डंप करना। पथ, संशोधित और कंसोल को आकार, और जावा से निष्पादित। गति अद्भुत थी। देशी प्रक्रिया, और डेटा को पढ़ने के लिए मेरा स्ट्रिंग हैंडलिंग एक सेकंड में 1000 से अधिक आइटम संसाधित कर सकता है।

इसलिए भले ही लोगों ने उपरोक्त टिप्पणी को स्थान दिया हो, यह एक वैध समाधान है, और इसने मेरी समस्या को हल किया है। मेरे मामले में मुझे पता था कि जिन फ़ोल्डरों की मुझे समय से पहले आकार की आवश्यकता थी, और मैं कमांड लाइन में अपने win32 ऐप को पारित कर सकता हूं। मैं एक डायरेक्टरी को मिनटों में प्रोसेस करने में घंटों से चला गया।

यह मुद्दा भी विंडोज के लिए विशिष्ट प्रतीत होता है। ओएस एक्स में एक ही मुद्दा नहीं था और जितनी तेजी से ओएस ऐसा कर सकता था उतनी तेजी से नेटवर्क फ़ाइल जानकारी तक पहुंच सकता है।

विंडोज़ पर जावा फ़ाइल हैंडलिंग बहुत ही भयानक है। फ़ाइलों के लिए स्थानीय डिस्क पहुँच हालांकि ठीक है। यह सिर्फ नेटवर्क शेयर था जिसने भयानक प्रदर्शन किया। विंडोज नेटवर्क शेयर पर जानकारी प्राप्त कर सकता है और एक मिनट के अंदर कुल आकार की गणना भी कर सकता है।

--Ben


3

यदि आप एक डायरेक्टरी में कई फाइलों का फाइल साइज चाहते हैं, तो उपयोग करें Files.walkFileTree। आप BasicFileAttributesजो प्राप्त करेंगे उससे आकार प्राप्त कर सकते हैं।

यह बहुत तेज है तो .length()परिणाम पर File.listFiles()या उपयोग Files.size()करने के परिणाम पर कॉल कर रहा है Files.newDirectoryStream()। मेरे परीक्षण के मामलों में यह लगभग 100 गुना तेज था।


FYI करें, Files.walkFileTreeएंड्रॉइड 26+ पर उपलब्ध है।
जोशुआ पिंटर

2

दरअसल, मुझे लगता है कि "एलएस" तेज हो सकता है। फ़ाइल जानकारी प्राप्त करने के लिए जावा में निश्चित रूप से कुछ समस्याएं हैं। दुर्भाग्य से विंडोज के लिए पुनरावर्ती एलएस का कोई समान सुरक्षित तरीका नहीं है। (cmd.exe का DIR / S भ्रमित हो सकता है और अनंत छोरों में त्रुटियां उत्पन्न कर सकता है)

XP पर, लैन पर एक सर्वर तक पहुंचने पर, मुझे एक फ़ोल्डर (33,000) में फ़ाइलों की संख्या और कुल आकार प्राप्त करने के लिए विंडोज में 5 सेकंड लगते हैं।

जब मैं जावा में इसके माध्यम से पुनरावृत्ति करता हूं, तो मुझे 5 मिनट से अधिक समय लगता है। मैंने फ़ाइल को करने में लगने वाले समय को मापना शुरू कर दिया था। डायनेमिक (), file.lastModified (), और file.toURI () और जो मैंने पाया है कि मेरा 99% समय उन 3 कॉलों द्वारा लिया जाता है। 3 कॉल मुझे वास्तव में करने की आवश्यकता है ...

1000 फ़ाइलों के लिए अंतर सर्वर पर 15ms स्थानीय बनाम 1800ms है। जावा में सर्वर पथ स्कैनिंग हास्यास्पद रूप से धीमी है। यदि मूल ओएस उसी फ़ोल्डर को स्कैन करने में तेज हो सकता है, तो जावा क्यों नहीं कर सकता है?

एक अधिक पूर्ण परीक्षण के रूप में, मैंने संशोधित तिथि की तुलना करने के लिए XP पर वाइनमार्गर का उपयोग किया, और सर्वर पर फ़ाइलों का आकार स्थानीय रूप से फ़ाइलों के बनाम। यह प्रत्येक फ़ोल्डर में 33,000 फ़ाइलों की संपूर्ण निर्देशिका ट्री पर चलना था। कुल समय, 7 सेकंड। जावा: 5 मिनट से अधिक।

इसलिए ओपी का मूल कथन और प्रश्न सत्य है, और मान्य है। स्थानीय फ़ाइल सिस्टम के साथ काम करते समय इसका कम ध्यान दिया जा सकता है। 33,000 आइटम के साथ फ़ोल्डर की स्थानीय तुलना करने से WinMerge में 3 सेकंड लगते हैं, और जावा में स्थानीय रूप से 32 सेकंड लगते हैं। तो फिर, इन बनाम अल्पविकसित परीक्षणों में जावा बनाम देशी 10x मंदी है।

जावा 1.6.0_22 (नवीनतम), गीगाबिट लैन और नेटवर्क कनेक्शन, पिंग 1ms (दोनों एक ही स्विच में) से कम है

जावा धीमा है।


2
यह भी ओएस विशिष्ट प्रतीत होता है। ओएस एक्स से एक ही फ़ोल्डर के बाद जा रहे एक ही जावा ऐप को सांबा का उपयोग करते हुए पूरे 33,000 आइटम, आकार और तिथियों की सूची में 26 सेकंड लगे। तो नेटवर्क जावा सिर्फ विंडोज पर धीमा है? (ओएस एक्स जावा 1.6.0_22 भी था।)
बेन स्पिंक

2

GHAD के बेंचमार्क से, कुछ लोगों ने उल्लेख किया है:

1> जैसे बालुसक ने उल्लेख किया है: इस मामले में धारा। उपलब्ध () प्रवाहित है।

क्योंकि उपलब्ध () इस इनपुट स्ट्रीम के लिए एक विधि के अगले मंगलाचरण द्वारा अवरुद्ध किए बिना इस इनपुट स्ट्रीम से पढ़ी जा सकने वाली बाइट्स की संख्या का अनुमान लगाता है (या खत्म हो सकता है)।

तो URL इस दृष्टिकोण को हटाने के लिए 1।

2> जैसा कि स्टुअर्ट ने उल्लेख किया है - टेस्ट रन का क्रम भी कैश अंतर बनाता है, इसलिए टेस्ट को अलग से चलाएं।


अब परीक्षण शुरू करें:

जब CHANNEL एक अकेले चलता है:

CHANNEL sum: 59691, per Iteration: 238.764

जब LENGTH एक अकेले चलता है:

LENGTH sum: 48268, per Iteration: 193.072

ऐसा लग रहा है कि LENGTH एक यहाँ विजेता है:

@Override
public long getResult() throws Exception {
    File me = new File(FileSizeBench.class.getResource(
            "FileSizeBench.class").getFile());
    return me.length();
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.