2011-08-31 21 views
14

मुझे डिस्क में 40 एमबी फ़ाइल मिली है और मुझे इसे बाइट सरणी का उपयोग करके स्मृति में "मैप" करने की आवश्यकता है।जावा: मेमोरी कुशल ByteArrayOutputStream

सबसे पहले, मैंने सोचा था कि फ़ाइल को बाइटएरे ऑटपुटस्ट्रीम में लिखना सबसे अच्छा तरीका होगा, लेकिन मुझे लगता है कि कॉपी ऑपरेशन के दौरान कुछ पल में 160 एमबी हीप स्पेस लेता है।

क्या किसी को RAM के फ़ाइल आकार के तीन बार उपयोग किए बिना ऐसा करने का बेहतर तरीका पता है?

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

एक और उच्च मेमोरी स्पॉट है: जब मैं बाइट [] वापस ByteArrayOutputStream.toByteArray के साथ मिलता हूं। इसके स्रोत कोड के लिए एक नज़र ले रहा है, मैं देख सकता यह सरणी क्लोनिंग है:

public synchronized byte toByteArray()[] { 
    return Arrays.copyOf(buf, count); 
} 

मैं सोच रहा हूँ मैं सिर्फ ByteArrayOutputStream का विस्तार करने और इस विधि को फिर से लिखने सकता है, इसलिए सीधे मूल सरणी वापस जाने के लिए। क्या यहां कोई संभावित खतरा है, धारा और बाइट सरणी को एक से अधिक बार उपयोग नहीं किया जाएगा?

+0

इसी तरह के प्रश्न http://stackoverflow.com/questions/964332/java-large-files-disk-io-performance – Santosh

उत्तर

13

MappedByteBuffer जो भी आप खोज रहे हैं हो सकता है।

मुझे आश्चर्य है कि स्मृति में फ़ाइल पढ़ने के लिए यह बहुत रैम लेता है, हालांकि। क्या आपने उचित क्षमता के साथ ByteArrayOutputStream का निर्माण किया है? यदि आपने नहीं किया है, तो स्ट्रीम 40 एमबी के अंत के करीब एक नया बाइट सरणी आवंटित कर सकती है, जिसका अर्थ है कि, उदाहरण के लिए, आप 3 9 एमबी का पूरा बफर और आकार के दो बार एक नया बफर लेंगे। जबकि अगर धारा में उचित क्षमता है, तो कोई पुनर्वितरण (तेज) नहीं होगा, और कोई बर्बाद स्मृति नहीं होगी।

+0

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

5

यदि आप वास्तव में मानचित्र मेमोरी में फ़ाइल चाहते हैं, तो FileChannel उचित तंत्र है।

यदि सब आप क्या करना चाहते एक सरल byte[] में फ़ाइल पढ़ने के लिए है (और वापस फाइल करने के लिए प्रदर्शित होने में कि सरणी में परिवर्तन की जरूरत नहीं है), तो बस एक सामान्य FileInputStream से एक उचित रूप से आकार byte[] में पढ़ने पर्याप्त होना चाहिए

GuavaFiles.toByteArray() है जो आपके लिए यह सब कुछ करता है।

+0

गुवा इस समस्या के लिए सबसे अच्छा choise है। धन्यवाद। – danik

10

ByteArrayOutputStream ठीक तब तक ठीक होना चाहिए जब आप निर्माता में उचित आकार निर्दिष्ट करते हैं। जब आप toByteArray पर कॉल करते हैं, तब भी यह एक प्रतिलिपि बनायेगा, लेकिन यह केवल अस्थायी है। क्या आपको वास्तव में स्मृति संक्षिप्त रूप से बहुत बढ़ रहा है?

वैकल्पिक रूप से, यदि आप पहले से ही अपने साथ शुरू करने के आकार को जानते हैं तो बस एक बाइट सरणी बना सकते हैं और उस बफर में FileInputStream से बार-बार पढ़ सकते हैं जब तक कि आपके पास सभी डेटा न हो।

+0

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

+0

@ user683887: फिर मैंने प्रस्तुत किए गए दूसरे विकल्प को बनाने के बारे में कैसे? इसके लिए केवल उतना ही डेटा आवश्यक होगा जितना आवश्यक है।यदि आपको फ़िल्टर लागू करने की आवश्यकता है, तो आप हमेशा फ़ाइल को दो बार पढ़ सकते हैं - एक बार यह जानने के लिए कि आपको किस आकार की आवश्यकता है, फिर फिर डेटा को वास्तव में पढ़ने के लिए। –

2

यदि आपके पास 40 एमबी डेटा है तो मुझे कोई कारण नहीं दिख रहा है कि बाइट [] बनाने के लिए 40 एमबी से अधिक समय क्यों लगेगा। मुझे लगता है कि आप एक बढ़ते ByteArrayOutputStream का उपयोग कर रहे हैं जो समाप्त होने पर बाइट [] प्रतिलिपि बनाता है।

आप पुराने बार फ़ाइल को एक बार दृष्टिकोण में पढ़ने का प्रयास कर सकते हैं।

File file = 
DataInputStream is = new DataInputStream(FileInputStream(file)); 
byte[] bytes = new byte[(int) file.length()]; 
is.readFully(bytes); 
is.close(); 

एक MappedByteBuffer का उपयोग करते हुए और अधिक कुशल है और डेटा की एक प्रतिलिपि (या ढेर ज्यादा उपयोग करते हुए) आप ByteBuffer सीधे उपयोग कर सकते हैं प्रदान की बचा जाता है, तो आप एक बाइट [] इसकी ज्यादा मदद करने की संभावना नहीं उपयोग करने के लिए लेकिन यदि।

2

... लेकिन मैं यह हद मैं अपने शक है कि करने के लिए प्रतिलिपि आपरेशन

मैं इस अत्यंत आश्चर्य की बात लगता है के दौरान कुछ पल में ढेर अंतरिक्ष के 160MB के बारे में लेता है ... लगता है कि आप ढेर उपयोग सही ढंग से माप रहे हैं। जिस तरह से

BufferedInputStream bis = new BufferedInputStream(
     new FileInputStream("somefile")); 
ByteArrayOutputStream baos = new ByteArrayOutputStream(); /* no hint !! */ 

int b; 
while ((b = bis.read()) != -1) { 
    baos.write((byte) b); 
} 
byte[] stuff = baos.toByteArray(); 

अब जब कि एक ByteArrayOutputStream का प्रबंधन करता है अपने बफर एक प्रारंभिक आकार आवंटित करने के लिए है, और (कम से कम) जब यह भरता अप बफर दोगुना:

मान लेते हैं कि अपने कोड कुछ इस तरह है चलो । इस प्रकार, सबसे खराब मामले में baos 40 एमबी फ़ाइल को पकड़ने के लिए 80 एमबी बफर का उपयोग कर सकता है।

अंतिम चरण बफर की सामग्री को पकड़ने के लिए बिल्कुल baos.size() बाइट्स की एक नई सरणी आवंटित करता है। वह 40 एमबी है। तो वास्तव में उपयोग में आने वाली स्मृति की चोटी की मात्रा 120 एमबी होनी चाहिए।

तो उन अतिरिक्त 40 एमबी का उपयोग कहां किया जा रहा है? मेरा अनुमान है कि वे नहीं हैं, और आप वास्तव में कुल ढेर आकार की रिपोर्ट कर रहे हैं, न कि पहुंच योग्य वस्तुओं द्वारा कब्जा कर लिया गया स्मृति की मात्रा।


तो समाधान क्या है?

  1. आप मेमोरी मैप किए गए बफर का उपयोग कर सकते हैं।

  2. जब आप ByteArrayOutputStream आवंटित करते हैं तो आप एक आकार संकेत दे सकते हैं; जैसे

    ByteArrayOutputStream baos = ByteArrayOutputStream(file.size()); 
    
  3. आप ByteArrayOutputStream पूरी तरह से साथ बांटना और एक बाइट सरणी में सीधे पढ़ सकते हैं।

    byte[] buffer = new byte[file.size()]; 
    FileInputStream fis = new FileInputStream(file); 
    int nosRead = fis.read(buffer); 
    /* check that nosRead == buffer.length and repeat if necessary */ 
    

दोनों विकल्प 1 और 2 40MB की एक चोटी स्मृति उपयोग एक 40MB फ़ाइल पढ़ते समय होनी चाहिए; यानी कोई बर्बाद जगह नहीं है।


यदि आप अपना कोड पोस्ट करते हैं तो यह सहायक होगा, और स्मृति उपयोग को मापने के लिए आपकी पद्धति का वर्णन किया जाएगा।


मैं सोच रहा हूँ मैं सिर्फ ByteArrayOutputStream का विस्तार कर सकते हैं और फिर से लिखने के लिए इस विधि है, तो सीधे मूल सरणी वापस जाने के लिए। क्या यहां कोई संभावित खतरा है, धारा और बाइट सरणी को एक से अधिक बार उपयोग नहीं किया जाएगा?

संभावित खतरा यह है कि अपनी मान्यताओं सही नहीं हैं, या किसी की वजह से गलत हो जाते हैं और कुछ अनजाने अपना कोड संशोधित ...

+0

धन्यवाद, @ स्टीफन। आप सही थे, अतिरिक्त ढेर उपयोग बीएओएस आकार के गलत प्रारंभिक कारण के कारण था, जैसा कि मैंने अपने अपडेट में वर्णित किया था। मैं स्मृति उपयोग को मापने के लिए visualvm का उपयोग कर रहा हूं: सुनिश्चित नहीं है कि यह सबसे अच्छा तरीका है या नहीं। – user683887

1

ByteArrayOutputStream के बफर विकास व्यवहार की व्याख्या के लिए, कृपया this answer पढ़ें।

अपने प्रश्न के उत्तर में, ByteArrayOutputStream का विस्तार करने के लिए सुरक्षित है। आपकी स्थिति में, संभवतः लिखने के तरीकों को ओवरराइड करना बेहतर है कि अधिकतम अतिरिक्त आवंटन सीमित है, कहें, 16 एमबी तक। संरक्षित buf [] सदस्य का पर्दाफाश करने के लिए आपको toByteArray को ओवरराइड नहीं करना चाहिए। ऐसा इसलिए है क्योंकि एक धारा एक बफर नहीं है; एक धारा एक बफर है जिसमें स्थिति सूचक और सीमा सुरक्षा होती है। इसलिए, वर्ग के बाहर से बफर को संभावित रूप से उपयोग करना और संभावित रूप से उपयोग करना खतरनाक है।

1

Google गुवा ByteSource स्मृति में बफरिंग के लिए एक अच्छा विकल्प प्रतीत होता है। ByteArrayOutputStream या ByteArrayList (कोल्ट लाइब्रेरी से) के कार्यान्वयन के विपरीत यह डेटा को एक विशाल बाइट सरणी में विलय नहीं करता है लेकिन प्रत्येक खंड को अलग से स्टोर करता है। एक उदाहरण:

List<ByteSource> result = new ArrayList<>(); 
try (InputStream source = httpRequest.getInputStream()) { 
    byte[] cbuf = new byte[CHUNK_SIZE]; 
    while (true) { 
     int read = source.read(cbuf); 
     if (read == -1) { 
      break; 
     } else { 
      result.add(ByteSource.wrap(Arrays.copyOf(cbuf, read))); 
     } 
    } 
} 
ByteSource body = ByteSource.concat(result); 

ByteSource के रूप में एक InputStream कभी भी बाद में पढ़ा जा सकता है:

InputStream data = body.openBufferedStream(); 
2

मैं मैं सिर्फ ByteArrayOutputStream का विस्तार करने और इस विधि को फिर से लिखने सकता है सोच रहा हूँ, इसलिए मूल सरणी वापस जाने के लिए सीधे। क्या यहां कोई संभावित खतरा है, धारा और बाइट सरणी को एक से अधिक बार उपयोग नहीं किया जाएगा?

आपको मौजूदा विधि के निर्दिष्ट व्यवहार को नहीं बदलना चाहिए, लेकिन यह एक नई विधि जोड़ने के लिए बिल्कुल ठीक है। यहाँ एक कार्यान्वयन है:

/** Subclasses ByteArrayOutputStream to give access to the internal raw buffer. */ 
public class ByteArrayOutputStream2 extends java.io.ByteArrayOutputStream { 
    public ByteArrayOutputStream2() { super(); } 
    public ByteArrayOutputStream2(int size) { super(size); } 

    /** Returns the internal buffer of this ByteArrayOutputStream, without copying. */ 
    public synchronized byte[] buf() { 
     return this.buf; 
    } 
} 

एक वैकल्पिक लेकिन बफर से किसी भी ByteArrayOutputStream तथ्य यह है कि इसके writeTo(OutputStream) विधि प्रदान की OutputStream करने के लिए सीधे बफर गुजरता उपयोग करने के लिए है होने का hackish तरीका:

/** 
* Returns the internal raw buffer of a ByteArrayOutputStream, without copying. 
*/ 
public static byte[] getBuffer(ByteArrayOutputStream bout) { 
    final byte[][] result = new byte[1][]; 
    try { 
     bout.writeTo(new OutputStream() { 
      @Override 
      public void write(byte[] buf, int offset, int length) { 
       result[0] = buf; 
      } 

      @Override 
      public void write(int b) {} 
     }); 
    } catch (IOException e) { 
     throw new RuntimeException(e); 
    } 
    return result[0]; 
} 

(यह काम करता है, लेकिन मुझे यकीन नहीं है कि यह उपयोगी है, बशर्ते कि उप-वर्गीकरण ByteArrayOutputStream आसान है।)

हालांकि, आपके बाकी प्रश्न से यह पसंद है ई आप जो चाहते हैं वह फ़ाइल की पूरी सामग्री का एक सादा byte[] है। जावा 7 के अनुसार, ऐसा करने का सबसे सरल और तेज़ तरीका Files.readAllBytes पर कॉल करें। जावा 6 और नीचे में, आप DataInputStream.readFully का उपयोग कर सकते हैं, जैसा कि Peter Lawrey's answer में है। किसी भी तरह से, आपको एक सरणी मिलेगी जिसे आवंटित किया गया है, एक बार सही आकार पर, ByteArrayOutputStream के बार-बार पुनर्वितरण के बिना।

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