2010-04-13 14 views
11

मैं दुर्भाग्यवश, दो प्रकार के वर्ण एन्कोडिंग वाली फ़ाइल से डेटा पढ़ रहा हूं।इनपुटस्ट्रीम रीडर बफरिंग समस्या

एक शीर्षलेख और शरीर है। हेडर हमेशा ASCII में होता है और उस चरित्र सेट को परिभाषित करता है जिसमें शरीर को एन्कोड किया जाता है।

हेडर निश्चित लंबाई नहीं है और इसकी सामग्री/लंबाई निर्धारित करने के लिए एक पार्सर के माध्यम से चलाया जाना चाहिए।

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

तो मैंने एक इनपुट इनपुट के साथ शुरू किया। मैं शुरुआत में एएससीआईआईआई के साथ एक इनपुटस्ट्रीम रीडर के साथ लपेटता हूं और हेडर को डीकोड करता हूं और शरीर के लिए चरित्र सेट निकालता हूं। सब अच्छा।

फिर मैं सही चरित्र सेट के साथ एक नया इनपुटस्ट्रीम रीडर बना देता हूं, इसे उसी इनपुटस्ट्रीम पर छोड़ देता हूं और शरीर को पढ़ने की कोशिश करना शुरू करता हूं।

दुर्भाग्यवश ऐसा प्रतीत होता है, जावाडोक यह पुष्टि करता है कि इनपुटस्ट्रीम रीडर effeciency उद्देश्यों के लिए आगे पढ़ने का विकल्प चुन सकता है। तो हेडर का पठन कुछ/सभी शरीर को चबाता है।

क्या किसी को इस मुद्दे के आसपास काम करने के लिए कोई सुझाव है? एक CharsetDecoder मैन्युअल रूप से और एक बाइट में एक बार में भोजन करना एक अच्छा विचार (संभवतः एक कस्टम रीडर कार्यान्वयन में लपेटा गया है?)

अग्रिम धन्यवाद।

संपादित करें: मेरा अंतिम समाधान एक इनपुटस्ट्रीम रीडर लिखना था जिसमें यह सुनिश्चित करने के लिए कोई बफरिंग नहीं है कि मैं शरीर के चबाने के बिना हेडर को पार्स कर सकता हूं। यद्यपि यह बहुत कुशल नहीं है, लेकिन मैं कच्चे इनपुटस्ट्रीम को BufferedInputStream के साथ लपेटता हूं, इसलिए यह कोई समस्या नहीं होगी।

// An InputStreamReader that only consumes as many bytes as is necessary 
// It does not do any read-ahead. 
public class InputStreamReaderUnbuffered extends Reader 
{ 
    private final CharsetDecoder charsetDecoder; 
    private final InputStream inputStream; 
    private final ByteBuffer byteBuffer = ByteBuffer.allocate(1); 

    public InputStreamReaderUnbuffered(InputStream inputStream, Charset charset) 
    { 
     this.inputStream = inputStream; 
     charsetDecoder = charset.newDecoder(); 
    } 

    @Override 
    public int read() throws IOException 
    { 
     boolean middleOfReading = false; 

     while (true) 
     { 
      int b = inputStream.read(); 

      if (b == -1) 
      { 
       if (middleOfReading) 
        throw new IOException("Unexpected end of stream, byte truncated"); 

       return -1; 
      } 

      byteBuffer.clear(); 
      byteBuffer.put((byte)b); 
      byteBuffer.flip(); 

      CharBuffer charBuffer = charsetDecoder.decode(byteBuffer); 

      // although this is theoretically possible this would violate the unbuffered nature 
      // of this class so we throw an exception 
      if (charBuffer.length() > 1) 
       throw new IOException("Decoded multiple characters from one byte!"); 

      if (charBuffer.length() == 1) 
       return charBuffer.get(); 

      middleOfReading = true; 
     } 
    } 

    public int read(char[] cbuf, int off, int len) throws IOException 
    { 
     for (int i = 0; i < len; i++) 
     { 
      int ch = read(); 

      if (ch == -1) 
       return i == 0 ? -1 : i; 

      cbuf[ i ] = (char)ch; 
     } 

     return len; 
    } 

    public void close() throws IOException 
    { 
     inputStream.close(); 
    } 
} 
+1

हो सकता है कि मैं गलत हूँ, लेकिन इस समय के बाद से मैंने सोचा था कि फ़ाइल एक ही समय में केवल एक ही एन्कोडिंग प्रकार हो सकता है। – Roman

+4

@Roman: आप कुछ भी फाइलों के साथ कर सकते हैं; वे केवल बाइट्स के अनुक्रम हैं। तो आप बाइट्स का एक गुच्छा लिख ​​सकते हैं जिसका अर्थ ASCII के रूप में किया जाना है, फिर यूटीएफ -16 के रूप में व्याख्या करने के लिए एक और अधिक बाइट्स लिखें, और यूटीएफ -32 के रूप में व्याख्या करने के लिए और भी बाइट्स लिखें। मैं यह नहीं कह रहा हूं कि यह एक अच्छा विचार है, हालांकि ओपी का उपयोग मामला निश्चित रूप से उचित है (आपके पास यह इंगित करने के लिए * कुछ * तरीका होना चाहिए कि फ़ाइल का एन्कोडिंग किस प्रकार उपयोग करता है)। –

+0

@ माइक क्यू - इनपुटस्ट्रीम रीडर यूनबफर्ड का अच्छा विचार। मैं एक अलग उत्तर का सुझाव देता हूं - यह ध्यान देने योग्य है :) –

उत्तर

3

आप 2 InputStream एस का उपयोग क्यों नहीं करते? एक हेडर पढ़ने के लिए और शरीर के लिए एक और।

दूसरा InputStreamskip हेडर बाइट्स होना चाहिए।

+0

धन्यवाद मुझे लगता है कि मुझे यह करना होगा। –

+0

आप कैसे जानते हैं कि क्या छोड़ना है? यह कहां समाप्त होता है यह जानने के लिए आपको हेडर को पढ़ने की आवश्यकता है। एक बार जब आप एक इनपुटस्ट्रेरा रीडर के साथ हेडर पढ़ना शुरू कर देते हैं, तो यह शरीर से बाइट्स पर चबा सकता है। –

1

मेरा पहला विचार स्ट्रीम को बंद करना और InputStream#skip का उपयोग करके InputStreamReader पर स्ट्रीम देने से पहले शीर्षलेख को छोड़ने के लिए इसे फिर से खोलना है।

आप सच में, सच नहीं फ़ाइल को फिर से खोलने के लिए चाहते हैं, तो आप file descriptors का उपयोग फाइल करने के लिए एक से अधिक धारा प्राप्त करने के लिए कर सकता है, हालांकि आप आप कर सकते हैं के बाद से channels उपयोग करने के लिए फ़ाइल में एक से अधिक पदों के लिए (हो सकता है मान लें कि आप reset के साथ स्थिति को रीसेट कर सकते हैं, यह समर्थित नहीं हो सकता है)।

+0

यदि आप एकाधिक 'FileInputStream' को उसी 'फ़ाइलडिस्क्रिप्टर' के साथ बनाते हैं, तो वे व्यवहार करेंगे जैसे कि वे एक ही स्ट्रीम हैं। –

+0

@ टॉम: हाँ, मैं मान रहा था कि वह श्रृंखला में उनका उपयोग करेगा, समानांतर में नहीं, और वह एक का उपयोग करने और दूसरे का उपयोग करने के बीच स्थिति को रीसेट करेगा। लेकिन आप यह नहीं मान सकते कि आप स्थिति को रीसेट कर सकते हैं ... (मुझे नहीं लगता कि वे * एक ही स्ट्रीम * की तरह व्यवहार करेंगे, मुझे लगता है कि इससे भी बदतर होगा; वे केवल वास्तविक फ़ाइल स्थिति साझा करेंगे। डेटा व्यक्तिगत उदाहरणों के भीतर कैशिंग सिद्धांत में वास्तव में, वास्तव में गड़बड़ कर सकती है अगर आपने समानांतर में उनका उपयोग करने का प्रयास किया।) –

1

मैं सुझाव देता हूं कि शुरुआत से स्ट्रीम को नए InputStreamReader के साथ शुरू करें। शायद मान लें कि InputStream.mark समर्थित है।

3

यहां छद्म कोड है।

  1. उपयोग InputStream, लेकिन यह चारों ओर एक Reader लपेट नहीं है।
  2. हेडर युक्त 0 बाइट पढ़ें और उन्हें ByteArrayOutputStream में संग्रहीत करें।
  3. ByteArrayOutputStream से ByteArrayInputStream बनाएँ और हैडर को डिकोड, इस बार ASCII चारसेट साथ Reader में ByteArrayInputStream लपेट दें।
  4. गैर-एएससीआई इनपुट की लंबाई की गणना करें, और बाइट्स की संख्या ByteArrayOutputStream में पढ़ें।
  5. दूसरा ByteArrayOutputStream से दूसरे ByteArrayInputStream बनाएँ और यह लपेट Reader साथ हैडर से चारसेट साथ।
+0

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

1

यह और भी आसान है:

जैसा कि आपने कहा, आपके शीर्षक ASCII में हमेशा होता है। तो InputStream से सीधे शीर्ष लेख पढ़ा है, और जब आप इसे पूरा कर चुके हैं, सही एन्कोडिंग के साथ रीडर बनाने और पढ़ने से यह

private Reader reader; 
private InputStream stream; 

public void read() { 
    int c = 0; 
    while ((c = stream.read()) != -1) { 
     // Read encoding 
     if (headerFullyRead) { 
      reader = new InputStreamReader(stream, encoding); 
      break; 
     } 
    } 
    while ((c = reader.read()) != -1) { 
     // Handle rest of file 
    } 
} 
+0

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

1

आप InputStream लपेट और सीमित कर बिल्कुल पढ़ता सिर्फ 1 बाइट के लिए तो एक समय, ऐसा लगता है कि इनपुटस्ट्रीम रीडर के अंदर बफरिंग अक्षम है।

इस तरह हमें इनपुटस्ट्रीम रीडर तर्क को फिर से लिखना नहीं है।

public class OneByteReadInputStream extends InputStream 
{ 
    private final InputStream inputStream; 

    public OneByteReadInputStream(InputStream inputStream) 
    { 
     this.inputStream = inputStream; 
    } 

    @Override 
    public int read() throws IOException 
    { 
     return inputStream.read(); 
    } 

    @Override 
    public int read(byte[] b, int off, int len) throws IOException 
    { 
     return super.read(b, off, 1); 
    } 
} 

का निर्माण करने के लिए:

new InputStreamReader(new OneByteReadInputStream(inputStream)); 
संबंधित मुद्दे