2013-08-29 6 views
15

मैं जावा में नया हूं और बहुत बड़ी फाइलें पढ़ने पर काम कर रहा हूं, समस्या को समझने और इसे हल करने में कुछ मदद की ज़रूरत है। हमारे पास कुछ विरासत कोड हैं जिन्हें इसे ठीक से चलाने के लिए अनुकूलित किया जाना चाहिए। फ़ाइल का आकार केवल 10 एमबी से 10 जीबी तक भिन्न हो सकता है। 800 एमबी आकार से अधिक फ़ाइल शुरू होने पर केवल परेशानी शुरू होती है।जावा आउटऑफमेमरी एक बड़ी टेक्स्ट फ़ाइल पढ़ने में त्रुटि

InputStream inFileReader = channelSFtp.get(path); // file reading from ssh. 
byte[] localbuffer = new byte[2048]; 
ByteArrayOutputStream bArrStream = new ByteArrayOutputStream(); 

int i = 0; 
while (-1 != (i = inFileReader.read(buffer))) { 
bArrStream.write(localbuffer, 0, i); 
} 

byte[] data = bArrStream.toByteArray(); 
inFileReader.close(); 
bos.close(); 

हम त्रुटि

java.lang.OutOfMemoryError: Java heap space 
    at java.util.Arrays.copyOf(Arrays.java:2271) 
    at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:113) 
    at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93) 
    at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:140) 

कोई मदद हो रही है की सराहना की होगी?

java -Xmx1024m .... 

उदहारण के लिए:

+1

आपके द्वारा वर्णित नमूना कोड में, आप बस पूरी फ़ाइल को 'ByteArrayOutputStream' में लोड कर रहे हैं। उपयोग का मामला क्या है? वास्तव में पूरे फ़ाइल डेटा को 'बाइट []' में चाहिए? – Santosh

+0

क्या आप मुझे बता सकते हैं कि आप किस जेडीके संस्करण का उपयोग करने की योजना बना रहे हैं, मेरे पास जेडीके 8 और जेडीके 7 या उससे कम के लिए अलग-अलग समाधान हैं। – Bhupi

+0

@Luffy यह जानने के बिना इस प्रश्न का उत्तर देने के लिए समझ में आता है ** क्यों ** इतना डेटा स्मृति में पढ़ा जाता है? – k3b

उत्तर

7

जावा आभासी मशीन (JVM) नियत ऊपरी स्मृति सीमा, आप इस प्रकार संशोधित कर सकते हैं, जिसके साथ चलाता है उपर्युक्त विकल्प (-Xmx ...) सीमा को 1024 मेगाबाइट तक सेट करता है। आप आवश्यकतानुसार संशोधन कर सकते हैं (आपकी मशीन, ओएस इत्यादि की सीमाओं के भीतर) ध्यान दें कि यह पारंपरिक अनुप्रयोगों से अलग है जो मांग पर ओएस से अधिक से अधिक स्मृति आवंटित करेगा।

हालांकि एक बेहतर समाधान आपके आवेदन इस तरह है कि आप एक बार में स्मृति में पूरे फ़ाइल को लोड करने की जरूरत नहीं है rework करने के लिए है। इस तरह आपको अपने जेवीएम को ट्यून करने की ज़रूरत नहीं है, और आप एक बड़ी मेमोरी पदचिह्न नहीं लगाते हैं।

4

आप स्मृति में 10 जीबी टेक्स्टफाइल नहीं पढ़ सकते हैं। आपको पहले एक्स एमबी पढ़ना है, इसके साथ कुछ करना है और अगले एक्स एमबी को पढ़ने की जरूरत है।

+3

यदि उसके पास 10 जीबी और 64 बिट जेवीएम है तो वह * ऐसा कर सकता है। वह शायद, हालांकि नहीं होना चाहिए। –

+0

विभाजन में पढ़ने के बारे में कोई मदद? –

+0

@ ब्रायन नहीं वह नहीं कर सकता। 64 बिट के तहत भी एक सरणी में केवल तत्वों का सीमित आकार है। – sigi

3

ByteArrayOutputStream एक इन-मेमोरी बफर को लिखता है। यदि यह वास्तव में है कि आप इसे कैसे काम करना चाहते हैं, तो आपको इनपुट के अधिकतम संभव आकार के बाद JVM ढेर का आकार देना होगा। साथ ही, यदि संभव हो, तो आप समय और संसाधनों को बचाने के लिए प्रसंस्करण शुरू करने से पहले इनपुट आकार की जांच कर सकते हैं।

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

4

एक बड़ा बफर पढ़ने का उपयोग करने का प्रयास करें 10 एमबी हो सकता है और फिर जांचें।

4

समस्या आप जो कर रहे हैं उसमें निहित है। पूरी फाइलों को स्मृति में पढ़ना हमेशा और हर जगह एक बुरा विचार है। आप वास्तव में मौजूदा तकनीक के साथ 10 जीबी फ़ाइल को स्मृति में पढ़ने में सक्षम नहीं होंगे जब तक कि आपके पास कुछ सुंदर स्टार्टलिंग हार्डवेयर न हों। लाइन द्वारा लाइन को संसाधित करने के लिए एक तरीका खोजें, रिकॉर्ड द्वारा रिकॉर्ड करें, खंड द्वारा रिकॉर्ड करें, ...

+0

"पूरी फाइलों को स्मृति में पढ़ना हमेशा और हर जगह एक बुरा विचार है"? मेरे संपादक को बताओ! :-) –

17

java.nio.MappedByteBuffer का उपयोग करने का प्रयास करें।

http://docs.oracle.com/javase/7/docs/api/java/nio/MappedByteBuffer.html

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

यदि मेरी समझ सही है, तो स्मृति-मैपिंग फ़ाइल की पूरी सामग्री को स्मृति पर लोड नहीं करती है (जिसका अर्थ है "लोड और अनलोड किए गए आंशिक रूप से आवश्यक"), तो मुझे लगता है कि एक 10 जीबी फ़ाइल आपकी याददाश्त नहीं खाएगी।

4

आउटपुट स्ट्रीम के पूरे ByteArray() प्राप्त करना अनिवार्य है?

byte[] data = bArrStream.toByteArray(); 

बेस्ट दृष्टिकोण लाइन & द्वारा लाइन पढ़ने के लिए है यह लाइन द्वारा लाइन लिखें। नीचे दी गई बड़ी फ़ाइलों को पढ़ने के लिए आप BufferedReader या Scanner का उपयोग कर सकते हैं।

import java.io.*; 
import java.util.*; 

public class FileReadExample { 
    public static void main(String args[]) throws FileNotFoundException { 
    File fileObj = new File(args[0]); 

    long t1 = System.currentTimeMillis(); 
    try { 
     // BufferedReader object for reading the file 
     BufferedReader br = new BufferedReader(new FileReader(fileObj)); 
     // Reading each line of file using BufferedReader class 
     String str; 
     while ((str = br.readLine()) != null) { 
      System.out.println(str); 
     } 
    }catch(Exception err){ 
     err.printStackTrace(); 
    } 
    long t2 = System.currentTimeMillis(); 
    System.out.println("Time taken for BufferedReader:"+(t2-t1)); 

    t1 = System.currentTimeMillis(); 
    try (
     // Scanner object for reading the file 
     Scanner scnr = new Scanner(fileObj);) { 
     // Reading each line of file using Scanner class 
     while (scnr.hasNextLine()) { 
      String strLine = scnr.nextLine(); 
      // print data on console 
      System.out.println(strLine); 
     } 
    } 
    t2 = System.currentTimeMillis(); 
    System.out.println("Time taken for scanner:"+(t2-t1)); 

    } 
} 

ऊपर दिए गए उदाहरण आपके ByteArrayOutputStream साथ System.out बदल सकते हैं। Read Large File

संबंधित एसई सवाल पर एक नज़र डालें:

कृपया अधिक जानकारी के लिए नीचे दिए गए लेख पर एक नजर है

Scanner vs. BufferedReader

11

भले ही आप JVM स्मृति सीमा बढ़ा सकते हैं, यह कहने की जरूरत नहीं है और फाइल को संसाधित करने के लिए 10 जीबी की तरह एक बड़ी मेमोरी आवंटित करना ओवरकिल और संसाधन गहन लगता है।

वर्तमान में आप "बाइटएरे ऑटपुटस्ट्रीम" का उपयोग कर रहे हैं जो डेटा को रखने के लिए आंतरिक मेमोरी रखता है। अपने कोड में यह पंक्ति इस बफर के अंत करने के लिए पिछले पढ़ने 2KB फ़ाइल हिस्सा जोड़कर रखता है:

bArrStream.write(localbuffer, 0, i); 

bArrStream बढ़ रही रखता है और अंततः आप स्मृति से बाहर चलाने के।

इसके बजाय आप अपने एल्गोरिथ्म का पुनर्गठन करने और एक स्ट्रीमिंग रास्ते में फ़ाइल पर कार्रवाई करना चाहिए:

InputStream inFileReader = channelSFtp.get(path); // file reading from ssh. 
byte[] localbuffer = new byte[2048]; 

int i = 0; 
while (-1 != (i = inFileReader.read(buffer))) { 
    //Deal with the current read 2KB file chunk here 
} 

inFileReader.close(); 
3

हाय मैं यह सोचते हैं कि आप लाइन द्वारा बड़े txt फ़ाइल पढ़ रहे हैं और डेटा लाइन द्वारा लाइन सेट किया गया है, उपयोग लाइन पढ़ने का दृष्टिकोण। जैसा कि मुझे पता है कि आप 6 जीबी तक पढ़ सकते हैं और अधिक हो सकते हैं। मैं इस दृष्टिकोण को आजमाने के लिए दृढ़ता से सलाह देता हूं।

data1 DATA2 ...

// Open the file 
FileInputStream fstream = new FileInputStream("textfile.txt"); 
BufferedReader br = new BufferedReader(new InputStreamReader(fstream)); 

    String strLine; 

//Read File Line By Line 
while ((strLine = br.readLine()) != null) { 
    // Print the content on the console 
    System.out.println (strLine); 
} 

//Close the input stream 
br.close(); 

Refrence for the code fragment

3

iteratively linewise फ़ाइल पढ़ें। इससे मेमोरी खपत में काफी कमी आएगी। वैकल्पिक रूप से आप

FileUtils.lineIterator (theFile, "UTF-8") का उपयोग कर सकते हैं;

अपाचे कॉमन्स आईओ द्वारा प्रदान किया गया।

FileInputStream inputStream = null; 
Scanner sc = null; 
try { 
inputStream = new FileInputStream(path); 
sc = new Scanner(inputStream, "UTF-8"); 
while (sc.hasNextLine()) { 
    String line = sc.nextLine(); 
    // System.out.println(line); 
} 
// note that Scanner suppresses exceptions 
if (sc.ioException() != null) { 
    throw sc.ioException(); 
} 
} finally { 
if (inputStream != null) { 
    inputStream.close(); 
} 
if (sc != null) { 
    sc.close(); 
} 

}

5

रन कमांड लाइन विकल्प -Xmx, जो ढेर का अधिकतम आकार सेट के साथ जावा।

See here for details..

+0

यह लिंक मेरे लिए काम नहीं कर रहा है, क्या आप लिंक के अतिरिक्त यहां महत्वपूर्ण जानकारी डाल सकते हैं? – innoSPG

2

आप ढेर आकार के रूप में निम्नलिखित जवाब में कहा गया है वृद्धि करनी चाहिए:

Increase heap size in Java

लेकिन याद रखें कि जावा रनटाइम और आप कोड कुछ जगह के साथ-साथ ले तो कुछ बफर जोड़ने वांछित अधिकतम।

2

लघु जवाब,

कुछ भी करने के बिना, आप 1.5 का एक पहलू से वर्तमान सीमा को धक्का कर सकते हैं। इसका मतलब है कि, यदि आप 800 एमबी की प्रक्रिया करने में सक्षम हैं, तो आप 1200 एमबी की प्रक्रिया कर सकते हैं। इसका मतलब यह भी है कि अगर java -Xm .... के साथ कुछ चाल से आप उस बिंदु पर जा सकते हैं जहां आपका वर्तमान कोड 7 जीबी संसाधित कर सकता है, तो आपकी समस्या हल हो जाती है, क्योंकि 1.5 कारक आपको 10.5 जीबी तक ले जाएगा, मानते हैं कि आपके पास उस सिस्टम में आपके स्थान पर उपलब्ध है और कि जेवीएम इसे प्राप्त कर सकता है।

लांग जवाब:

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

क्या अब हो रहा है कि ByteArrayOutputStream वस्तुओं आकार 32 यदि आप का एक डिफ़ॉल्ट बफर के साथ बनाया जाता है किसी भी आकार की आपूर्ति नहीं (यह आपका मामला है)। जब भी आप ऑब्जेक्ट पर write विधि को कॉल करते हैं, तो एक आंतरिक मशीनरी शुरू होती है। openjdk implementation release 7u40-b43 जो आपकी त्रुटि के आउटपुट के साथ पूरी तरह मेल खाता है, यह जांचने के लिए कि बफर में बाइट्स लिखने के लिए पर्याप्त जगह है, एक आंतरिक विधि ensureCapacity का उपयोग करती है। यदि पर्याप्त कमरा नहीं है, तो बफर के आकार को बढ़ाने के लिए एक और आंतरिक विधि grow कहा जाता है। विधि grow उचित आकार को परिभाषित करता है और नौकरी करने के लिए Arrays कक्षा से copyOf विधि को कॉल करता है। बफर का उचित आकार वर्तमान आकार और सभी सामग्री (वर्तमान सामग्री और नई सामग्री लिखने के लिए) के आकार के बीच अधिकतम है। वर्ग Arrays (follow the link) से विधि नए बफर के लिए स्थान आवंटित करती है, पुराने बफर की सामग्री को नए पर कॉपी करें और इसे grow पर वापस कर दें।

आपकी समस्या नए बफर के लिए स्थान के आवंटन पर होती है, कुछ write के बाद, आप उस बिंदु पर पहुंच जाते हैं जहां उपलब्ध स्मृति समाप्त हो जाती है: java.lang.OutOfMemoryError: Java heap space

अगर हम विस्तार में देखें, तो आप 2048 का हिस्सा द्वारा पढ़ रहे हैं तो

  • के लिए अपना पहला लिखने 2048
  • अपने दूसरे कॉल करने के लिए 32 से बफर के आकार बढ़ता करने के लिए इसे दोगुना होगा 2 * 2048
  • आपका तीसरा कॉल इसे 2^2 * 2048 पर ले जाएगा, आपको आवंटन की आवश्यकता से पहले दो बार लिखने का समय है।
  • फिर 2^3 * 2048, आपके पास फिर से आवंटित करने से पहले 4 मोर्स लिखने का समय होगा।
  • किसी बिंदु पर, आपका बफर आकार 2^18 * 2048 होगा जो 2^1 9 * 1024 या 2^9 * 2^20 (512 एमबी)
  • फिर 2^1 9 * 2048 जो 1024 एमबी है या 1 जीबी

कुछ जो आपके वर्णन में अस्पष्ट है वह यह है कि आप 800 एमबी तक पढ़ सकते हैं, लेकिन आगे नहीं जा सकते हैं। आपको मुझे यह समझा देना है।

मुझे उम्मीद है कि आपकी सीमा बिल्कुल 2 की शक्ति होगी (या अगर हम 10 इकाइयों की शक्ति का उपयोग करते हैं तो बंद करें)। उस संबंध में, मैं उम्मीद करता हूं कि आप इनमें से किसी एक के ऊपर तुरंत परेशानी शुरू करें: 256 एमबी, 512 एमबी, 1 जीबी, 2 जीबी, आदि

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

ByteArrayOutputStream bArrStream = new ByteArrayOutputStream(myMaxSize); 

यह सिर के ऊपर पृष्ठभूमि स्मृति आवंटन के तहत होता है को कम करने का लाभ दिया है फोन करके यह अग्रिम आरक्षित कर सकते हैं बफर का अधिकतम आकार को खोजने आपको खुश रखने के लिए हुड ऐसा करके, आप अभी 1.5 सीमा तक पहुंचने में सक्षम होंगे। यह बस इसलिए है क्योंकि आखिरी बार बफर बढ़ गया था, यह वर्तमान आकार के आधे से वर्तमान आकार तक चला गया, और किसी बिंदु पर आपके पास मौजूदा बफर और बूढ़े दोनों को याद में मिला। लेकिन अब आप अपनी सीमा के 3 गुना से आगे नहीं जा पाएंगे। स्पष्टीकरण बिल्कुल वही है।

कहा गया है कि, मेरे पास डेटा को दिए गए आकार के हिस्सों, एक समय में एक हिस्से से संसाधित करने के अलावा समस्या को हल करने के लिए कोई जादू सुझाव नहीं है। एक और अच्छा दृष्टिकोण Takahiko Kawasaki के सुझाव का उपयोग करना होगा और MappedByteBuffer का उपयोग करना होगा। ध्यान रखें कि किसी भी मामले में आपको 10 जीबी की फाइल लोड करने में सक्षम होने के लिए कम से कम 10 जीबी भौतिक मेमोरी या स्वैप मेमोरी की आवश्यकता होगी।

देखें

0

इसके बारे में सोचने के बाद, मैंने दूसरा जवाब देने का फैसला किया। मैंने इस दूसरे उत्तर को रखने के फायदे और नुकसान पर विचार किया, और फायदे इसके लिए जा रहे हैं। तो यहाँ यह है।

सुझाए गए विचारों में से अधिकांश एक दिए गए तथ्य को भूल रहे हैं: सरणी के आकार में एक अंतर्निहित सीमा है (ByteArrayOutputStream सहित) जिसमें आप जावा में हो सकते हैं। और वह सीमा सबसे बड़ी int मान द्वारा निर्धारित की जाती है जो 2^31 - 1 (2 गीगा से थोड़ा कम) है। इसका मतलब है कि आप केवल अधिकतम 2 जीबी (-1 बाइट) पढ़ सकते हैं और इसे एक ByteArrayOutputStream में डाल सकते हैं। यदि वीएम अधिक नियंत्रण चाहता है तो सीमा वास्तव में सरणी आकार के लिए छोटी हो सकती है।

मेरा सुझाव फ़ाइल की पूरी सामग्री रखने वाले byte[] के बजाय byte[] के ArrayList का उपयोग करना है। और इसे अंतिम data सरणी में डालने से पहले ByteArrayOutputStream में डालने के आवश्यक चरण को भी हटा दें। यहाँ एक उदाहरण अपने मूल कोड के आधार पर है:

InputStream inFileReader = channelSFtp.get(path); // file reading from ssh. 

// good habits are good, define a buffer size 
final int BUF_SIZE = (int)(Math.pow(2,30)); //1GB, let's not go close to the limit 

byte[] localbuffer = new byte[BUF_SIZE]; 

int i = 0; 
while (-1 != (i = inFileReader.read(localbuffer))) { 
    if(i<BUF_SIZE){ 
     data.add(Arrays.copyOf(localbuffer, i)) 
     // No need to reallocate the reading buffer, we copied the data 
    }else{ 
     data.add(localbuffer) 
     // reallocate the reading buffer 
     localbuffer = new byte[BUF_SIZE] 
    } 
} 

inFileReader.close(); 
// Process your data, keep in mind that you have a list of buffers. 
// So you need to loop over the list 

बस अपने कार्यक्रम के लिए पर्याप्त भौतिक स्मृति या स्वैप के साथ 64 बिट्स सिस्टम पर ठीक काम करना चाहिए चल रहा है। अब यदि आप शुरुआत में वीएम आकार को सही ढंग से ढेर में मदद करने के लिए इसे गति देना चाहते हैं, तो -Xms और -Xmx विकल्पों के साथ चलाएं।उदाहरण के लिए यदि आप 10 जीबी फ़ाइल को संभालने में सक्षम होने के लिए 12 जीबी का ढेर चाहते हैं, तो java -Xms12288m -Xmx12288m YourApp

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