2015-11-10 15 views
7

Tôi đã triển khai (trong Java) một Iterator khá đơn giản để trả về tên của tệp trong cấu trúc thư mục đệ quy và sau khoảng 2300 tệp không thành công "Quá nhiều tệp mở trong hệ thống" (sự cố thực sự đang cố gắng tải một lớp, nhưng tôi cho rằng danh sách thư mục là thủ phạm).Lỗi "Mở nhiều tệp trong hệ thống" khi liệt kê cấu trúc thư mục đệ quy

Cấu trúc dữ liệu được duy trì bởi trình lặp là một ngăn chứa nội dung của các thư mục được mở ở mỗi cấp.

Logic thực tế là khá cơ bản:

private static class DirectoryIterator implements Iterator<String> { 

     private Stack<File[]> directories; 
     private FilenameFilter filter; 
     private Stack<Integer> positions = new Stack<Integer>(); 
     private boolean recurse; 
     private String next = null; 

     public DirectoryIterator(Stack<File[]> directories, boolean recurse, FilenameFilter filter) { 
      this.directories = directories; 
      this.recurse = recurse; 
      this.filter = filter; 
      positions.push(0); 
      advance(); 
     } 

     public boolean hasNext() { 
      return next != null; 
     } 

     public String next() { 
      String s = next; 
      advance(); 
      return s; 
     } 

     public void remove() { 
      throw new UnsupportedOperationException(); 
     } 

     private void advance() { 
      if (directories.isEmpty()) { 
       next = null; 
      } else { 
       File[] files = directories.peek(); 
       while (positions.peek() >= files.length) { 
        directories.pop(); 
        positions.pop(); 
        if (directories.isEmpty()) { 
         next = null; 
         return; 
        } 
        files = directories.peek(); 
       } 
       File nextFile = files[positions.peek()]; 
       if (nextFile.isDirectory()) { 
        int p = positions.pop() + 1; 
        positions.push(p); 
        if (recurse) { 
         directories.push(nextFile.listFiles(filter)); 
         positions.push(0); 
         advance(); 
        } else { 
         advance(); 
        } 
       } else { 
        next = nextFile.toURI().toString(); 
        count++; 
        if (count % 100 == 0) { 
         System.err.println(count + " " + next); 
        } 
        int p = positions.pop() + 1; 
        positions.push(p); 
       } 
      } 
     } 
    } 

Tôi muốn hiểu có bao nhiêu "mở file" này đòi hỏi. Trong hoàn cảnh nào là thuật toán này "mở" một tập tin, và khi nào nó lại bị đóng lại?

Tôi đã nhìn thấy một số mã gọn gàng sử dụng Java 7 hoặc Java 8, nhưng tôi hạn chế đến Java 6.

+0

Chỉ cần chạy mã của bạn trên một hệ thống tập tin với hơn 1.000.000 tập tin, và không nhận được vấn đề bạn đang nhìn thấy. Tôi đang sử dụng JDK 1.6.0_34 trên Windows. Có lẽ vấn đề là ở nơi khác trong mã? Bạn có thể đăng mã cho 'FilenameFilter' mà bạn đang sử dụng không? Đó có thể là vấn đề. – msandiford

+0

Nó có thể là hệ thống tập tin của bạn không phải là sâu, do đó, các nguồn lực đang được trả lại cho hệ điều hành của GC. Hoặc có thể hệ điều hành của bạn có giới hạn lớn hơn đối với các tệp đang mở. –

+0

Vâng, tôi nằm thức đêm qua tự hỏi liệu FileNameFilter có đổ lỗi hay không. Nhưng không: phương thức accept() thực hiện 'return new File (dir, name) .isDirectory() || pattern.matcher (name) .matches(); ' –

Trả lời

6

Khi bạn gọi nextFile.listFiles(), một bộ mô tả tập tin cơ bản được mở ra để đọc các thư mục . Không có cách nào để đóng một cách rõ ràng bộ mô tả này, vì vậy bạn đang dựa vào bộ sưu tập rác. Khi mã của bạn hạ xuống một cây sâu, về cơ bản nó thu thập một chồng các cá thể nextFile không thể thu thập được.

Bước 1: đặt nextFile = null trước khi gọi trước(). Điều này giải phóng đối tượng cho việc thu gom rác thải.

Bước 2: bạn có thể cần phải gọi System.gc() sau khi vô hiệu hóa nextFile để khuyến khích thu gom rác nhanh chóng. Thật không may, không có cách nào để buộc GC.

Bước 3: bạn có thể cần tăng giới hạn tệp đang mở trên hệ điều hành của mình. Trên Linux, điều này có thể được thực hiện với ulimit (1).

Nếu bạn có thể di chuyển sang Java 7 trở lên thì DirectoryStream sẽ giải quyết được sự cố của bạn. Thay vì sử dụng nextFile.listFiles(), sử dụng Files.newDirectoryStream (nextFile.toPath()) để lấy một DirectoryStream. Sau đó bạn có thể lặp qua luồng và sau đó đóng() nó để giải phóng tài nguyên hệ điều hành. Mỗi đường dẫn trả về có thể được chuyển đổi trở lại một tệp tin với toFile(). Tuy nhiên, bạn có thể muốn cấu trúc lại để chỉ sử dụng Path thay vì File.

+0

Các op đề cập đến anh ta bị ràng buộc với Java 6. –

+0

Bạn nói đúng, Đường dẫn chỉ> = Java 7. Tôi sẽ chỉnh sửa câu trả lời của mình bằng cách thay thế Java 6. –

1

Cảm ơn mọi người vì đã giúp đỡ và tư vấn. Tôi đã thiết lập rằng vấn đề là thực sự trong những gì đang được thực hiện với các tập tin sau khi họ được trả về bởi iterator: mã "khách hàng" đang mở các tập tin khi chúng được phân phối, và không phải là dọn dẹp đúng cách. Nó phức tạp bởi thực tế là các tập tin trở lại đang thực sự được xử lý song song.

Tôi cũng đã viết lại DireectoryIterator, mà tôi chia sẻ trong trường hợp bất cứ ai đang quan tâm:

private static class DirectoryIterator implements Iterator<String> { 

     private Stack<Iterator<File>> directories; 
     private FilenameFilter filter; 
     private boolean recurse; 
     private String next = null; 

     public DirectoryIterator(Stack<Iterator<File>> directories, boolean recurse, FilenameFilter filter) { 
      this.directories = directories; 
      this.recurse = recurse; 
      this.filter = filter; 
      advance(); 
     } 

     public boolean hasNext() { 
      return next != null; 
     } 

     public String next() { 
      String s = next; 
      advance(); 
      return s; 
     } 

     public void remove() { 
      throw new UnsupportedOperationException(); 
     } 

     private void advance() { 
      if (directories.isEmpty()) { 
       next = null; 
      } else { 
       Iterator<File> files = directories.peek(); 
       while (!files.hasNext()) { 
        directories.pop(); 
        if (directories.isEmpty()) { 
         next = null; 
         return; 
        } 
        files = directories.peek(); 
       } 
       File nextFile = files.next(); 
       if (nextFile.isDirectory()) { 
        if (recurse) { 
         directories.push(Arrays.asList(nextFile.listFiles(filter)).iterator()); 
        } 
        advance(); 
       } else { 
        next = nextFile.toURI().toString(); 
       } 
      } 
     } 
    } 
Các vấn đề liên quan