Bir InputStream nasıl kopyalanır?


162

Bazı işlemler yapmak için bir yönteme geçmek bir InputStream var. Başka bir yöntemde aynı InputStream kullanacağım, ancak ilk işlemden sonra, InputStream yöntemin içinde kapalı görünüyor.

Onu kapatan yönteme göndermek için InputStream'i nasıl klonlayabilirim? Başka bir çözüm var mı?

EDIT: InputStream kapatır yöntemleri bir lib dış bir yöntemdir. Kapanma ya da kapama konusunda kontrolüm yok.

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
Yöntem döndükten sonra akışı "sıfırlamak" ister misiniz? Yani, akışı baştan mı okuyorsun?
aioobe

Evet, InputStream'i kapatan yöntemler kodlandığı karakter kümesini döndürür. İkinci yöntem, ilk yöntemde bulunan karakter kümesini kullanarak InputStream öğesini bir String'e dönüştürmektir.
Renato Dinhani

Bu durumda cevabımda anlattığım şeyi yapabilmelisin.
Kaj

Çözmenin en iyi yolunu bilmiyorum, ama sorunumu başka türlü çözüyorum. Jericho HTML Ayrıştırıcısının toString yöntemi, doğru biçimde biçimlendirilmiş String'i döndürür. Şu anda ihtiyacım olan her şey.
Renato Dinhani

Yanıtlar:


188

Tek yapmanız gereken aynı bilgileri bir kereden fazla okumaksa ve giriş verileri belleğe sığacak kadar küçükse, verileri InputStreambir ByteArrayOutputStream öğesine kopyalayabilirsiniz .

Sonra ilişkili bayt dizisini elde edebilir ve istediğiniz kadar "klonlanmış" ByteArrayInputStream s açabilirsiniz .

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()); 

Ancak, yeni verileri almak için orijinal akışı açık tutmanız gerekiyorsa, bu harici close()yöntemi izlemeniz ve bir şekilde çağrılmasını önlemeniz gerekir.

GÜNCELLEME (2019):

Java 9'dan bu yana, orta bitler aşağıdakilerle değiştirilebilir InputStream.transferTo:

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

Benim sorun başka bir çözüm thar InputStream kopyalama gerektirmez, ama ben InputStream kopyalamak gerekirse, bu en iyi çözüm olduğunu düşünüyorum.
Renato Dinhani

7
Bu yaklaşım, giriş akışının tam içeriğiyle orantılı belleği tüketir. BuradakiTeeInputStream cevapta açıklandığı gibi kullanmak daha iyidir .
aioobe

2
IOUtils (apache commons), arabellek kodunuzun ortasında okuma / yazma yapacak bir kopyalama yöntemine sahiptir.
14'te rethab

31

Apache'yi kullanmak istiyorsunuz CloseShieldInputStream:

Bu, akışın kapanmasını önleyecek bir sargıdır. Böyle bir şey yapardın.

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();

İyi görünüyor, ama burada çalışmıyor. Yazımı kodla düzenleyeceğim.
Renato Dinhani

CloseShieldorijinal HttpURLConnectiongiriş akışınız bir yerde kapalı olduğu için çalışmıyor . Metodunuz korumalı akışla IOUtils'i çağırmamalı IOUtils.toString(csContent,charset)mı?
Anthony Accioly

Belki de bu olabilir. HttpURLConnection kapatılmasını önleyebilir miyim?
Renato Dinhani

1
@Renato. Belki de sorun hiç close()çağrı değil, Akışın sonuna kadar okunmasıdır. Yana mark()ve reset()http bağlantıları için en iyi yöntemler olmayabilir, belki cevabım açıklanan bayt dizisi yaklaşımı bakmak gerekir.
Anthony Accioly

1
Bir şey daha, her zaman aynı URL'ye yeni bir bağlantı açabilirsiniz. Buraya bakın: stackoverflow.com/questions/5807340/…
Anthony Accioly

11

Klonlayamazsınız ve sorununuzu nasıl çözeceğiniz, verilerin kaynağının ne olduğuna bağlıdır.

Bir çözüm, InputStream öğesindeki tüm verileri bir bayt dizisine okumak ve daha sonra bu bayt dizisi etrafında bir ByteArrayInputStream oluşturmak ve bu giriş akışını yönteminize iletmektir.

Edit 1: Diğer metodun da aynı verileri okuması gerekiyorsa. Yani akışı "sıfırlamak" istiyorsunuz.


Hangi kısımda yardıma ihtiyacın olduğunu bilmiyorum. Sanırım bir dereden nasıl okumayı biliyorsun? InputStream'deki tüm verileri okuyun ve verileri ByteArrayOutputStream'e yazın. Tüm verileri okumayı tamamladıktan sonra ByteArrayOutputStream üzerindekiByteArray () öğesini çağırın. Sonra bu bayt dizisini ByteArrayInputStream öğesinin yapıcısına iletin.
Kaj

8

Akıştan okunan veriler büyükse, Apache Commons IO'dan bir TeeInputStream kullanmanızı öneririm. Bu şekilde, girdiyi çoğaltabilir ve klon olarak t'd bir boru geçirebilirsiniz.


5

Bu her durumda çalışmayabilir, ama işte ne yaptım: FilterInputStream sınıfını genişletti ve harici lib veri okurken bayt gerekli işleme yapmak.

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
    }

}

Ardından StreamBytesWithExtraProcessingInputStream, giriş akışında geçtiğiniz yerin bir örneğini iletmeniz yeterlidir . Yapıcı parametresi olarak orijinal giriş akışı ile.

Bunun bayt için bayt çalıştığına dikkat edilmelidir, bu nedenle yüksek performans bir gereklilikse bunu kullanmayın.


3

UPD. Daha önce yorumu kontrol edin. Tam olarak sorulan şey bu değil.

Eğer kullanıyorsanız apache.commonsakışları kopyalayabilirsiniz IOUtils.

Aşağıdaki kodu kullanabilirsiniz:

InputStream = IOUtils.toBufferedInputStream(toCopy);

Durumunuza uygun tam örnek:

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);
}

Bu kod bazı bağımlılıklar gerektirir:

UZMAN

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

GRADLE

'commons-io:commons-io:2.4'

İşte bu yöntem için DOC referansı:

Bir InputStream öğesinin tüm içeriğini getirir ve sonuç InputStream ile aynı verileri temsil eder. Bu yöntem,

Kaynak Giriş Akışı yavaş. İlişkili ağ kaynakları olduğundan uzun süre açık tutamayız. İlişkili ağ zaman aşımı var.

IOUtilsBurada daha fazla bilgi bulabilirsiniz: http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/IOUtils.html#toBufferedInputStream(java.io.InputStream)


7
Bu , giriş akışını klonlamaz , yalnızca ara belleğe alır. Bu aynı değil; OP aynı akışı tekrar okumak (kopyasını) istemektedir.
Raphael

1

Aşağıda Kotlin ile çözüm var.

InputStream'inizi ByteArray'e kopyalayabilirsiniz

val inputStream = ...

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

val byteInputStream = ByteArrayInputStream(byteOutputStream.toByteArray())

Birden byteInputStreamçok kez okumanız gerekirse , byteInputStream.reset()tekrar okumadan önce arayın .

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


0

Aşağıdaki sınıf hile yapmalıdır. Bir örnek oluşturun, "çarpma" yöntemini çağırın ve kaynak giriş akışını ve ihtiyacınız olan kopya miktarını sağlayın.

Önemli: Klonlanmış tüm akışları ayrı ayrı iş parçacıklarıyla aynı anda tüketmelisiniz.

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();
                }
            }
        }
    }
}

Soruya cevap vermiyor. Akışı karakter kümesini belirlemek için bir yöntemde kullanmak ve daha sonra ikinci yöntemde karakter kümesiyle birlikte yeniden okumak istiyor .
Lorne Marquis

0

Bir giriş akışının klonlanması iyi bir fikir olmayabilir, çünkü bu, klonlanan giriş akışının ayrıntıları hakkında derin bilgi gerektirir. Bunun bir çözümü, aynı kaynaktan tekrar okunan yeni bir giriş akışı oluşturmaktır.

Yani bazı Java 8 özelliklerini kullanarak bu şöyle görünecektir:

public class Foo {

    private Supplier<InputStream> inputStreamSupplier;

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

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

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

Bu yöntem, halihazırda mevcut olan kodu yeniden kullanacağı olumlu etkiye sahiptir - kapsüllenmiş giriş akışının oluşturulması inputStreamSupplier. Ve akışın klonlanması için ikinci bir kod yolunun korunmasına gerek yoktur.

Öte yandan, akıştan okumak pahalıysa (düşük bant genişliği bağlantısı üzerinden yapıldığından), bu yöntem maliyetleri iki katına çıkarır. Bu, önce akış içeriğini yerel olarak depolayacak ve InputStreamşu anda yerel bir kaynak sağlayacak belirli bir tedarikçi kullanılarak atlatılabilir .


Bu cevap benim için net değil. Tedarikçiyi mevcut bir şirketten nasıl başlatırsınız is?
user1156544

@ user1156544 Klon yazdığım gibi, bir giriş akışı iyi bir fikir olmayabilir, çünkü bu, klonlanan giriş akışının ayrıntıları hakkında derin bilgi gerektirir. mevcut bir giriş için bir giriş akışı oluşturmak üzere tedarikçiyi kullanamazsınız. Tedarikçi, her çağrıldığında yeni bir giriş akışı oluşturmak için a java.io.Fileveya java.net.URLörneğin kullanabilir.
SpaceTrucker

Şimdi görüyorum. Bu, OP'nin açıkça sorduğu gibi inputstream ile çalışmaz, ancak orijinal veri kaynağıysa File veya URL ile çalışır. Teşekkürler
user1156544
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.