[java] Java는 파일 크기를 효율적으로 얻습니다.

인터넷 검색 중에 사용 java.io.File#length()속도가 느릴 수 있습니다.
FileChannel있다size() 잘으로 사용할 방법을.

Java에서 파일 크기를 얻는 효율적인 방법이 있습니까?



답변

글쎄, 아래 코드를 사용하여 측정하려고했습니다.

런 = 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가 제공 한 벤치 마크는 길이를 얻는 것 외에도 많은 다른 것들 (반사, 인스턴스화 객체 등)을 측정합니다. 우리가 이러한 것들을 제거하려고하면 한 번의 호출에 대해 다음 시간을 마이크로 초 단위로 얻습니다.

   반복 당 파일 합계 ___ 19.0, ___ 19.0
    반복마다 raf sum ___ 16.0, ___ 16.0
반복 당 채널 합계 __273.0, __ 273.0

100 회 실행 및 10000 회 반복의 경우

   반복 당 파일 sum__1767629.0, __ 1.7676290000000001
    반복 당 raf sum ___ 881284.0, __ 0.8812840000000001
반복 당 채널 합계 ___ 414286.0, __ 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));
    }
  }
}


답변

이 게시물의 모든 테스트 사례는 테스트 된 각 방법에 대해 동일한 파일에 액세스 할 때 결함이 있습니다. 따라서 테스트 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


답변

리소스 대신 절대 경로로 액세스 된 파일을 사용하도록 코드를 수정하면 다른 결과가 나타납니다 (1 실행, 1 반복 및 100,000 바이트 파일의 경우 10 바이트 파일의 시간은 100,000 바이트와 동일 함) )

길이 합계 : 33, 반복 당 : 33.0

채널 합계 : 3626, 반복 당 : 3626.0

URL 합계 : 294, 반복 당 : 294.0


답변

rgrig의 벤치 마크에 따라 FileChannel 및 RandomAccessFile 인스턴스를 열고 닫는 데 걸리는 시간도 고려해야합니다. 이러한 클래스는 파일을 읽기위한 스트림을 엽니 다.

벤치 마크를 수정 한 후 85MB 파일에서 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 ()가 가장 빠른 방법입니다. 파일을 읽기 / 쓰기와 같은 다른 목적으로 사용하려는 경우 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에는 동일한 문제가 없었으며 OS에서 가능한 한 빨리 네트워크 파일 정보에 액세스 할 수있었습니다.

Windows에서 Java 파일 처리가 끔찍합니다. 파일에 대한 로컬 디스크 액세스는 괜찮습니다. 끔찍한 성능을 유발 한 것은 네트워크 공유였습니다. Windows는 네트워크 공유에 대한 정보를 얻고 1 분 안에 전체 크기를 계산할 수도 있습니다.

-벤


답변

디렉토리에있는 여러 파일의 파일 크기를 원하면을 사용하십시오 Files.walkFileTree. 당신은 BasicFileAttributes당신이받을 크기를 얻을 수 있습니다 .

이것은 훨씬 더 빨리 다음 호출 .length()의 결과에 File.listFiles()또는 사용 Files.size()의 결과에 Files.newDirectoryStream(). 내 테스트 사례에서는 약 100 배 빠릅니다.