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


166

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

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


7
File.length () "느려질 수 있습니다"라는 링크를 제공 할 수 있습니까?
matt b

1
죄송합니다, 여기에 링크 javaperformancetuning.com/tips/rawtips.shtml "File.length ()와 같은 파일 정보는 시스템 호출을 필요로하며 느려질 수 있습니다." 실제로 혼란스러운 진술이므로 시스템 호출이라고 거의 가정합니다.
joshjdevl

25
파일 길이를 얻으려면 방법에 관계없이 시스템 호출이 필요합니다. 네트워크 나 다른 매우 느린 파일 시스템을 사용하면 속도가 느려질 수 있습니다. File.length ()보다 빠른 방법은 없으며 여기서 "느린"의 정의는 불필요하게 호출하지 않는다는 의미입니다.
jsight

나는 그것이 GHad가 아래에서 테스트하려고했던 것이라고 생각합니다. 내 결과는 (ubuntu 8.04에서) : 하나의 액세스 URL만이 가장 빠릅니다. 5 번의 달리기, 50 번의 반복 CHANNEL은 아직 가장 혼란 스럽습니까? :) 내 목적으로, 나는 단지 하나의 액세스를하고있을 것입니다. 이상하지만? 우리는 다른 결과를 가지고 있음
joshjdevl

1
정보가 캐시가 아닌 디스크에있는 경우이 작업이 매우 느려질 수 있습니다. (1000 배 더 느리게) 그러나 필요한 정보가 항상 캐시에 있는지 확인하는 것 (예 : 사전로드 및 메모리가 충분한 메모리를 유지하는 등) 외에는이 작업을 수행 할 수있는 작업이 거의 없습니다.
Peter Lawrey

답변:


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
XP 방식이든 Linux이든 URL 방식이 단일 액세스에 가장 적합한 것 같습니다. Greetz GHad
GHad

73
stream.available()파일 길이를 반환하지 않습니다. 다른 스트림을 차단하지 않고 읽을 수있는 바이트 수를 반환합니다. 반드시 파일 길이와 바이트 수가 같을 필요는 없습니다. 스트림에서 실제 길이를 얻으려면 실제로 읽어야 합니다 (그리고 읽은 바이트를 계산하십시오).
BalusC

11
이 벤치 마크가 정확하거나 오히려 그 해석이 올바르지 않습니다. 반복 횟수가 적 으면 나중에 테스트 할 때 운영 체제의 파일 캐싱을 활용합니다. 높은 반복 테스트에서 순위는 정확하지만 File.length ()가 무언가를 캐싱하기 때문이 아니라 다른 두 옵션이 동일한 방법을 기반으로하지만 추가 작업을 수행하여 속도를 늦추기 때문에 간단합니다.
x4u

2
@Paolo, 파일 시스템 액세스 캐싱 및 최적화는 OS의 주요 책임 중 하나입니다. faqs.org/docs/linux_admin/buffer-cache.html 좋은 벤치마킹 결과를 얻으려면 각 실행 전에 캐시를 비워야합니다.
z0r

3
InputStream.available ()에 대한 javadoc이 말한 것 외에도 available () 메소드가 int를 리턴한다는 사실은 URL 접근 방식에 대한 적신호 여야합니다. 3GB 파일로 시도하면 파일 길이를 결정하는 올바른 방법이 아니라는 것이 분명합니다.
Scrubbie

32

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

3
실제로, 당신은 그것이 다른 측면을 측정한다고 말하는 것은 정확하지만, 나는 내 질문에서 더 명확해야합니다. 여러 파일의 파일 크기를 얻으려고하는데 가장 빠른 방법을 원합니다. 그 실제 시나리오이기 때문에 그래서 정말 오버 헤드 계정 개체 생성을 고려하고 할 필요 할
joshjdevl

3
getResource에 소요되는 시간은 약 90 %입니다. Java 바이트 코드가 포함 된 파일의 이름을 얻으려면 리플렉션을 사용해야한다고 의심합니다.

20

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

9

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

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

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

URL 합계 : 294, 반복 당 : 294.0


9

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

8

나는이 같은 문제에 부딪쳤다. 네트워크 공유에서 파일 크기와 수정 날짜 90,000 개의 파일을 가져와야했습니다. Java를 사용하고 가능한 최소한으로 사용하려면 시간이 오래 걸립니다. (파일에서 URL과 객체의 경로도 가져와야했습니다. 따라서 약간 다르지만 한 시간 이상이 걸렸습니다.) 그런 다음 기본 Win32 실행 파일을 사용하고 동일한 작업을 수행하여 파일을 덤프했습니다. 콘솔에 경로, 수정 및 크기를 지정하고 Java에서 실행합니다. 속도는 놀랍습니다. 기본 프로세스와 데이터를 읽는 문자열 처리는 초당 1000 개가 넘는 항목을 처리 할 수 ​​있습니다.

따라서 사람들이 위의 의견에 순위를 매겼지만 이것은 유효한 해결책이며 내 문제를 해결했습니다. 필자의 경우 미리 크기가 필요한 폴더를 알고 명령 줄에서 win32 앱으로 전달할 수있었습니다. 나는 디렉토리를 몇 분 동안 처리하기 위해 몇 시간에서 갔다.

이 문제는 Windows에만 국한된 것으로 보입니다. OS X에는 동일한 문제가 없었으며 OS에서 가능한 한 빨리 네트워크 파일 정보에 액세스 할 수있었습니다.

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

-벤


3

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

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


참고 Files.walkFileTree로 Android 26 이상에서 사용할 수 있습니다.
Joshua Pinter

2

실제로 "ls"가 더 빠를 수 있다고 생각합니다. Java에는 파일 정보를 얻는 것과 관련하여 분명히 몇 가지 문제가 있습니다. 불행히도 Windows에는 안전한 안전한 재귀 방법이 없습니다. (cmd.exe의 DIR / S는 혼란스럽고 무한 루프에서 오류를 생성 할 수 있습니다)

XP에서 LAN의 서버에 액세스하면 Windows에서 폴더의 파일 수 (33,000)와 총 크기를 가져 오는 데 5 초가 걸립니다.

Java에서 이것을 통해 재귀 적으로 반복하면 5 분 이상 걸립니다. file.length (), file.lastModified () 및 file.toURI ()를 수행하는 데 걸리는 시간을 측정하기 시작했으며, 내가 찾은 것은 99 %의 시간이 3 번의 호출에 걸린다는 것입니다. 내가 실제로해야 할 3 가지 전화 ...

1000 파일의 차이는 서버에서 15ms 로컬과 1800ms입니다. Java에서 서버 경로 스캔이 엄청나게 느립니다. 기본 OS가 동일한 폴더를 빠르게 검색 할 수 있다면 왜 Java를 할 수 없습니까?

보다 완전한 테스트로 XP의 WineMerge를 사용하여 서버의 파일과 로컬의 파일의 수정 된 날짜와 크기를 비교했습니다. 이것은 각 폴더에 33,000 개의 파일로 구성된 전체 디렉토리 트리를 반복했습니다. 총 시간, 7 초 자바 : 5 분 이상.

따라서 OP의 원래 진술과 질문은 사실이며 유효합니다. 로컬 파일 시스템을 다룰 때 눈에 띄지 않습니다. WinMerge에서 폴더와 33,000 개의 항목을 로컬로 비교하면 3 초가 걸리고 Java에서는 로컬로 32 초가 걸립니다. 다시 말하지만, 자바 대 네이티브는 이러한 기초 테스트에서 10 배의 속도 저하입니다.

Java 1.6.0_22 (최신), 기가비트 LAN 및 네트워크 연결, 핑이 1ms 미만 (둘 다 동일한 스위치에 있음)

자바는 느리다.


2
이것은 또한 OS에 특정한 것으로 보입니다. 삼바를 사용하여 OS X에서 동일한 폴더를 따라 동일한 Java 응용 프로그램을 수행하면 전체 33,000 항목, 크기 및 날짜를 ​​나열하는 데 26 초가 걸렸습니다. 그렇다면 네트워크 Java가 Windows에서 느리게 진행됩니까? (OS X도 java 1.6.0_22였습니다.)
Ben Spink

2

GHad의 벤치 마크에서 사람들이 언급 한 몇 가지 문제가 있습니다.

1> BalusC와 같이 언급 :이 경우 stream.available ()이 흐릅니다.

available ()은 다음 입력 스트림에 대한 메소드 호출에 의해 차단되지 않고이 입력 스트림에서 읽거나 건너 뛸 수있는 바이트 수의 추정값 을 리턴 합니다.

따라서이 방법으로 URL을 먼저 제거하십시오.

2> StuartH가 언급했듯이-테스트 실행 순서에 따라 캐시 차이가 발생하므로 테스트를 별도로 실행하여 제거하십시오.


이제 테스트를 시작하십시오 :

채널 하나가 단독으로 실행될 때 :

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();
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.