geri

Sen OutputStream'e yaz, ben InputStream'den okuyayım

02/06/2012

Yakın zamanda yaşadığım görece karmaşık bir sorundan yola çıkarak bir Java OutputStream'in nasıl InputStream'e dönüştürülebileceğinden bahsetmek istiyorum. Bu gereksinim OutputStream'e veri yazan bir sınıfın oluşturduğu veriyi, InputStream'den veri okuyan bir sınıfa göndermek istediğimiz durumda ortaya çıkıyor. Stephen Ostermiller bu konuda çok güzel bir yazı yazmış ve soruna üç farklı çözüm yolu getirmiş.

1.yöntem: Byte Array kullanın

ByteArrayOutputStream out = new ByteArrayOutputStream();
class1.putDataOnOutputStream(out);
class2.processDataFromInputStream(
    new ByteArrayInputStream(out.toByteArray())
);

Bu yöntem oldukça kolay ancak tüm veriyi bellekteki bir byte dizisine aldığınızdan büyük veriler için Out Of Memory hatası ile karşılaşmanız olası.

2.yöntem: Pipe kullanın

Veriyi PipedOutputStream'e yazan ayrı bir thread oluşturun. Veri yazıldıkça asıl thread PipedInputSream'den veriyi okuyabilir.

PipedInputStream in = new PipedInputStream();
PipedOUtputStream out = new PipedOutputStream(in);
  new Thread(
    new Runnable(){
      public void run(){
        class1.putDataOnOutputStream(out);
      }
    }
).start();
class2.processDataFromInputStream(in);

Bu yöntem ek bir kütüphaneye/koda ihtiyaç duyulmadan gerçeklendiği için benim önerebileceğim en iyi yöntem. Fakat okuma ve yazma işlerinin ayrı threadler içinde yapıldığından emin olmalısınız. Aksi halde yüksek olasılıkla threadiniz bloklanacaktır.

3.yöntem: Circular Buffer kullanın

Bu yöntemde Ostermiller, kendi geliştirdiği CircularBuffer sınıflarının kullanımını öneriyor. Geliştirdiği kodlar GPLv2 lisanslı olduğundan açık kaynak projelerinizde gönül rahatlığıyla kullanabilirsiniz.

Multiple Thread Örneği
CircularByteBuffer cbb = new CircularByteBuffer();
new Thread(
  new Runnable(){
    public void run(){
      class1.putDataOnOutputStream(cbb.getOutputStream());
    }
}
).start();
class2.processDataFromInputStream(cbb.getInputStream());
Single Thread Örneği
// buffer all data in a circular buffer of infinite size

CircularByteBuffer cbb = new CircularByteBuffer(CircularByteBuffer.INFINITE_SIZE);
class1.putDataOnOutputStream(cbb.getOutputStream());
class2.processDataFromInputStream(cbb.getInputStream());

Yazımın başında bahsettiğim görece karmaşık soruna gelelim. Amacım bir RESTful API aracılığıyla çektiğim dosya verisini(genelde büyük boyutlu), bellekte başka bir yere kopyalamadan doğrudan istemciye(tarayıcıya) göndermek idi. Uygulamamı her zamanki gibi Play! Framework kullanarak geliştiriyordum. API çağrısı için Async Http Client kullanmaktaydım. Async Http Client veri geldikçe bir OutputStream'e yazabilmenize imkan sağlıyor.

OutputStream olarak Play!'in response.out üyesini kullanmak istediğimde OOM hatası alınca bu OutputStream'in bir ByteArrayOutputStream olduğunu farkettim. Bunun yerine response.direct üyesine bir InputStream vermek ve Content-Length başlığını setlemek gerekiyor. Content-Length setlemezseniz HTTP protokolü gereği response başlıklarının body'den önce gitmesi gerektiği için tüm stream'in yine belleğe alınıp boyutunun hesaplanması gerekiyor. Bu da OOM almanıza sebep oluyor. Çözüm yöntemini bulunca (response.direct üyesine bir InputStream vermek ve Content-Length başlığını setlemek) geriye tek bir sorun kaldı: Async Http Client'ın yazdığı OutputStream'i InputStream'e dönüştürerek response.direct'e atamak. Bunun için de yukarıda önerdiğim Pipe yöntemini kullandım.

Sorularınızı ya da önerilerinizi yorum olarak bekliyorum.

Follow me on Twitter