2017-06-20 10 views
18

पर मौलिक छेद के साथ एक अनुक्रमिक फ़ाइल को कुशलता से लिखने के लिए कैसे करें मुझे एक फ़ाइल में रिकॉर्ड्स लिखने की आवश्यकता है जहां एक फ़ाइल स्थान पर डेटा लिखा जाता है (यानी, स्थिति की तलाश करें) संख्यात्मक कुंजी के मूल्य के आधार पर । उदाहरण के लिए, यदि कुंजी 100 है, तो मैं स्थिति 400 पर लिख सकता हूं।जावा - इसमें

रिकॉर्ड में संख्यात्मक कुंजी और डेटा का एक टुकड़ा शामिल है। रिकॉर्ड बहुत बड़ा नहीं होगा (कुछ बाइट्स)। हालांकि, बहुत सारे रिकॉर्ड (लाखों) हो सकते हैं।

  1. कुंजी होगा- वृद्धि हो रही है:

    दो संभव परिदृश्यों रहे हैं। इस मामले में, DataOutputStream का उपयोग करके को लपेटने का सबसे अच्छा तरीका है, I/O थ्रूपुट को अधिकतम करने के लिए बफर आकार को कुछ संख्या (उदा। 64k) पर सेट करना।

  2. चाबियाँ बढ़ रही हैं लेकिन संभवतः बड़े अंतराल के साथ। इस मामले में आउटपुटस्ट्रीम का उपयोग करने के लिए फ़ाइल में अंतराल में शून्य होने की आवश्यकता होगी। इससे बचने के लिए, RandomAccessFile बेहतर होगा क्योंकि यह अंतराल पर खोज सकता है, यदि पूरे ब्लॉक को खोजना संभव हो तो अंतरिक्ष को बचाया जा सकता है। दोष यह है कि, जहां तक ​​मुझे पता है, RandomAccessFile बफर नहीं करता है, इसलिए अनुक्रमिक कुंजी के लिए यह विधि धीमी हो जाएगी।

हालांकि, संभावित स्थिति यह है कि फ़ाइल दोनों का थोड़ा सा है। Monotonically बढ़ती कुंजी के अनुक्रम हैं। बहुत बड़ी अंतराल के साथ छोटे अंतर के साथ कुछ कुंजियां हैं।

जो मैं खोज रहा हूं वह एक ऐसा समाधान है जो दोनों दुनिया के सर्वश्रेष्ठ प्रदान करता है। यह हो सकता है कि यदि कुंजी के बीच एक अंतर का पता चला है तो मैं दो I/O मोड के बीच स्विच करता हूं। हालांकि, यह बेहतर होगा यदि मानक जावा क्लास है जो इन दोनों चीजों को कर सकती है। मैंने FileImageOutputStream देखा है, लेकिन मुझे यकीन नहीं है कि यह कैसे काम करता है।

ध्यान दें कि मैं कोड नमूने की तलाश नहीं कर रहा हूं (हालांकि जटिल समाधानों के प्रदर्शन के लिए यह सहायक होगा), केवल एक सामान्य रणनीति। अनुक्रमिक डेटा के लिए इष्टतम आकार बफर आकारों को जानना अच्छा होगा और किस बिंदु (अंतराल आकार) पर आपको एक अनुक्रमिक रणनीति से यादृच्छिक-पहुंच रणनीति में स्विच करने की आवश्यकता है।

संपादित करें:

के लिए एक जवाब स्वीकार करने के लिए, मैं कुछ आश्वासन चाहते हैं कि प्रस्तावित समाधान दोनों संभालती है, न सिर्फ यह है कि यह हो सकता है। इसकी आवश्यकता होगी:

  • पुष्टि कि अनुक्रमिक मोड buffered है।
  • पुष्टि है कि यादृच्छिक पहुंच मोड फ़ाइल में छेद छोड़ देता है।

इसके अलावा, समाधान को स्मृति कुशल होने की आवश्यकता है क्योंकि इनमें से कई फाइलें एक साथ खुल सकती हैं।

संपादित 2

फ़ाइलों को एक NAS पर हो सकता है। यह डिज़ाइन द्वारा नहीं है, लेकिन केवल यह मान्यता है कि एक एंटरप्राइज़ वातावरण में, इस आर्किटेक्चर का बहुत उपयोग किया जाता है और समाधान शायद इसे संभाल लेना चाहिए (शायद बेहतर नहीं) और इसके उपयोग को रोकें। AFAIK, इससे write() और lseek() पर आधारित समाधान को प्रभावित नहीं करना चाहिए, लेकिन कुछ और गूढ़ समाधानों को अमान्य कर सकता है। 

+0

क्या फ़ाइल का आकार तय किया गया है? या क्या कुंजी के आधार पर इसे विकसित करने की आवश्यकता है? मैं लिखने के संचालन के लिए बस 'मैप्डबेट बफर' का उपयोग करूंगा .. अगर फ़ाइल बहुत बड़ी है या बढ़ने की जरूरत है, तो मैं इसे एक वर्ग में लपेटूंगा जो "ब्लॉक" में मैप करता है और फिर जब आप लिख रहे हों तो ब्लॉक को ले जाया जाएगा .. इसके लिए एल्गोरिदम काफी सरल है .. बस एक ब्लॉक आकार चुनें जो आपके द्वारा लिखे गए डेटा के लिए समझ में आता है .. – Nim

+0

फ़ाइल का आकार समय से पहले ज्ञात नहीं है। फ़ाइल नेटवर्क ड्राइव पर हो सकती है - मुझे यकीन नहीं है कि यह आपके समाधान को प्रभावित करता है – rghome

+0

'java.nio.channels' पर एक नज़र डालें। आप 'FileChannel' के साथ यादृच्छिक पहुंच कर सकते हैं, और buffered डेटा लिख ​​सकते हैं। – teppic

उत्तर

-1

मैंने इस पर अपना मन बदल दिया है। आपको MappedByteBuffer का उपयोग करना चाहिए। यह वर्चुअल मेमोरी उपप्रणाली के हिस्से के रूप में ऑपरेटिंग सिस्टम द्वारा संचालित है, जो आपकी बफरिंग आवश्यकता को पूरा करता है; लिखते समय स्मृति के लिए लिखना जितना तेज़ होता है; और यह छेद वाली फाइलें लिखते समय ऑपरेटिंग सिस्टम के व्यवहार के अधीन है, जो उस आवश्यकता को पूरा करता है।

+0

हां - मैंने अपने प्रश्न में RandomAccessFile का उल्लेख किया - मुझे पता है कि इसका उपयोग कैसे करें। हालांकि, लेखन बफर के साथ क्रमशः लेखन की तुलना में unbuffered और इसलिए बहुत धीमी है। याद रखें कि रिकॉर्ड छोटे हैं। जो मैं चाहता हूं वह बफर और यादृच्छिक पहुंच है (मैं अपना केक रखना चाहता हूं और इसे खा सकता हूं)। – rghome

+0

तो आप एक बार पूरी फाइल को मानचित्र करेंगे? और आप फ़ाइल के अंत को और लिखने की आवश्यकता को कैसे संभालेंगे? मुझे लगता है कि इसे रीमेपिंग की जरूरत है ... और फिर, हम उसी जवाब में भाग लेते हैं जिसका आपने मेरे जवाब के बारे में उल्लेख किया है ... या क्या मुझे कुछ याद आ रही है? –

1

संपादित/चेतावनी: इस समाधान के साथ संभावित गठिया हैं, क्योंकि यह MappedByteBuffer का भारी उपयोग करता है, और यह स्पष्ट नहीं है कि संबंधित संसाधन कैसे जारी किए जाते हैं। this Q&A & JDK-4724038 : (fs) Add unmap method to MappedByteBuffer देखें। एक वर्ग है जो "ब्लॉक" और में नक्शे में

रैप इस:

कहा जा रहा है, यह भी इस पोस्ट के अंत


मैं वास्तव में क्या Nim suggested करना होगा देखने के लिए कृपया फिर जब आप लिख रहे हों तो ब्लॉक को ले जाया जाएगा .. इसके लिए एल्गोरिदम काफी सरल है .. बस एक ब्लॉक आकार चुनें जो आपके द्वारा लिखे गए डेटा के लिए समझ में आता है ..

वास्तव में, मैं वास्तव में किया था कि साल पहले और बस कोड को खोदा, यह (एक डेमो के लिए न्यूनतम करने के लिए छीन लिया, एक भी विधि के साथ डेटा लिखने के लिए) इस प्रकार है:

import java.io.IOException; 
import java.io.RandomAccessFile; 
import java.nio.MappedByteBuffer; 
import java.nio.channels.FileChannel; 
import java.nio.file.Path; 

public class SlidingFileWriterThingy { 

    private static final long WINDOW_SIZE = 8*1024*1024L; 
    private final RandomAccessFile file; 
    private final FileChannel channel; 
    private MappedByteBuffer buffer; 
    private long ioOffset; 
    private long mapOffset; 

    public SlidingFileWriterThingy(Path path) throws IOException { 
     file = new RandomAccessFile(path.toFile(), "rw"); 
     channel = file.getChannel(); 
     remap(0); 
    } 

    public void close() throws IOException { 
     file.close(); 
    } 

    public void seek(long offset) { 
     ioOffset = offset; 
    } 

    public void writeBytes(byte[] data) throws IOException { 
     if (data.length > WINDOW_SIZE) { 
      throw new IOException("Data chunk too big, length=" + data.length + ", max=" + WINDOW_SIZE); 
     } 
     boolean dataChunkWontFit = ioOffset < mapOffset || ioOffset + data.length > mapOffset + WINDOW_SIZE; 
     if (dataChunkWontFit) { 
      remap(ioOffset); 
     } 
     int offsetWithinBuffer = (int)(ioOffset - mapOffset); 
     buffer.position(offsetWithinBuffer); 
     buffer.put(data, 0, data.length); 
    } 

    private void remap(long offset) throws IOException { 
     mapOffset = offset; 
     buffer = channel.map(FileChannel.MapMode.READ_WRITE, mapOffset, WINDOW_SIZE); 
    } 

} 

यहाँ एक परीक्षण टुकड़ा है:

SlidingFileWriterThingy t = new SlidingFileWriterThingy(Paths.get("/tmp/hey.txt")); 
t.writeBytes("Hello world\n".getBytes(StandardCharsets.UTF_8)); 
t.seek(1000); 
t.writeBytes("Are we there yet?\n".getBytes(StandardCharsets.UTF_8)); 
t.seek(50_000_000); 
t.writeBytes("No but seriously?\n".getBytes(StandardCharsets.UTF_8)); 

और क्या आउटपुट फ़ाइल की तरह दिखता है:

$ hexdump -C /tmp/hey.txt 
00000000 48 65 6c 6c 6f 20 77 6f 72 6c 64 0a 00 00 00 00 |Hello world.....| 
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 
* 
000003e0 00 00 00 00 00 00 00 00 41 72 65 20 77 65 20 74 |........Are we t| 
000003f0 68 65 72 65 20 79 65 74 3f 0a 00 00 00 00 00 00 |here yet?.......| 
00000400 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 
* 
02faf080 4e 6f 20 62 75 74 20 73 65 72 69 6f 75 73 6c 79 |No but seriously| 
02faf090 3f 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |?...............| 
02faf0a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 
* 
037af080 

मुझे आशा है कि मैं रुई नहीं था अनावश्यक बिट्स और नामकरण को हटाकर सब कुछ ... कम से कम ऑफसेट गणना सही दिखती है (0x3e0 + 8 = 1000, और 0x02faf080 = 50000000)।

ब्लॉक (बाएँ स्तंभ) फ़ाइल के कब्जे की संख्या, और एक ही आकार के एक और गैर स्पार्स फ़ाइल:

$ head -c 58388608 /dev/zero > /tmp/not_sparse.txt 
$ ls -ls /tmp/*.txt 
    8 -rw-r--r-- 1 nug nug 58388608 Jul 19 00:50 /tmp/hey.txt 
57024 -rw-r--r-- 1 nug nug 58388608 Jul 19 00:58 /tmp/not_sparse.txt 

ब्लॉक (और वास्तविक "विरल") की संख्या ओएस & पर निर्भर करेगा फाइल सिस्टम, उपर्युक्त डेबियन बस्टर पर था, ext4 - मैकोज़ के लिए स्पैस फाइलें एचएफएस + पर समर्थित नहीं हैं, और विंडोज़ पर उन्हें प्रोग्राम को कुछ विशिष्ट करने की आवश्यकता होती है जिसे मैं पर्याप्त नहीं जानता, लेकिन यह आसान या यहां तक ​​कि करने योग्य नहीं लगता है जावा से, सुनिश्चित नहीं है।

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

जब तक कोई बहुत अच्छा कारण नहीं है, तो जटिल जटिल दोहरी-मोड प्रणाली को बनाए रखने के बजाय, यह एकल तंत्र के साथ इसे सरल रखना सबसे अच्छा है।


कमजोरी और स्मृति की खपत के बारे में, मैं राम की 800GB के साथ एक घंटे के लिए किसी भी मुद्दे के बिना लिनक्स पर नीचे तनाव परीक्षण भाग गया गया है, एक मशीन पर, और रैम 1G के साथ एक और बहुत मामूली वी एम पर । सिस्टम पूरी तरह स्वस्थ दिखता है, जावा प्रक्रिया किसी भी महत्वपूर्ण मात्रा में हीप मेमोरी का उपयोग नहीं करती है।

String path = "/tmp/data.txt"; 
    SlidingFileWriterThingy w = new SlidingFileWriterThingy(Paths.get(path)); 
    final long MAX = 5_000_000_000L; 
    while (true) { 
     long offset = 0; 
     while (offset < MAX) { 
      offset += Math.pow(Math.random(), 4) * 100_000_000; 
      if (offset > MAX/5 && offset < 2*MAX/5 || offset > 3*MAX/5 && offset < 4*MAX/5) { 
       // Keep 2 big "empty" bands in the sparse file 
       continue; 
      } 
      w.seek(offset); 
      w.writeBytes(("---" + new Date() + "---").getBytes(StandardCharsets.UTF_8)); 
     } 
     w.seek(0); 
     System.out.println("---"); 
     Scanner output = new Scanner(new ProcessBuilder("sh", "-c", "ls -ls " + path + "; free") 
       .redirectErrorStream(true).start().getInputStream()); 
     while (output.hasNextLine()) { 
      System.out.println(output.nextLine()); 
     } 
     Runtime r = Runtime.getRuntime(); 
     long memoryUsage = (100 * (r.totalMemory() - r.freeMemory()))/r.totalMemory(); 
     System.out.println("Mem usage: " + memoryUsage + "%"); 
     Thread.sleep(1000); 
    } 

तो हाँ प्रयोग पर आधारित है, हो सकता है यह केवल हाल ही में Linux सिस्टम पर ठीक से काम करता है, शायद यह सिर्फ उस विशेष कार्यभार के साथ भाग्य है ... लेकिन मैं इसे कुछ प्रणालियों और पर एक मान्य समाधान है सोचने के लिए शुरू कर वर्कलोड, यह उपयोगी हो सकता है।

+0

यह विल हर बार जब आप रीमेप करते हैं तो एक नया मैप किए गए बाइट बफर बनाते हैं। कोई अच्छी तरह परिभाषित समय नहीं है जिस पर इन्हें जारी किया जाता है, इसलिए आप स्मृति से बहुत जल्दी बाहर निकलने के लिए उत्तरदायी हैं। – EJP

+0

यह सच है कि यह कचरा कलेक्टर और शायद ओएस तंत्र पर निर्भर करता है। यह लिनक्स पर बड़ी फाइलों के साथ हमारे लिए काफी अच्छा काम करता है, मैं एससीएम इतिहास और एप्लिकेशन उपयोग को वापस देखूंगा, देख सकता हूं कि मुझे –

+0

के कारण होने वाली समस्याओं के बारे में युक्तियां या जानकारी मिलती है, यह * सच नहीं है कि यह कचरा कलेक्टर पर निर्भर करता है। मैंने जो लिखा वह पढ़ें। कोई अच्छी तरह से परिभाषित समय नहीं है जिस पर 'मैप्डबेटबफर' कचरा-एकत्रित किया जा सकता है। इसलिए वे कचरे से एकत्रित होने के लिए उत्तरदायी नहीं हैं * नहीं। जो स्मृति थकावट का कारण बनता है। यह 'मैप्डबेट बफर' के साथ एक प्रसिद्ध मुद्दा है। – EJP

0

आप कुछ बाइट्स के लाखों रिकॉर्ड कहते हैं। तो आइए मान लें कि यह 10 लाख 10 बाइट्स है, जिसका अर्थ है कि लिखने के लिए फ़ाइल लगभग 100 एमबी होगी। हमारे समय में, यह ज्यादा नहीं है।

मैं सिर्फ एक नक्शा बनाउंगा जिसमें सभी महत्वपूर्ण मूल्य जोड़े संग्रहीत किए गए थे। फिर एक Functioon लिखेंगे जो मानचित्र की सामग्री को byte[] पर क्रमबद्ध करता है। और फिर बस Files.write() डिस्क पर बाइट्स। फिर पुरानी फ़ाइल को नई फ़ाइल से प्रतिस्थापित करें। या, बेहतर अभी तक, पुरानी फ़ाइल को पहले स्थानांतरित करें, फिर नया स्थानांतरित करें।

+0

अन्य संख्याओं के लिए मानचित्रों को मानचित्रित करने के लिए एक मानचित्र बेहद अक्षम है। आप एक कस्टम मैप फ्रॉप कोल्ट या ट्रोव का उपयोग कर सकते हैं, लेकिन फिर भी बहुत अच्छा नहीं है। – rghome

0

मुझे लगता है कि जब आपकी चाबियाँ क्रमशः बढ़ने के बाद क्रमशः बढ़ती हैं तो अंतराल को "समाप्त" अनुक्रम में जोड़ने की कोई और कुंजी नहीं होगी। यदि यह सही है तो मैं निम्नलिखित समाधान

के रूप में अपनी चाबी रूप में लंबे समय sujest हैं क्रमिक रूप से बढ़ रही है आपके 1 दृष्टिकोण के साथ काम कर रखने रखना:

लिखने एक BufferedOutputStream लपेटकर एक DataOutputStream का उपयोग कर, कुछ करने के लिए बफर आकार की स्थापना I/O थ्रूपुट को अधिकतम करने के लिए संख्या (उदाहरण के लिए 64k)।

अपने डेटा को एक temp फ़ाइल में लिखें। एक बार अंतराल होने पर अगली अस्थायी फ़ाइल में लिखना शुरू हो जाता है और आपकी अस्थायी फ़ाइलों का रिकॉर्ड रखा जाता है। इस तरह आप अंतराल के बिना रिकॉर्ड्स के प्रति अनुक्रम फ़ाइल प्राप्त करते हैं। एक बार जब आप अपनी मुख्य फ़ाइल के लिए डेटा को संसाधित कर लेते हैं तो एक अलग विधि है जो आपकी अस्थायी फ़ाइलों को अंतिम फ़ाइल में स्मार्ट रूप से संयोजित करेगी। यह एक आसान काम होगा क्योंकि आप जानते हैं कि प्रत्येक temp फ़ाइल में कोई अंतर नहीं है

+0

मुझे लगता है कि यहां नकारात्मकता यह है कि आप फ़ाइल को दो बार लिखने जा रहे हैं। – rghome

+0

आप सही हैं, लेकिन बाद के चरण में संगतता कार्य किया जा सकता है और सिस्टम व्यस्त होने पर महत्वपूर्ण संसाधन नहीं ले सकता है। लाभ यह है कि आप अपने अनुक्रमिक भाग लिखते समय बहुत कुशलतापूर्वक (प्रदर्शन के अनुसार) काम करेंगे और तर्क बहुत आसान है। –

0

इस पर मेरा पहला प्रयास RandomAccessFile को जीवंत रूप से उपयोग करना होगा और देखें कि यह पर्याप्त तेज़ है या नहीं। अगर यह धीमा है तो मैं वास्तव में आश्चर्यचकित हूं - हालांकि जावा इसे बफर नहीं करेगा, फाइल सिस्टम कार्यान्वयन होगा।


यदि वाकई प्रदर्शन की समस्याओं कर रहे हैं, के (जावा-ish स्यूडोकोड) पंक्तियों के साथ लिखने तर्क के साथ एक बफरिंग मुखौटा में RandomAccessFile रैप करने के लिए, मेरा अगला प्रयास होगा:

void write(record, location) { 
    if(location != lastLocation + recordLength) { 
      flushBufferToRandomAccessFile(); 
    ) 
    addToBuffer(record); 
    flushBufferToRandomAccessFileIfFull(); 
    lastLocation = location; 
} 

बफर byte[] होगा। यहां संभावित जीत यह है कि आप randomAccessFile.write(record, 0, shortLength) के बजाय कम randomAccessFile.write(buffer, 0, longLength) कर रहे हैं।

Buffer कक्षा - बाइट्स, स्थान प्रारंभ, अंतिम स्थान में एक buffered ब्लॉक के बारे में सभी आवश्यक जानकारी को encapsulating करके आप इसे थोड़ा सा साफ कर सकते हैं। आपको close() विधि में फ़ाइल करने के लिए बफर को फ्लश करने की भी आवश्यकता होगी)।

यही है, तो आप ढेर स्मृति में रिकॉर्ड के ब्लॉक एकत्र कर रहे हैं RandomAccessFile को निस्तब्धता:

  • जब आप अपने बफर के आकार तक पहुँचने,
  • जब एक रिकॉर्ड स्थान के साथ सन्निहित नहीं है वर्तमान बफ़र ब्लॉक
  • पिछले रिकॉर्ड के बाद

मुझे खुशी है कि आप स्मृति बर्बाद नहीं करना चाहते हैं - लेकिन क्या यह ढेर में है की परवाह किए बिना या कहीं और, ज्ञापन आरई स्मृति है, और आप इसके बिना बफरिंग नहीं कर सकते हैं। इस समाधान के साथ आप अपने बफर के आकार को ट्यून कर सकते हैं - और यहां तक ​​कि यदि यह केवल दो रिकॉर्ड के लिए पर्याप्त है, तो यह लिखने की संख्या को कम कर सकता है।

यदि आप स्मृति उपयोग के बारे में कट्टरपंथी बनना चाहते हैं, तो आप गलत भाषा का उपयोग कर रहे हैं।


यदि यह अभी भी पर्याप्त तेज़ नहीं था, तो मैं लिखने को अन्य धागे में ले जाने पर विचार करता। तो अपने रिकॉर्ड को कतार में लिखें, और कतार से फ़ाइल-लेखन धागे का उपभोग करें। यह फाइल को अपने आप में तेजी से लिख नहीं पाएगा, लेकिन इसका मतलब है कि निर्माता बैकलॉग पर पकड़ सकता है जबकि निर्माता अलग-अलग काम कर रहा है - इसलिए इसकी उपयोगिता इस बात पर निर्भर करती है कि निर्माता के पास ऐसा कोई अन्य काम है या नहीं।

+0

मुझे लगता है कि यह एक व्यवहार्य समाधान है, हालांकि अगर मैं केवल एक छोटा सा अंतर था तो मैं पूरे बफर को फ्लश नहीं करूंगा। बफर के लिए कुछ के आवंटन स्मृति उपयोग के लिए स्वीकार्य है। मुझे हालांकि कहना है, मैं उम्मीद कर रहा था कि कहीं भी एक मानक जावा क्लास था जिसने मुझे बिना किसी लिखने के किया। – rghome

+0

बेशक, आप बफर में छोटे खाली ब्लॉक शामिल कर सकते हैं - लेकिन आप सूक्ष्म-अनुकूलन का पीछा कर रहे हैं, और कम रिटर्न होगा। – slim

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