कैसे एक InputStream क्लोन करने के लिए?


162

मेरे पास एक इनपुटस्ट्रीम है जिसे मैं कुछ प्रोसेसिंग करने के लिए एक विधि के पास देता हूं। मैं अन्य विधि में उसी InputStream का उपयोग करूंगा, लेकिन पहली प्रसंस्करण के बाद, InputStream विधि के अंदर बंद हो जाता है।

मैं उसे कैसे बंद करने वाली विधि को भेजने के लिए InputStream को क्लोन कर सकता हूं? एक और उपाय है?

EDIT: InputStream को बंद करने वाली विधियाँ एक lib से एक बाहरी विधि है। मुझे बंद करने या नहीं करने के बारे में नियंत्रण नहीं है।

private String getContent(HttpURLConnection con) {
    InputStream content = null;
    String charset = "";
    try {
        content = con.getInputStream();
        CloseShieldInputStream csContent = new CloseShieldInputStream(content);
        charset = getCharset(csContent);            
        return  IOUtils.toString(content,charset);
    } catch (Exception e) {
        System.out.println("Error downloading page: " + e);
        return null;
    }
}

private String getCharset(InputStream content) {
    try {
        Source parser = new Source(content);
        return parser.getEncoding();
    } catch (Exception e) {
        System.out.println("Error determining charset: " + e);
        return "UTF-8";
    }
}

2
क्या आप विधि वापस आने के बाद स्ट्रीम को "रीसेट" करना चाहते हैं? यानी, शुरू से स्ट्रीम पढ़ी?
ऐयोबेब

हां, जो तरीके इनपुटस्ट्रीम को बंद करते हैं, वह चारसेट लौटाता है जो इसे एन्कोड किया गया था। पहली विधि में पाए गए चारसेट का उपयोग करके इनपुटस्ट्रीम को एक स्ट्रिंग में परिवर्तित करने के लिए दूसरी विधि है।
रेनाटो दिनानी

आपको उस स्थिति में होना चाहिए जो मैं अपने उत्तर में बता रहा हूं।
काज

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

जवाबों:


188

यदि आप केवल इतना ही जानकारी एक बार से अधिक पढ़ने के लिए है क्या करना चाहते हैं, और इनपुट डेटा स्मृति में फिट करने के लिए छोटे पर्याप्त है, तो आप अपने से डेटा कॉपी कर सकते हैं InputStreamएक करने के लिए ByteArrayOutputStream

तब आप बाइट्स के संबद्ध सरणी को प्राप्त कर सकते हैं और आप की तरह कई "क्लोन" ByteArrayInputStream एस खोल सकते हैं।

ByteArrayOutputStream baos = new ByteArrayOutputStream();

// Fake code simulating the copy
// You can generally do better with nio if you need...
// And please, unlike me, do something about the Exceptions :D
byte[] buffer = new byte[1024];
int len;
while ((len = input.read(buffer)) > -1 ) {
    baos.write(buffer, 0, len);
}
baos.flush();

// Open new InputStreams using the recorded bytes
// Can be repeated as many times as you wish
InputStream is1 = new ByteArrayInputStream(baos.toByteArray()); 
InputStream is2 = new ByteArrayInputStream(baos.toByteArray()); 

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

अद्यतन (2019):

जावा 9 के बाद से मध्यम बिट्स को इसके साथ बदला जा सकता है InputStream.transferTo:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
input.transferTo(baos);
InputStream firstClone = new ByteArrayInputStream(baos.toByteArray()); 
InputStream secondClone = new ByteArrayInputStream(baos.toByteArray()); 

मैं अपनी समस्या के लिए एक और समाधान देता हूं थार में इनपुटस्ट्रीम को कॉपी करना शामिल नहीं है, लेकिन मुझे लगता है कि अगर मुझे इनपुटस्ट्रीम की प्रतिलिपि बनाने की आवश्यकता है, तो यह सबसे अच्छा समाधान है।
रेनाटो दिनानी

7
यह दृष्टिकोण इनपुट स्ट्रीम की पूर्ण सामग्री के लिए आनुपातिक खपत करता है। यहाँTeeInputStream पर उत्तर में वर्णित के रूप में उपयोग करने के लिए बेहतर है
aioobe

2
IOUtils (अपाचे कॉमन्स से) में एक कॉपी विधि है जो आपके कोड के बीच में बफर को रीड / राइट करेगा।
रीब

31

आप अपाचे का उपयोग करना चाहते हैं CloseShieldInputStream:

यह एक आवरण है जो धारा को बंद होने से रोकेगा। आप ऐसा कुछ करेंगे।

InputStream is = null;

is = getStream(); //obtain the stream 
CloseShieldInputStream csis = new CloseShieldInputStream(is);

// call the bad function that does things it shouldn't
badFunction(csis);

// happiness follows: do something with the original input stream
is.read();

अच्छा लग रहा है, लेकिन यहाँ काम नहीं करता है। मैं अपनी पोस्ट को कोड के साथ संपादित करूंगा।
रेनाटो दिनानी

CloseShieldकाम नहीं कर रहा है क्योंकि आपकी मूल HttpURLConnectionइनपुट स्ट्रीम कहीं बंद हो रही है। क्या आपकी विधि को सुरक्षित स्ट्रीम के साथ IOUtils को कॉल नहीं करना चाहिए IOUtils.toString(csContent,charset)?
एंथोनी एक्यूली

शायद यह हो सकता है। मैं HttpURLConnection बंद होने से रोक सकता हूं?
रेनाटो दिनानी

1
@Renato। शायद समस्या close()कॉल नहीं है , लेकिन वास्तव में स्ट्रीम को अंत तक पढ़ा जा रहा है। चूंकि mark()और reset()http कनेक्शन के लिए सबसे अच्छा तरीका नहीं हो सकता है, हो सकता है कि आप मेरे जवाब में वर्णित बाइट सरणी दृष्टिकोण पर एक नज़र डालें।
एंथोनी एक्यूप्ली

1
एक और बात, आप हमेशा एक ही URL के लिए एक नया कनेक्शन खोल सकते हैं। यहां देखें: stackoverflow.com/questions/5807340/…
एंथनी एकॉली

11

आप इसे क्लोन नहीं कर सकते हैं, और आप अपनी समस्या को कैसे हल करने जा रहे हैं, यह इस बात पर निर्भर करता है कि डेटा का स्रोत क्या है।

एक समाधान यह है कि InputStream के सभी डेटा को बाइट सरणी में पढ़ा जाए, और फिर उस बाइट सरणी के चारों ओर एक ByteArrayInputStream बनाएं, और उस इनपुट स्ट्रीम को अपनी विधि में पास करें।

संपादित करें 1: अर्थात्, यदि अन्य विधि को भी उसी डेटा को पढ़ने की आवश्यकता है। यानी आप स्ट्रीम को "रीसेट" करना चाहते हैं।


मुझे नहीं पता कि आपको किस हिस्से की मदद चाहिए। मुझे लगता है कि आप एक धारा से पढ़ना जानते हैं? InputStream के सभी डेटा पढ़ें, और डेटा को ByteArrayOutputStream लिखें। आप सभी डेटा को पढ़ने के पूरा होने के बाद ByteArrayOutputStream पर toByteArray () कॉल करें। फिर उस बाइट ऐरे को एक बाइटएरेइंटरप्यूटस्ट्रीम के कंस्ट्रक्टर में पास करें।
काज

8

यदि स्ट्रीम से पढ़ा गया डेटा बड़ा है, तो मैं Apache Commons IO से TeeInputStream का उपयोग करने की सलाह दूंगा। इस तरह आप अनिवार्य रूप से इनपुट को दोहरा सकते हैं और अपने क्लोन के रूप में एक t'd पाइप पास कर सकते हैं।


5

यह सभी स्थितियों में काम नहीं कर सकता है, लेकिन यहाँ मैंने क्या किया है: मैंने FilterInputStream क्लास को बढ़ाया और बाइट्स की आवश्यक प्रोसेसिंग की, क्योंकि एक्सटर्नल लीब डेटा को पढ़ता है।

public class StreamBytesWithExtraProcessingInputStream extends FilterInputStream {

    protected StreamBytesWithExtraProcessingInputStream(InputStream in) {
        super(in);
    }

    @Override
    public int read() throws IOException {
        int readByte = super.read();
        processByte(readByte);
        return readByte;
    }

    @Override
    public int read(byte[] buffer, int offset, int count) throws IOException {
        int readBytes = super.read(buffer, offset, count);
        processBytes(buffer, offset, readBytes);
        return readBytes;
    }

    private void processBytes(byte[] buffer, int offset, int readBytes) {
       for (int i = 0; i < readBytes; i++) {
           processByte(buffer[i + offset]);
       }
    }

    private void processByte(int readByte) {
       // TODO do processing here
    }

}

तब आप बस एक उदाहरण से गुजरते हैं StreamBytesWithExtraProcessingInputStreamकि आप इनपुट स्ट्रीम में कहाँ से गुज़रे हैं। कंस्ट्रक्टर पैरामीटर के रूप में मूल इनपुट स्ट्रीम के साथ।

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


3

युपीडी। पहले टिप्पणी की जाँच करें। यह वही नहीं है जो पूछा गया था।

यदि आप उपयोग कर रहे हैं तो आप apache.commonsधाराओं का उपयोग करके कॉपी कर सकते हैं IOUtils

आप निम्नलिखित कोड का उपयोग कर सकते हैं:

InputStream = IOUtils.toBufferedInputStream(toCopy);

आपकी स्थिति के लिए उपयुक्त पूर्ण उदाहरण यहां दिया गया है:

public void cloneStream() throws IOException{
    InputStream toCopy=IOUtils.toInputStream("aaa");
    InputStream dest= null;
    dest=IOUtils.toBufferedInputStream(toCopy);
    toCopy.close();
    String result = new String(IOUtils.toByteArray(dest));
    System.out.println(result);
}

इस कोड को कुछ निर्भरताओं की आवश्यकता है:

MAVEN

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

Gradle

'commons-io:commons-io:2.4'

इस विधि के लिए यहाँ DOC संदर्भ दिया गया है:

एक InputStream की संपूर्ण सामग्री प्राप्त करता है और उसी डेटा को InputStream के रूप में प्रस्तुत करता है। यह विधि उपयोगी है जहाँ,

Source InputStream धीमा है। इसमें नेटवर्क संसाधन जुड़े हुए हैं, इसलिए हम इसे लंबे समय तक खुला नहीं रख सकते हैं। इसमें नेटवर्क टाइमआउट जुड़ा हुआ है।

आप IOUtilsयहां और अधिक जानकारी प्राप्त कर सकते हैं : http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/IOUtils.html#toBufferedInputStory(-ava.io.InputStream)


7
यह इनपुट स्ट्रीम को क्लोन नहीं करता है बल्कि केवल इसे बफ़र करता है। वही नहीं है; ओपी उसी धारा को फिर से (एक प्रति) पढ़ना चाहता है।
राफेल

1

नीचे कोटलिन के साथ समाधान है।

आप अपने InputStream को ByteArray में कॉपी कर सकते हैं

val inputStream = ...

val byteOutputStream = ByteArrayOutputStream()
inputStream.use { input ->
    byteOutputStream.use { output ->
        input.copyTo(output)
    }
}

val byteInputStream = ByteArrayInputStream(byteOutputStream.toByteArray())

यदि आपको byteInputStreamकई बार पढ़ने की आवश्यकता है , तो byteInputStream.reset()दोबारा पढ़ने से पहले कॉल करें।

https://code.luasoftware.com/tutorials/kotlin/how-to-clone-inputstream/


0

नीचे के वर्ग को चाल चलनी चाहिए। बस एक उदाहरण बनाएं, "मल्टीप्ल" विधि को कॉल करें, और स्रोत इनपुट स्ट्रीम और आपके द्वारा आवश्यक डुप्लिकेट की मात्रा प्रदान करें।

महत्वपूर्ण: आपको अलग-अलग धागों में एक साथ सभी क्लोन धाराओं का उपभोग करना चाहिए।

package foo.bar;

import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class InputStreamMultiplier {
    protected static final int BUFFER_SIZE = 1024;
    private ExecutorService executorService = Executors.newCachedThreadPool();

    public InputStream[] multiply(final InputStream source, int count) throws IOException {
        PipedInputStream[] ins = new PipedInputStream[count];
        final PipedOutputStream[] outs = new PipedOutputStream[count];

        for (int i = 0; i < count; i++)
        {
            ins[i] = new PipedInputStream();
            outs[i] = new PipedOutputStream(ins[i]);
        }

        executorService.execute(new Runnable() {
            public void run() {
                try {
                    copy(source, outs);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

        return ins;
    }

    protected void copy(final InputStream source, final PipedOutputStream[] outs) throws IOException {
        byte[] buffer = new byte[BUFFER_SIZE];
        int n = 0;
        try {
            while (-1 != (n = source.read(buffer))) {
                //write each chunk to all output streams
                for (PipedOutputStream out : outs) {
                    out.write(buffer, 0, n);
                }
            }
        } finally {
            //close all output streams
            for (PipedOutputStream out : outs) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

सवाल का जवाब नहीं देता। वह चारसेट का निर्धारण करने के लिए एक विधि में धारा का उपयोग करना चाहता है और फिर दूसरी विधि में अपने चारसेट के साथ इसे फिर से पढ़ता है।
लोर्ने

0

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

तो कुछ जावा 8 सुविधाओं का उपयोग करना इस तरह दिखेगा:

public class Foo {

    private Supplier<InputStream> inputStreamSupplier;

    public void bar() {
        procesDataThisWay(inputStreamSupplier.get());
        procesDataTheOtherWay(inputStreamSupplier.get());
    }

    private void procesDataThisWay(InputStream) {
        // ...
    }

    private void procesDataTheOtherWay(InputStream) {
        // ...
    }
}

इस पद्धति का सकारात्मक प्रभाव है कि यह उस कोड का पुन: उपयोग करेगा जो पहले से ही है - इनपुट स्ट्रीम का निर्माण इनकैप्सुलेटेड इन inputStreamSupplier । और धारा के क्लोनिंग के लिए दूसरा कोड पथ बनाए रखने की आवश्यकता नहीं है।

दूसरी ओर, यदि स्ट्रीम से पढ़ना महंगा है (क्योंकि यह एक कम बैंडविथ कनेक्शन पर किया गया है), तो यह विधि लागतों को दोगुना कर देगी। एक विशिष्ट आपूर्तिकर्ता का उपयोग करके इसे दरकिनार किया जा सकता है जो पहले स्थानीय स्तर पर स्ट्रीम सामग्री को संग्रहीत करेगा और उसके लिए InputStreamअब स्थानीय संसाधन प्रदान करेगा ।


यह उत्तर मेरे लिए स्पष्ट नहीं है। आप मौजूदा से आपूर्तिकर्ता को कैसे आरंभ करते हैं is?
user1156544

@ user1156544 जैसा कि मैंने लिखा है कि इनपुट स्ट्रीम का क्लोनिंग एक अच्छा विचार नहीं हो सकता है, क्योंकि इसके लिए इनपुट स्ट्रीम के विवरण के बारे में गहन जानकारी की आवश्यकता होती है। आप एक मौजूदा एक इनपुट स्ट्रीम फ्रॉन बनाने के लिए आपूर्तिकर्ता का उपयोग नहीं कर सकते। हर बार आह्वान किए जाने पर आपूर्तिकर्ता नई इनपुट स्ट्रीम बनाने के लिए java.io.Fileया java.net.URLउदाहरण के लिए उपयोग कर सकता है।
स्पेसट्रूकर

मुझे अब दिख गया। यह इनपुटस्ट्रीम के साथ काम नहीं करेगा क्योंकि ओपी स्पष्ट रूप से पूछता है, लेकिन फ़ाइल या URL के साथ यदि वे डेटा के मूल स्रोत हैं। धन्यवाद
user1156544
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.