java có được kích thước tập tin hiệu quả


166

Trong khi googling, tôi thấy rằng việc sử dụng java.io.File#length()có thể chậm. FileChannelcó mộtsize() phương pháp có sẵn là tốt.

Có một cách hiệu quả trong java để có được kích thước tập tin?


7
bạn có thể cung cấp các liên kết nói rằng File.length () "có thể bị chậm" không?
matt b

1
xin lỗi, đây là liên kết javaperformancetuning.com/tips/rawtips.shtml tìm kiếm "Thông tin tệp như File.length () yêu cầu một cuộc gọi hệ thống và có thể bị chậm." Đó thực sự là một tuyên bố khó hiểu, dường như gần như cho rằng đó sẽ là một cuộc gọi hệ thống.
joshjdevl

25
Lấy chiều dài tập tin sẽ yêu cầu một cuộc gọi hệ thống bất kể bạn thực hiện nó như thế nào. Nó có thể chậm nếu trên mạng hoặc một số hệ thống tệp rất chậm khác. Không có cách nào nhanh hơn để có được nó hơn File.length () và định nghĩa "chậm" ở đây chỉ có nghĩa là đừng gọi nó là không cần thiết.
jsight

Tôi nghĩ đó là những gì GHad đã cố gắng thử nghiệm dưới đây. Kết quả của tôi là (Trên Ubuntu 8.04): chỉ cần một URL truy cập là nhanh nhất. 5 lần chạy, 50 lần lặp CHANNEL là khó hiểu nhanh nhất chưa? :) cho mục đích của tôi, tôi sẽ chỉ thực hiện một truy cập. mặc dù nó lạ rằng chúng tôi đã nhận được kết quả khác nhau
joshjdevl

1
Thao tác này có thể rất chậm nếu thông tin trên đĩa thay vì trong bộ đệm. (như chậm hơn 1000 lần), tuy nhiên, bạn có thể làm rất ít việc này ngoài việc đảm bảo thông tin bạn cần luôn ở trong bộ đệm (chẳng hạn như tải trước và có đủ bộ nhớ để lưu trong bộ nhớ)
Peter Lawrey

Câu trả lời:


102

Vâng, tôi đã cố gắng đo nó với mã dưới đây:

Đối với lượt chạy = 1 và lần lặp = 1, phương thức URL nhanh nhất được theo dõi bởi kênh. Tôi chạy cái này với một số tạm dừng mới khoảng 10 lần. Vì vậy, đối với truy cập một lần, sử dụng URL là cách nhanh nhất tôi có thể nghĩ đến:

LENGTH sum: 10626, per Iteration: 10626.0

CHANNEL sum: 5535, per Iteration: 5535.0

URL sum: 660, per Iteration: 660.0

Đối với lượt chạy = 5 và số lần lặp = 50, hình ảnh sẽ khác nhau.

LENGTH sum: 39496, per Iteration: 157.984

CHANNEL sum: 74261, per Iteration: 297.044

URL sum: 95534, per Iteration: 382.136

Tệp phải được lưu trữ các cuộc gọi đến hệ thống tệp, trong khi các kênh và URL có một số chi phí.

Mã số:

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
Có vẻ như cách URL là cách tốt nhất để truy cập một lần cho dù là XP hay linux. Greetz GHad
GHad

73
stream.available()không trả về chiều dài tập tin. Nó trả về số lượng byte có sẵn để đọc mà không chặn các luồng khác. Nó không nhất thiết phải có cùng số lượng byte với độ dài tệp. Để có được độ dài thực từ một luồng, bạn thực sự cần phải đọc nó (và đếm số byte đã đọc trong khi đó).
BalusC

11
Điểm chuẩn này là hay đúng hơn là giải thích của nó là không chính xác. Trong số lần lặp thấp, các bài kiểm tra sau sẽ tận dụng bộ đệm ẩn tệp của hệ điều hành. Trong các bài kiểm tra lặp lại cao hơn, xếp hạng là chính xác nhưng không phải vì File.length () đang lưu trữ một cái gì đó mà đơn giản chỉ vì 2 tùy chọn khác dựa trên cùng một phương thức nhưng thực hiện thêm công việc làm chậm chúng.
x4u

2
@Paolo, lưu trữ và tối ưu hóa truy cập hệ thống tệp là một trong những trách nhiệm chính của HĐH. faqs.org/docs/linux_admin/buffer-cache.html Để có kết quả đo điểm chuẩn tốt, bộ đệm phải được xóa trước mỗi lần chạy.
z0r

3
Ngoài những gì javadoc cho InputStream.av Available () nói, thực tế là phương thức có sẵn () trả về một int nên là một cờ đỏ chống lại cách tiếp cận URL. Hãy thử nó với tệp 3 GB và rõ ràng đó không phải là cách hợp lệ để xác định độ dài tệp.
Scrubbie

32

Điểm chuẩn được đưa ra bởi GHad đo lường rất nhiều thứ khác (chẳng hạn như phản xạ, khởi tạo vật thể, v.v.) bên cạnh việc lấy chiều dài. Nếu chúng ta cố gắng loại bỏ những thứ này thì trong một cuộc gọi, tôi sẽ nhận được những lần sau tính bằng micrô giây:

   tổng số tệp ___ 19.0, mỗi lần lặp ___ 19.0
    raf sum ___ 16.0, mỗi lần lặp ___ 16.0
kênh sum__273.0, mỗi lần lặp _273.0

Trong 100 lần chạy và 10000 lần lặp tôi nhận được:

   tập tin sum171767629.0, mỗi lần lặp _1.7676290000000001
    raf sum ___ 881284.0, mỗi lần lặp _0,8812840000000001
tổng kênh ___ 414286.0, mỗi lần lặp _0.414286

Tôi đã chạy mã sửa đổi sau đây làm đối số tên của tệp 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
Thật ra, trong khi bạn đúng khi nói nó đo lường các khía cạnh khác, tôi nên nói rõ hơn trong câu hỏi của mình. Tôi đang tìm cách để có được kích thước tệp của nhiều tệp và tôi muốn cách nhanh nhất có thể. Vì vậy, tôi thực sự cần phải tính đến việc tạo đối tượng và chi phí, vì đó là một kịch bản thực sự
joshjdevl

3
Khoảng 90% thời gian được dành cho thứ getResource đó. Tôi nghi ngờ bạn cần sử dụng sự phản chiếu để có được tên của một tệp có chứa một số mã byte Java.

20

Tất cả các trường hợp thử nghiệm trong bài đăng này đều thiếu sót khi họ truy cập cùng một tệp cho mỗi phương pháp được thử nghiệm. Vì vậy, bộ nhớ đệm đĩa đá trong đó kiểm tra 2 và 3 lợi ích từ. Để chứng minh quan điểm của mình, tôi đã lấy trường hợp thử nghiệm do GHAD cung cấp và thay đổi thứ tự liệt kê và dưới đây là kết quả.

Nhìn vào kết quả tôi nghĩ File.length () là người chiến thắng thực sự.

Thứ tự kiểm tra là thứ tự đầu ra. Bạn thậm chí có thể thấy thời gian thực hiện trên máy của tôi khác nhau giữa các lần thực hiện nhưng File.Lipse () khi không phải lần đầu tiên và phát sinh truy cập đĩa đầu tiên đã giành chiến thắng.

---
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

Khi tôi sửa đổi mã của bạn để sử dụng tệp được truy cập bởi một đường dẫn tuyệt đối thay vì tài nguyên, tôi nhận được một kết quả khác (cho 1 lần chạy, 1 lần lặp và tệp 100.000 byte - lần cho tệp 10 byte giống hệt với 100.000 byte )

Tổng LENGTH: 33, mỗi lần lặp: 33,0

KÊNH tổng: 3626, mỗi lần lặp: 3626.0

Tổng số URL: 294, mỗi lần lặp: 294.0


9

Để đáp ứng với điểm chuẩn của rgrig, thời gian để mở / đóng các phiên bản FileChannel & RandomAccessFile cũng cần được tính đến, vì các lớp này sẽ mở một luồng để đọc tệp.

Sau khi sửa đổi điểm chuẩn, tôi nhận được các kết quả này cho 1 lần lặp trên tệp 85 MB:

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

Đối với 10000 lần lặp trên cùng một tệp:

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

Nếu tất cả những gì bạn cần là kích thước tệp, file.length () là cách nhanh nhất để làm điều đó. Nếu bạn có kế hoạch sử dụng tệp cho các mục đích khác như đọc / viết, thì RAF dường như là một lựa chọn tốt hơn. Chỉ cần đừng quên đóng kết nối tập tin :-)

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

Tôi gặp vấn đề tương tự. Tôi cần lấy kích thước tệp và ngày sửa đổi của 90.000 tệp trên mạng chia sẻ. Sử dụng Java và tối giản nhất có thể, sẽ mất một thời gian rất dài. (Tôi cũng cần phải lấy URL từ tệp và đường dẫn của đối tượng. Vì vậy, nó thay đổi đôi chút, nhưng hơn một giờ.) Sau đó, tôi đã sử dụng một tệp thực thi Win32 gốc và thực hiện cùng một tác vụ, chỉ cần hủy tệp đường dẫn, sửa đổi và kích thước tới bàn điều khiển và thực hiện điều đó từ Java. Tốc độ thật tuyệt vời. Quá trình riêng và xử lý chuỗi của tôi để đọc dữ liệu có thể xử lý hơn 1000 mục một giây.

Vì vậy, mặc dù mọi người đã xếp hạng nhận xét trên, đây là một giải pháp hợp lệ và đã giải quyết được vấn đề của tôi. Trong trường hợp của tôi, tôi biết các thư mục tôi cần có kích thước trước thời hạn và tôi có thể chuyển nó trong dòng lệnh cho ứng dụng win32 của mình. Tôi đã đi từ giờ để xử lý một thư mục đến phút.

Vấn đề dường như cũng là cụ thể của Windows. OS X không có cùng một vấn đề và có thể truy cập thông tin tệp mạng nhanh như hệ điều hành có thể làm như vậy.

Xử lý tệp Java trên Windows là khủng khiếp. Truy cập đĩa cục bộ cho các tập tin là tốt mặc dù. Đó chỉ là chia sẻ mạng gây ra hiệu suất khủng khiếp. Windows có thể nhận thông tin về chia sẻ mạng và tính tổng kích thước trong một phút.

--Ben


3

Nếu bạn muốn kích thước tệp của nhiều tệp trong một thư mục, hãy sử dụng Files.walkFileTree. Bạn có thể có được kích thước từ BasicFileAttributesmà bạn sẽ nhận được.

Điều này nhanh hơn nhiều sau đó gọi .length()kết quả File.listFiles()hoặc sử dụng Files.size()trên kết quả của Files.newDirectoryStream(). Trong trường hợp thử nghiệm của tôi, nó đã nhanh hơn khoảng 100 lần.


FYI, Files.walkFileTreecó sẵn trên Android 26+.
Joshua Pinter

2

Trên thực tế, tôi nghĩ rằng "ls" có thể nhanh hơn. Chắc chắn có một số vấn đề trong Java liên quan đến việc lấy thông tin Tệp. Thật không may, không có phương pháp an toàn tương đương ls đệ quy cho Windows. (DIR / S của cmd.exe có thể bị lẫn lộn và tạo ra lỗi trong các vòng lặp vô hạn)

Trên XP, truy cập máy chủ trên mạng LAN, tôi mất 5 giây trong Windows để lấy số lượng tệp trong một thư mục (33.000) và tổng kích thước.

Khi tôi lặp lại đệ quy thông qua điều này trong Java, tôi phải mất hơn 5 phút. Tôi bắt đầu đo thời gian cần thiết để thực hiện file.length (), file.lastModified () và file.toURI () và điều tôi nhận thấy là 99% thời gian của tôi được thực hiện bởi 3 cuộc gọi đó. 3 cuộc gọi tôi thực sự cần phải làm ...

Sự khác biệt cho 1000 tệp là 15ms cục bộ so với 1800ms trên máy chủ. Quá trình quét đường dẫn máy chủ trong Java rất chậm. Nếu hệ điều hành gốc có thể nhanh chóng quét cùng thư mục đó, tại sao Java không thể?

Để thử nghiệm đầy đủ hơn, tôi đã sử dụng WineMerge trên XP để so sánh ngày sửa đổi và kích thước của các tệp trên máy chủ so với các tệp cục bộ. Điều này đã lặp lại trên toàn bộ cây thư mục gồm 33.000 tệp trong mỗi thư mục. Tổng thời gian, 7 giây. java: hơn 5 phút.

Vì vậy, tuyên bố và câu hỏi ban đầu từ OP là đúng và hợp lệ. Nó ít được chú ý khi làm việc với một hệ thống tập tin cục bộ. Thực hiện so sánh cục bộ thư mục với 33.000 mục mất 3 giây trong WinMerge và mất 32 giây cục bộ trong Java. Vì vậy, một lần nữa, java so với bản địa là một sự chậm lại 10 lần trong các thử nghiệm thô sơ này.

Java 1.6.0_22 (mới nhất), Gigabit LAN và các kết nối mạng, ping nhỏ hơn 1ms (cả hai trong cùng một công tắc)

Java chậm.


2
Điều này cũng có vẻ là hệ điều hành cụ thể. Thực hiện cùng một ứng dụng java đi sau cùng một thư mục từ OS X bằng samba, phải mất 26 giây để liệt kê toàn bộ 33.000 mục, kích cỡ và ngày. Vậy mạng Java chỉ chậm trên Windows? (OS X cũng là java 1.6.0_22.)
Ben Spink

2

Từ điểm chuẩn của GHad, có một vài vấn đề mọi người đã đề cập:

1> Giống như BalusC đã đề cập: stream.av Available () được truyền trong trường hợp này.

Vì có sẵn () trả về ước tính số lượng byte có thể được đọc (hoặc bỏ qua) từ luồng đầu vào này mà không bị chặn bởi lời gọi tiếp theo của phương thức cho luồng đầu vào này.

Vì vậy, 1 để loại bỏ URL phương pháp này.

2> Như StuartH đã đề cập - thứ tự chạy thử cũng tạo ra sự khác biệt về bộ đệm, vì vậy hãy loại bỏ nó bằng cách chạy thử nghiệm riêng.


Bây giờ bắt đầu kiểm tra:

Khi CHANNEL một người chạy một mình:

CHANNEL sum: 59691, per Iteration: 238.764

Khi LENGTH một người chạy một mình:

LENGTH sum: 48268, per Iteration: 193.072

Vì vậy, có vẻ như LENGTH là người chiến thắng ở đây:

@Override
public long getResult() throws Exception {
    File me = new File(FileSizeBench.class.getResource(
            "FileSizeBench.class").getFile());
    return me.length();
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.