2010-07-30 9 views
27

मैं एक प्रोग्राम लिख रहा हूं जो निर्देशिका देखता है और जब फाइलें इसमें बनाई जाती हैं, तो यह नाम बदलती है और उन्हें एक नई निर्देशिका में ले जाती है। मेरे पहले कार्यान्वयन में मैंने जावा की वॉच सर्विस एपीआई का इस्तेमाल किया जो 1kb फ़ाइलों का परीक्षण करते समय ठीक काम करता था। समस्या यह है कि वास्तव में बनाई गई फाइलें 50-300 एमबी से कहीं भी हैं। जब ऐसा हुआ तो वॉचर एपीआई तुरंत फाइल ढूंढ लेगा लेकिन इसे स्थानांतरित नहीं कर सका क्योंकि यह अभी भी लिखा जा रहा था। मैंने वॉचर को एक लूप में डालने का प्रयास किया (जिसने फ़ाइल को स्थानांतरित किए जाने तक अपवाद उत्पन्न किए) लेकिन यह बहुत अक्षम था।जावा: बड़ी फ़ाइलों को स्थानांतरित करने के लिए निर्देशिका देखना

चूंकि यह काम नहीं करता है, इसलिए मैंने एक टाइमर का उपयोग करने की कोशिश की जो प्रत्येक 10s फ़ोल्डर को चेक करता है और फिर जब यह कर सकता है तो फ़ाइलों को ले जाता है। यह वह तरीका है जिसके लिए मैं जा रहा हूं।

प्रश्न: क्या कोई संकेत है कि जब कोई अपवाद जांच किए बिना फ़ाइल को लिखा जा रहा है या लगातार आकार की तुलना किए बिना लिखा जा रहा है? मुझे एक टाइमर (और अपवादों में चलने) के बजाय लगातार प्रत्येक फ़ाइल के लिए वॉचर एपीआई का उपयोग करने का विचार पसंद है।

सभी प्रतिक्रियाओं की बहुत सराहना की जाती है!

nt

+1

'मैंने वॉचर को एक लूप में डालने का प्रयास किया (जिसने फ़ाइल को स्थानांतरित किए जाने तक अपवाद उत्पन्न किए) लेकिन यह बहुत अक्षम था। हाँ, यह एक भयानक समाधान है। नियंत्रण प्रवाह के प्रबंधन के लिए अपवाद नहीं किए जाते हैं। –

+1

दुख की बात है @ntmp, जो मैंने अभी तक परीक्षण किया है, अपवादों की तलाश करना यह बताने का सबसे अच्छा तरीका था कि ओएस अभी भी "लेखन" या "प्रतिलिपि" फ़ाइल था। लेकिन मैं @ सेन पैट्रिक फ़्लॉइड से सहमत हूं कि यह काम करने के लिए यह एक भयानक तरीका है। व्यक्तिगत रूप से मैं चाहता हूं कि चेक java.io.File API का हिस्सा था। यकीन नहीं है कि यह क्यों नहीं था। लागू करने के लिए जेवीएम लोगों को छोड़ दिया जाएगा और हमारे डेवलपर्स के लिए इसे आसान बना देगा .... –

+3

"अपवाद के लिए जांच" दृष्टिकोण यूनिक्स पर भी काम नहीं करेगा, क्योंकि यूनिक्स फाइल सिस्टम लिखे गए फाइलों को लॉक नहीं करता है। यूनिक्स पर, जावा आंशिक रूप से आंशिक रूप से लिखित फ़ाइल को स्थानांतरित करेगा, जिसके परिणामस्वरूप दूषित डेटा होगा। – Raman

उत्तर

11

एक संकेत है कि मूल फ़ाइल पूरा हो गया है के रूप में एक और फ़ाइल लिखें। I.g 'fileorg.dat' बढ़ रहा है अगर फ़ाइल 'fileorg.done' फ़ाइल बनाएं और केवल 'fileorg.done' के लिए देखें।

चालाक नामकरण सम्मेलनों के साथ आपको समस्या नहीं होनी चाहिए।

9

दो समाधान:

पहले the answer by stacker की एक मामूली बदलाव है:

अधूरा फ़ाइलों के लिए एक अनूठा उपसर्ग का प्रयोग करें। myhugefile.zip के बजाय myhugefile.zip.inc की तरह कुछ। अपलोड/निर्माण समाप्त होने पर फ़ाइलों का नाम बदलें। घड़ी से .inc फाइलों को बाहर निकालें।

दूसरा फाइल बनाने/अपलोड/लिखने के लिए एक ही ड्राइव पर एक अलग फ़ोल्डर का उपयोग करना है और उन्हें तैयार होने के बाद उन्हें देखे गए फ़ोल्डर में ले जाना है। यदि वे एक ही ड्राइव पर हैं (फ़ाइल सिस्टम निर्भर, मुझे लगता है) पर चलना एक परमाणु क्रिया होना चाहिए।

किसी भी तरह से, फ़ाइलों को बनाने वाले क्लाइंट को कुछ अतिरिक्त काम करना होगा।

+0

समस्या यह है कि मेरे पास फाइल बनाने वाले क्लाइंट पर बहुत कम नियंत्रण है। मैं एक अद्वितीय उपसर्ग नहीं जोड़ सकता। मैं उस फ़ोल्डर को निर्दिष्ट कर सकता हूं जो फाइलें भी लिखी गई हैं लेकिन मैं ग्राहक को लिखने के बाद उन्हें किसी अन्य फ़ोल्डर में स्थानांतरित करने के लिए नहीं कह सकता। – nite

+1

@ntmp क्या आपको इस समस्या के बारे में कुछ समाधान मिला है, कृपया मेरे साथ साझा करें क्योंकि मुझे भी उसी तरह की समस्या का सामना करना पड़ रहा है –

-1

मुझे लगता है कि java.io.File.canWrite() आपको बताएगा कि फ़ाइल कब लिखी गई है।

+0

मैंने फ़ाइल में एक थ्रेड लेखन के साथ त्वरित परीक्षण करने की कोशिश की है जबकि अन्य थ्रेड canWrite() विधि की जांच करता है लेकिन यह हमेशा सच हो जाता है। – Serxipc

+0

असल में मेरा मानना ​​है कि यह देखने के लिए कि क्या आपको लिखने की अनुमति है या नहीं, यह सिर्फ ओएस की जांच करता है। आपको सुरक्षा दृष्टिकोण से अनुमति हो सकती है, लेकिन इसे समाप्त होने के इंतजार के दृष्टिकोण से नहीं। –

0

यह एक बहुत ही दिलचस्प चर्चा है, निश्चित रूप से यह एक रोटी और मक्खन उपयोग का मामला है: एक नई फ़ाइल बनाने के लिए प्रतीक्षा करें और फिर कुछ फैशन में फ़ाइल पर प्रतिक्रिया दें। यहां दौड़ की स्थिति दिलचस्प है, निश्चित रूप से यहां उच्च स्तर की आवश्यकता एक घटना प्राप्त करने के लिए है और फिर वास्तव में फ़ाइल पर एक पठन लॉक प्राप्त करें (कम से कम)। बड़ी फ़ाइलों या बस फ़ाइल की बहुत सारी रचनाओं के साथ, इसे कार्यकर्ता धागे के पूरे पूल की आवश्यकता हो सकती है जो समय-समय पर नव निर्मित फाइलों पर ताले पाने की कोशिश करते हैं और जब वे सफल होते हैं, वास्तव में काम करते हैं। लेकिन जैसा कि मुझे यकीन है कि एनटी को एहसास हो जाता है, इसे इसे स्केल करने के लिए सावधानी से ऐसा करना होगा क्योंकि आखिरकार यह एक मतदान दृष्टिकोण है, और स्केलेबिलिटी और मतदान दो शब्द नहीं हैं जो अच्छी तरह से साथ जाते हैं।

19

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

कार्यान्वयन का महत्वपूर्ण हिस्सा निम्नानुसार है। कार्यक्रम प्रतीक्षा समय समाप्त होने तक या एक नई घटना होने तक प्रतीक्षा करता है। प्रत्येक बार फ़ाइल संशोधित होने पर समाप्ति समय रीसेट हो जाता है। यदि प्रतीक्षा समय समाप्त होने से पहले एक फ़ाइल हटा दी जाती है तो इसे सूची से निकाल दिया जाता है। मैं उम्मीद expirationtime का समय समाप्त के साथ चुनाव पद्धति का उपयोग, कि (lastmodified + waittime) है

private final Map<Path, Long> expirationTimes = newHashMap(); 
private Long newFileWait = 10000L; 

public void run() { 
    for(;;) { 
     //Retrieves and removes next watch key, waiting if none are present. 
     WatchKey k = watchService.take(); 

     for(;;) { 
      long currentTime = new DateTime().getMillis(); 

      if(k!=null) 
       handleWatchEvents(k); 

      handleExpiredWaitTimes(currentTime); 

      // If there are no files left stop polling and block on .take() 
      if(expirationTimes.isEmpty()) 
       break; 

      long minExpiration = min(expirationTimes.values()); 
      long timeout = minExpiration-currentTime; 
      logger.debug("timeout: "+timeout); 
      k = watchService.poll(timeout, TimeUnit.MILLISECONDS); 
     } 
    } 
} 

private void handleExpiredWaitTimes(Long currentTime) { 
    // Start import for files for which the expirationtime has passed 
    for(Entry<Path, Long> entry : expirationTimes.entrySet()) { 
     if(entry.getValue()<=currentTime) { 
      logger.debug("expired "+entry); 
      // do something with the file 
      expirationTimes.remove(entry.getKey()); 
     } 
    } 
} 

private void handleWatchEvents(WatchKey k) { 
    List<WatchEvent<?>> events = k.pollEvents(); 
    for (WatchEvent<?> event : events) { 
     handleWatchEvent(event, keys.get(k)); 
    } 
    // reset watch key to allow the key to be reported again by the watch service 
    k.reset(); 
} 

private void handleWatchEvent(WatchEvent<?> event, Path dir) throws IOException { 
    Kind<?> kind = event.kind(); 

    WatchEvent<Path> ev = cast(event); 
     Path name = ev.context(); 
     Path child = dir.resolve(name); 

    if (kind == ENTRY_MODIFY || kind == ENTRY_CREATE) { 
     // Update modified time 
     FileTime lastModified = Attributes.readBasicFileAttributes(child, NOFOLLOW_LINKS).lastModifiedTime(); 
     expirationTimes.put(name, lastModified.toMillis()+newFileWait); 
    } 

    if (kind == ENTRY_DELETE) { 
     expirationTimes.remove(child); 
    } 
} 
+4

इस धागे में सबसे अच्छा जवाब - यह 2013 है और क्या उन्होंने जावा में इसे पहले ही तय कर लिया है, या फिर भी इस तरह के कोड का उपयोग करना आवश्यक है? – gregn3

+1

निश्चित रूप से इस धागे में सबसे उपयोगी उत्तर ... –

0

-currentTime मैं एक ऐसी ही स्थिति से निपटने के लिए जब मैं अपलोड की गई फ़ाइलों को हस्तांतरण करने के एक फाइल सिस्टम द्रष्टा लागू किया था। इस समस्या को हल करने के लिए मैंने जिस समाधान को कार्यान्वित किया है, उनमें निम्नलिखित शामिल हैं:

1- सबसे पहले, अनप्रचारित फ़ाइल का मानचित्र बनाए रखें (जब तक फ़ाइल की प्रतिलिपि बनाई जा रही है, तब तक फ़ाइल सिस्टम Modify_Event उत्पन्न करता है, ताकि आप अनदेखा कर सकें अगर ध्वज झूठा है)।

2- आपकी फ़ाइल में प्रोसेसर, आप सूची से एक फ़ाइल लेते हैं और जांचते हैं कि यह फाइल सिस्टम द्वारा लॉक है या नहीं, यदि हां, तो आपको अपवाद मिलेगा, बस इस अपवाद को पकड़ें और अपना धागा प्रतीक्षा स्थिति में रखें (यानी 10 सेकंड) और तब लॉक जारी होने तक पुनः प्रयास करें। फ़ाइल को संसाधित करने के बाद, आप या तो ध्वज को सही में बदल सकते हैं या इसे मानचित्र से हटा सकते हैं।

यह समाधान तब तक प्रभावी नहीं होगा जब एक ही फ़ाइल के कई संस्करण प्रतीक्षा समय के दौरान स्थानांतरित हो जाते हैं।

चीयर्स, रामजी

3

हालांकि यह चौकीदार सेवा एपीआई द्वारा notificated किया जाना संभव नहीं है जब इतनी नकल खत्म, सभी विकल्प 'के आसपास काम' हो रहा है (यह एक सहित!)।

जैसा कि ऊपर टिप्पणी की,

1) बढ़ते या कॉपी यूनिक्स पर एक विकल्प नहीं है;

2) फ़ाइल.canWrite हमेशा लिखता है अगर आपको लिखने की अनुमति है, भले ही फ़ाइल की प्रतिलिपि बनाई जा रही हो;

3) एक टाइमआउट या एक नई घटना होने तक इंतजार करना एक विकल्प होगा, लेकिन अगर सिस्टम अधिभारित हो तो क्या होगा लेकिन प्रतिलिपि समाप्त नहीं हुई थी? यदि टाइमआउट एक बड़ा मूल्य है, तो प्रोग्राम इतने लंबे समय तक इंतजार करेगा।

4) एक और फ़ाइल को 'ध्वज' पर लिखना कि कॉपी समाप्त हो गया है, यदि आप फ़ाइल का उपभोग नहीं कर रहे हैं, तो यह एक विकल्प नहीं है। , कैसे तत्काल आप एक बार यह लिखा जा रहा से किया जाता है फ़ाइल स्थानांतरित करने की आवश्यकता पर निर्भर करता है

boolean locked = true; 

while (locked) { 
    RandomAccessFile raf = null; 
    try { 
      raf = new RandomAccessFile(file, "r"); // it will throw FileNotFoundException. It's not needed to use 'rw' because if the file is delete while copying, 'w' option will create an empty file. 
      raf.seek(file.length()); // just to make sure everything was copied, goes to the last byte 
      locked = false; 
     } catch (IOException e) { 
      locked = file.exists(); 
      if (locked) { 
       System.out.println("File locked: '" + file.getAbsolutePath() + "'"); 
       Thread.sleep(1000); // waits some time 
      } else { 
       System.out.println("File was deleted while copying: '" + file.getAbsolutePath() + "'"); 
      } 
    } finally { 
      if (raf!=null) { 
       raf.close();  
      } 
     } 
} 
0

तुम भी केवल एक स्थिर अंतिम संशोधित टाइमस्टैम्प के लिए जाँच करें और कर सकते हैं:

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

4

मुझे पता है कि यह एक पुराना सवाल है लेकिन शायद यह किसी की मदद कर सकता है।

if (kind == ENTRY_CREATE) { 
      System.out.println("Creating file: " + child); 

      boolean isGrowing = false; 
      Long initialWeight = new Long(0); 
      Long finalWeight = new Long(0); 

      do { 
       initialWeight = child.toFile().length(); 
       Thread.sleep(1000); 
       finalWeight = child.toFile().length(); 
       isGrowing = initialWeight < finalWeight; 

      } while(isGrowing); 

      System.out.println("Finished creating file!"); 

     } 

जब फ़ाइल बनाया जा रहा है, यह बड़ा और बड़ा हो रही होगी:

मैं एक ही मुद्दा था, मैं तो क्या किया निम्नलिखित था। तो मैंने एक सेकंड से अलग वजन की तुलना करने के लिए क्या किया था। ऐप लूप में होगा जब तक कि दोनों वजन समान नहीं होते।

+0

इससे मेरी मदद की - आपको बहुत धन्यवाद :) +1 – lulu88

+1

यह सुनिश्चित नहीं है कि यह Win7 पर काम करेगा क्योंकि, फ़ाइल की प्रतिलिपि करते समय, Win7 हार्ड में सभी आवश्यक स्थान आवंटित करता है डिस्क और फिर फ़ाइल के बाइट्स के साथ "भरता है"। –

+0

यह मेरे विन 7 पर काम नहीं कर रहा है – Tianhai

3

ऐसा लगता है जैसे अपाचे कैमल फ़ाइल (java.io.File.renameTo) का नाम बदलने का प्रयास करके फ़ाइल-न किए-अपलोड-अपलोड करने की समस्या को संभालता है। अगर नाम विफल रहता है, कोई ताला नहीं है, लेकिन कोशिश करते रहें। जब नाम सफल होता है, तो वे इसे वापस नाम देते हैं, फिर इच्छित प्रसंस्करण के साथ आगे बढ़ते हैं।

ऑपरेशंस.रेनेमफ़ाइल नीचे देखें। GenericFileRenameExclusiveReadLockStrategy.java और FileUtil.java

public boolean acquireExclusiveReadLock(...) throws Exception { 
    LOG.trace("Waiting for exclusive read lock to file: {}", file); 

    // the trick is to try to rename the file, if we can rename then we have exclusive read 
    // since its a Generic file we cannot use java.nio to get a RW lock 
    String newName = file.getFileName() + ".camelExclusiveReadLock"; 

    // make a copy as result and change its file name 
    GenericFile<T> newFile = file.copyFrom(file); 
    newFile.changeFileName(newName); 
    StopWatch watch = new StopWatch(); 

    boolean exclusive = false; 
    while (!exclusive) { 
     // timeout check 
     if (timeout > 0) { 
      long delta = watch.taken(); 
      if (delta > timeout) { 
       CamelLogger.log(LOG, readLockLoggingLevel, 
         "Cannot acquire read lock within " + timeout + " millis. Will skip the file: " + file); 
       // we could not get the lock within the timeout period, so return false 
       return false; 
      } 
     } 

     exclusive = operations.renameFile(file.getAbsoluteFilePath(), newFile.getAbsoluteFilePath()); 
     if (exclusive) { 
      LOG.trace("Acquired exclusive read lock to file: {}", file); 
      // rename it back so we can read it 
      operations.renameFile(newFile.getAbsoluteFilePath(), file.getAbsoluteFilePath()); 
     } else { 
      boolean interrupted = sleep(); 
      if (interrupted) { 
       // we were interrupted while sleeping, we are likely being shutdown so return false 
       return false; 
      } 
     } 
    } 

    return true; 
} 
0

लिनक्स में बड़ी फ़ाइल के लिए, फ़ाइलों .filepart के विस्तार के साथ की नकल की हो जाता है: यहाँ अपाचे कैमल स्रोत के लिंक हैं। आपको केवल कॉमन्स एपीआई का उपयोग करके एक्सटेंशन की जांच करने और ENTRY_CREATE ईवेंट पंजीकृत करने की आवश्यकता है। मैं अपने .csv फ़ाइलें (1GB) के साथ इस परीक्षण किया है और जोड़ने यह

public void run() 
{ 
    try 
    { 
     WatchKey key = myWatcher.take(); 
     while (key != null) 
     { 
      for (WatchEvent event : key.pollEvents()) 
      { 
       if (FilenameUtils.isExtension(event.context().toString(), "filepart")) 
       { 
        System.out.println("Inside the PartFile " + event.context().toString()); 
       } else 
       { 
        System.out.println("Full file Copied " + event.context().toString()); 
        //Do what ever you want to do with this files. 
       } 
      } 
      key.reset(); 
      key = myWatcher.take(); 
     } 
    } catch (InterruptedException e) 
    { 
     e.printStackTrace(); 
    } 
} 
0

काम किया तो आपके पास लेखन प्रक्रिया पर नियंत्रण है नहीं करते हैं, सब ENTRY_CREATED घटनाओं लॉग इन करें और निरीक्षण अगर वहाँ पैटर्न हैं।

मेरे मामले में, फ़ाइलें WebDav (अपाचे) और अस्थायी फ़ाइलों का एक बहुत कुछ के माध्यम से बनाई गई हैं बनाई गई हैं, लेकिन यह भी दोENTRY_CREATED घटनाओं एक ही फाइल के लिए ट्रिगर कर रहे हैं। दूसरा ENTRY_CREATED ईवेंट इंगित करता है कि कॉपी प्रक्रिया पूरी हो गई है।

यहां मेरा उदाहरण ENTRY_CREATED ईवेंट हैं। पूर्ण फ़ाइल पथ छपा है (अपनी लॉग, अलग हो सकता है अनुप्रयोग है कि फ़ाइल लिखते हैं पर निर्भर करता है):

[info] application - /var/www/webdav/.davfs.tmp39dee1 was created 
[info] application - /var/www/webdav/document.docx was created 
[info] application - /var/www/webdav/.davfs.tmp054fe9 was created 
[info] application - /var/www/webdav/document.docx was created 
[info] application - /var/www/webdav/.DAV/__db.document.docx was created 

जैसा कि आप देख, मैं document.docx के लिए दो ENTRY_CREATED घटनाओं मिलता है। दूसरी घटना के बाद मुझे पता है कि फाइल पूरी हो गई है। मेरे मामले में अस्थायी फ़ाइलों को स्पष्ट रूप से अनदेखा किया जाता है।