문제

인터넷 검색 중에 다음을 사용하는 것을 봅니다. 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가 제공하는 벤치마크는 길이를 얻는 것 외에도 많은 다른 항목(예: 반사, 개체 인스턴스화 등)을 측정합니다.이러한 것들을 제거하려고 하면 한 번의 호출에 대해 마이크로초 단위로 다음과 같은 시간이 발생합니다.

   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

나는 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과 개체의 경로도 가져와야 했습니다.따라서 다소 다양하지만 1시간 이상 소요됩니다.) 그런 다음 기본 Win32 실행 파일을 사용하고 동일한 작업을 수행했습니다. 파일 경로, 수정 및 크기를 콘솔에 덤프하고 Java에서 실행했습니다.속도는 놀라웠습니다.기본 프로세스와 데이터 읽기를 위한 문자열 처리는 초당 1000개 이상의 항목을 처리할 수 있습니다.

따라서 사람들이 위 댓글의 순위를 낮추더라도 이는 유효한 솔루션이며 내 문제를 해결했습니다.내 경우에는 필요한 크기의 폴더를 미리 알고 있었고 이를 명령줄에서 내 win32 앱에 전달할 수 있었습니다.나는 몇 시간에서 몇 분 동안 디렉토리를 처리했습니다.

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

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

--벤

디렉토리에 있는 여러 파일의 파일 크기를 원하면 다음을 사용하십시오. Files.walkFileTree.사이즈는 에서 확인하실 수 있어요 BasicFileAttributes 당신이 받게 될 것입니다.

전화하는 것보다 훨씬 빠릅니다. .length() 결과에 File.listFiles() 또는 사용 Files.size() 결과에 Files.newDirectoryStream().내 테스트 사례에서는 약 100배 더 빨랐습니다.

실제로는 "ls"가 더 빠를 수도 있다고 생각합니다.Java에는 파일 정보 가져오기와 관련된 몇 가지 문제가 있습니다.불행히도 Windows에는 재귀 ls와 동등한 안전한 방법이 없습니다.(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의 원래 진술과 질문은 사실이고 유효합니다.로컬 파일 시스템을 다룰 때는 눈에 띄지 않습니다.33,000개의 항목이 있는 폴더를 로컬로 비교하는 데 WinMerge에서는 3초가 걸리고 Java에서는 로컬로 32초가 걸립니다.다시 말하지만, Java와 네이티브는 이러한 기초적인 테스트에서 10배의 속도 저하를 보입니다.

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

자바는 느립니다.

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

1>BalusC가 언급한 것처럼:이 경우에는 stream.available()이 흐릅니다.

available()이 다음을 반환하기 때문에 추정 이 입력 스트림에 대한 다음 메소드 호출에 의해 차단되지 않고 이 입력 스트림에서 읽을 수 있는(또는 건너뛸 수 있는) 바이트 수입니다.

따라서 먼저 이 접근 방식으로 URL을 제거합니다.

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


이제 테스트를 시작하세요:

CHANNEL 1이 단독으로 실행되는 경우:

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();
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top