2010-06-29 4 views
19

मुझे संसाधित होने के लिए लगभग 2 मिलियन xml की निर्देशिका से निपटना होगा।"स्मृति से बाहर" अपवाद के बिना जावा में 2 मिलियन फाइल निर्देशिका को कैसे सूचीबद्ध करें

मैंने पहले ही कतारों का उपयोग करके मशीनों और धागे के बीच काम को वितरित करने की प्रसंस्करण को हल कर लिया है और सब ठीक हो जाता है।

लेकिन अब बड़ी समस्या कतारों को बढ़ाने के लिए 2 मिलियन फ़ाइलों के साथ निर्देशिका पढ़ने की बाधा है।

मैंने File.listFiles() विधि का उपयोग करने का प्रयास किया है, लेकिन यह मुझे जावा out of memory: heap space अपवाद देता है। कोई विचार?

+1

क्षमा करें, लेकिन कौन सा ओएस इसका समर्थन नहीं करता है? 1 9 50 में रह रहे हो? मुझे विभिन्न टूल्स उड़ाने का पता है, उदाहरण के लिए विंडोज़ पर (एक्सप्लोरर बहुत धीमा हो रहा है), लेकिन फाइल सिस्टम इसका समर्थन करता है। – TomTom

+5

@ टॉमटॉम: एफएटी 32 (पुराना, लेकिन किसी भी तरह से 1 9 50 युग, और अभी भी काफी आम है) प्रति निर्देशिका filesf 65k की फाइलों की एक सीमा है। –

+0

लेकिन मानते हैं कि कोई इसका उपयोग करता है जो नकारात्मक है - डिवाइस को छोड़कर इसका समर्थन नहीं कर रहा है, और फिर समस्या - अनुमान लगाएं - "फाइलों को सूचीबद्ध करने में समस्या" नहीं होगी। – TomTom

उत्तर

11

सबसे पहले, क्या आपके पास जावा 7 का उपयोग करने की कोई संभावना है? वहां आपके पास FileVisitor और Files.walkFileTree है, जो शायद आपकी स्मृति बाधाओं के भीतर काम करना चाहिए।

अन्यथा, एक ही रास्ता मैं के बारे में सोच सकते हैं एक फिल्टर है कि हमेशा रिटर्न के साथ File.listFiles(FileFilter filter) उपयोग करने के लिए है false (सुनिश्चित करना है कि फाइलों से भरा सरणी स्मृति में रखा कभी नहीं किया गया है), लेकिन यह पकड़ता फ़ाइलों के साथ संसाधित करने के लिए रास्ता, और शायद उन्हें एक निर्माता/उपभोक्ता कतार में रखता है या फ़ाइल के नाम बाद में ट्रैवर्सल के लिए डिस्क पर लिखता है।

वैकल्पिक रूप से, अगर आप फ़ाइलों के नाम पर नियंत्रण, या अगर वे कुछ अच्छा तरीका में नामित कर रहे हैं, आप मात्रा में फ़ाइलों को एक फिल्टर है कि फार्म पर फ़ाइल नाम को स्वीकार करता है का उपयोग कर संसाधित कर सकते हैं file0000000 - filefile0001000 तो file0001000 - filefile0002000 और इतने पर।

नाम हैं तो नहीं इस तरह एक अच्छा तरीका में नाम, तो आप उन्हें फ़ाइल-नाम है, जो काफी समान रूप से पूर्णांकों के उस समूह पर वितरित करने के लिए माना जाता है के हैश-कोड के आधार पर छानने की कोशिश कर सकते।


अद्यतन: साई। शायद काम नहीं करेगा।

public File[] listFiles(FilenameFilter filter) { 
    String ss[] = list(); 
    if (ss == null) return null; 
    ArrayList v = new ArrayList(); 
    for (int i = 0 ; i < ss.length ; i++) { 
     if ((filter == null) || filter.accept(this, ss[i])) { 
      v.add(new File(ss[i], this)); 
     } 
    } 
    return (File[])(v.toArray(new File[v.size()])); 
} 

तो यह शायद पहली पंक्ति वैसे भी पर विफल हो जाएगा ... क्रमबद्ध का निराशाजनक: बस listFiles कार्यान्वयन पर एक नज़र था। मेरा मानना ​​है कि फाइलों को अलग-अलग निर्देशिकाओं में रखना सबसे अच्छा विकल्प है।

बीटीडब्ल्यू, क्या आप फ़ाइल नाम का उदाहरण दे सकते हैं? क्या वे "अनुमान लगाने योग्य" हैं? जैसा

for (int i = 0; i < 100000; i++) 
    tryToOpen(String.format("file%05d", i)) 
+0

जावा 7 अभी एक विकल्प नहीं है। वर्तमान में मैं फ़िल्टर विकल्प का प्रयास कर रहा हूं। शुक्र है कि फाइलें फ़ाइल नाम में लिखी गई पदानुक्रम है। तो यह विकल्प काम कर सकता है। – Fgblanch

+1

एओओब प्रभावी ढंग से यह काम नहीं किया। मैंने पाया फ़ाइल नाम "guessables" कर रहे हैं :) तो मैं यह दूसरी तरह के आसपास करेंगे: फ़ाइल नाम उत्पन्न और फिर फ़ोल्डर में जाने के लिए और उन तक पहुंचने का प्रयास करें। आपकी मदद के लिए बहुत बहुत धन्यवाद – Fgblanch

1

मुट्ठी पर आप अपने JVM की स्मृति को गुजरने के साथ -Xmx1024m उदा।

+0

मुझे एहसास है कि यह समस्या को ठीक नहीं करेगा, और JVM बस * थोड़ा * बाद में स्मृति से बाहर हो जाएगा। – Piskvor

+0

@Piskvor यदि ऐसा है, तो मुझे लगता है कि इस समस्या को हल करने का कोई तरीका नहीं है। जो भी आप ओएस फाइल सिस्टम को पार्स करने के लिए उपयोग करते हैं, उसे बाइट्स के एक निश्चित मात्रा की आवश्यकता होगी - 2 मिलियन फाइलों के साथ यह तेजी से बहुत अधिक हो सकता है। – InsertNickHere

+0

आपको एक ही समय में अपने सभी डेटा रैम में रखने की आवश्यकता नहीं है। – Piskvor

2

आप उसी निर्देशिका में 2 मिलियन फ़ाइलों को क्यों स्टोर करते हैं? मैं कल्पना कर सकता हूं कि यह पहले से ही ओएस स्तर पर पहुंच को धीमा कर देता है।

मैं निश्चित रूप से प्रसंस्करण से पहले ही उपनिर्देशिकाओं (जैसे सृजन की तिथि/समय) में विभाजित होना चाहता हूं। लेकिन अगर किसी कारण से यह संभव नहीं है, तो क्या यह प्रसंस्करण के दौरान किया जा सकता है? जैसे निर्देशिका 1 में प्रक्रिया 1 के लिए कतारबद्ध 1000 फ़ाइलों को स्थानांतरित करें, निर्देशिका 2 में प्रक्रिया 2 के लिए एक और 1000 फ़ाइलें। फिर प्रत्येक प्रक्रिया/थ्रेड केवल इसके लिए विभाजित (सीमित संख्या में) फ़ाइलों को देखता है।

+0

उन्हें अपनी समस्या में डालना। मैं ओएस बैश कार्यों पर भी उस पर सोच रहा हूं। प्रोसेसिंग के दौरान ऐसा करना संभव नहीं है क्योंकि निर्देशिका प्रोग्रामेटिक रूप से सूचीबद्ध करने का प्रयास करते समय अपवाद आता है। – Fgblanch

0

कृपया ओओएम अपवाद का पूरा स्टैक ट्रेस पोस्ट करें जहां यह पता लगाने के लिए कि बाधा कहां है, साथ ही साथ एक छोटा, पूरा जावा प्रोग्राम जो आप देखते हैं।

यह सबसे अधिक संभावना है क्योंकि आप स्मृति में सभी दो मिलियन प्रविष्टियों को एकत्र करते हैं, और वे फिट नहीं होते हैं। क्या आप ढेर की जगह बढ़ा सकते हैं?

8

उपयोग File.list() बजाय File.listFiles() - String वस्तुओं यह रिटर्न File वस्तुओं से भी कम स्मृति, और (अधिक महत्वपूर्ण बात यह निर्देशिका के स्थान के आधार पर) वे शामिल नहीं है पूरा पथ नाम खपत करते हैं।

फिर, परिणाम को संसाधित करते समय आवश्यक File वस्तुओं का निर्माण करें।

हालांकि, यह मनमाने ढंग से बड़ी निर्देशिकाओं के लिए भी काम नहीं करेगा। निर्देशिकाओं के पदानुक्रम में अपनी फ़ाइलों को व्यवस्थित करने का यह एक बेहतर विचार है ताकि कोई भी निर्देशिका कुछ हज़ार प्रविष्टियों से अधिक न हो।

0

यदि फ़ाइल नाम कुछ नियमों का पालन करते हैं, तो आप फ़ाइल सूची के प्रबंधनीय भाग प्राप्त करने के लिए File.listFiles के बजाय File.list(filter) का उपयोग कर सकते हैं।

-3

इस इस हैक (यूनिक्स के लिए) काम करेंगे प्रयास करें, यह मेरे लिए काम करता है, लेकिन मैं नहीं इतने सारे दस्तावेज था ...

File dir = new File("directory"); 
String[] children = dir.list(); 
if (children == null) { 
    //Either dir does not exist or is not a directory 
    System.out.print("Directory doesn't exist\n"); 
} 
else { 
    for (int i=0; i<children.length; i++) { 
    // Get filename of file or directory 
    String filename = children[i]; 
} 
+0

यह सीधे पूछताछ करने वाले के लिए काम नहीं कर रहा है, उसके पास कई फाइलें हैं –

9

तो जावा 7 एक विकल्प नहीं है,:

Process process = Runtime.getRuntime().exec(new String[]{"ls", "-f", "/path"}); 
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 
String line; 
while (null != (line = reader.readLine())) { 
    if (line.startsWith(".")) 
     continue; 
    System.out.println(line); 
} 

-f पैरामीटर (man ls से) वह तेज़ हो जाएगी:

-f  do not sort, enable -aU, disable -lst 
+1

यह एक हैक नहीं है लेकिन सीमित जावा एपीआई से निपटने का एक तरीका है;) लेकिन इसे अन्य ऑपरेटिंग सिस्टम के लिए समर्थन जोड़ना चाहिए, और यह प्राइमा होगा;) –

2

के बाद आप Windows पर हैं, ऐसा लगता है जैसे आप श "cmd/k dir/b target_directory" जैसे कुछ शुरू करने के लिए प्रोसेसबिल्डर का उपयोग किया जा सकता है, उस के आउटपुट को कैप्चर करें, और इसे फ़ाइल में रूट करें। फिर आप उस फ़ाइल को एक समय में एक पंक्ति को संसाधित कर सकते हैं, फ़ाइल नामों को पढ़ सकते हैं और उनसे निपट सकते हैं।

कभी भी बेहतर नहीं? ;)

5

यदि आप जावा 7 का उपयोग कर सकते हैं तो यह इस तरह से किया जा सकता है और आपके पास स्मृति समस्याओं से बाहर नहीं होगा।

Path path = FileSystems.getDefault().getPath("C:\\path\\with\\lots\\of\\files"); 
     Files.walkFileTree(path, new FileVisitor<Path>() { 
      @Override 
      public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { 
       return FileVisitResult.CONTINUE; 
      } 

      @Override 
      public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 
       // here you have the files to process 
       System.out.println(file); 
       return FileVisitResult.CONTINUE; 
      } 

      @Override 
      public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { 
       return FileVisitResult.TERMINATE; 
      } 

      @Override 
      public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 
       return FileVisitResult.CONTINUE; 
      } 
     }); 
-1

आप एक विशेष फ़ाइल नाम फ़िल्टर के साथ सूचीफाइल का उपयोग कर सकते हैं। पहली बार FilenameFilter को सूची में भेजा जाता हैफाइल यह पहली 1000 फाइलों को स्वीकार करता है और फिर उन्हें विज़िट के रूप में सहेजता है।

अगली बार FilenameFilter को सूची फ़ाइल में भेजा जाता है, यह पहली 1000 देखी गई फ़ाइलों को अनदेखा करता है और अगले 1000 को लौटाता है, और तब तक पूरा हो जाता है।

+0

सूचीफाइल में सबसे पहली पंक्ति (यहां तक ​​कि FilenameFilter के साथ) तारों की एक सरणी बनाता है - प्रत्येक निर्देशिका में फ़ाइल नाम स्ट्रिंग करता है। इसके अलावा, @aioobe द्वारा इंगित किया गया। – gjain

3

आप अपाचे फ़ाइल उपयोग पुस्तकालय के साथ ऐसा कर सकते हैं। कोई स्मृति समस्या नहीं है। मैंने visualvm के साथ जांच की थी।

Iterator<File> it = FileUtils.iterateFiles(folder, null, true); 
    while (it.hasNext()) 
    { 
    File fileEntry = (File) it.next(); 
    } 

आशा है कि मदद करता है। अलविदा

+1

फ़ाइल उपयोग (2 के साथ चेक किया गया।4) आंतरिक रूप से फ़ाइल # सूची() का भी उपयोग करता है, इसलिए बड़ी निर्देशिकाओं के साथ एक ही समस्या दिखाई देगी। ध्यान दें कि #iterateFiles() सिर्फ #listFiles() के परिणाम से .iterator() लौटाता है। – ankon

0

पहले दृष्टिकोण के रूप में आप कुछ जेवीएम मेमोरी सेटिंग्स को ट्वीक करने का प्रयास कर सकते हैं, उदा। हेप आकार को बढ़ाएं क्योंकि इसका सुझाव दिया गया था या यहां तक ​​कि आक्रामक हेप विकल्प का भी उपयोग करें। बड़ी मात्रा में फाइलों को ध्यान में रखते हुए, यह मदद नहीं कर सकता है, तो मैं समस्या को हल करने का सुझाव दूंगा। प्रत्येक में फ़ाइल नामों के साथ कई फाइलें बनाएं, प्रति फ़ाइल 500k फ़ाइल नाम कहें और उनसे पढ़ें।

0

मै मैलवेयर स्कैनिंग एप्लिकेशन विकसित करते समय मुझे एक ही समस्या का सामना करना पड़ा।मेरा समाधान सभी फ़ाइलों को सूचीबद्ध करने के लिए खोल कमांड कमांड है। फ़ोल्डर द्वारा फ़ोल्डर ब्राउज़ करने के लिए यह पुनरावर्ती तरीकों से तेज़ है।

शेल कमांड के बारे में अधिक देखें: http://adbshell.com/commands/adb-shell-ls

 Process process = Runtime.getRuntime().exec("ls -R /"); 
     BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); 

     //TODO: Read the stream to get a list of file path. 
0

यह भी जरूरी है जावा 7, लेकिन यह Files.walkFileTree जवाब से अधिक आसान है कि अगर आप सिर्फ एक निर्देशिका की सामग्री को सूचीबद्ध करने और पूरे पेड़ चलना नहीं चाहता:

Path dir = Paths.get("/some/directory"); 
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) { 
    for (Path path : stream) { 
     handleFile(path.toFile()); 
    } 
} catch (IOException e) { 
    handleException(e); 
} 

DirectoryStream के कार्यान्वयन प्लेटफ़ॉर्म-विशिष्ट है और कभी नहीं File.list या यह ऐसा कुछ कहता है, बजाय यूनिक्स या Windows सिस्टम कॉल है कि एक निर्देशिका से अधिक पुनरावृति का उपयोग कर एक समय में प्रवेश

संबंधित मुद्दे