2012-11-09 17 views
5

Czy ktoś może mi powiedzieć, jak sklonować wejście, biorąc jak najmniej czasu na tworzenie? Muszę wielokrotnie klonować inputstream dla wielu metod przetwarzania IS. Próbowałem na trzy sposoby i rzeczy nie działają z tego czy innego powodu.Jak sklonować strumień wejściowy w języku Java w minimalnym czasie?

Metoda nr 1: Dzięki społeczności stackoverflow znalazłem następujący link, który był pomocny i włączyłem fragment kodu do mojego programu.

How to clone an InputStream?

Jednak użycie tego kodu może trwać do jednej minuty (dla pliku 10 MB), aby utworzyć sklonowane inputstreams i mój program musi być tak szybko, jak to możliwe.

int read = 0; 
    byte[] bytes = new byte[1024*1024*2]; 

    ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
    while ((read = is.read(bytes)) != -1) 
     bos.write(bytes,0,read); 
    byte[] ba = bos.toByteArray(); 

    InputStream is1 = new ByteArrayInputStream(ba); 
    InputStream is2 = new ByteArrayInputStream(ba); 
    InputStream is3 = new ByteArrayInputStream(ba); 

Sposób nr 2: Próbowałem też za pomocą BufferedInputStream sklonować IS. To był szybki (najwolniejszy czas tworzenia == 1 ms. Najszybszy == 0ms). Jednak po tym, jak wysłałem is1 do przetworzenia, przetwarzanie metod is2 i is3 spowodowało błąd mówiąc, że nie było nic do przetworzenia, prawie tak jak wszystkie 3 zmienne poniżej odnoszące się do tego samego IS.

is = getFileFromBucket(path,filename); 
    ... 
    ... 
    InputStream is1 = new BufferedInputStream(is); 
    InputStream is2 = new BufferedInputStream(is); 
    InputStream is3 = new BufferedInputStream(is); 

Sposób nr 3: myślę kompilator mnie okłamuje. Sprawdziłem markSupported() dla is1 dla dwóch powyższych przykładów. To prawda wrócił więc pomyślałem mogłem uruchomić

is1.mark() 
    is1.reset() 

lub po prostu

is1.reset(); 

przed przekazaniem IS do moich odpowiednich metod. W obu powyższych przykładach pojawia się komunikat o nieprawidłowym oznaczeniu.

Brakuje mi pomysłów, więc z góry dzięki za pomoc, jaką możesz mi dać.

P.S. Z komentarzy, które otrzymałem od ludzi, muszę wyjaśnić kilka rzeczy dotyczących mojej sytuacji: 1) Ten program działa na VM 2) Wejście jest przekazywane do mnie z innej metody. Nie jestem czytania z lokalnego pliku 3) Wielkość InputStream nie jest znany

+3

Uruchomienie kodu dla metody nr 1 zajmuje 18 ms (w przypadku pliku 10 MB) na moim komputerze. Czy coś jest nie tak z twoim sprzętem? –

+0

Dzięki za odpowiedź. Nie sądzę, żeby coś było nie tak z moim sprzętem. Po prostu uderzyło mnie to, że zapomniałem wspomnieć o 2 rzeczach: a) to jest na maszynie wirtualnej i b) wejście to plik jpg. Najszybszy jest czas 11 sekund, ale moje testy sprawdzają się, uśrednianie trwa około 30 sekund, najwolniej trwało około 1 minuty (dokładnie 53 sekundy). – Classified

+1

Możesz uzyskać nieznaczne zwiększenie, jeśli to zrobisz: bajt [] ba = new byte [is.available()]; // Działa, jeśli jest to FileInputStream new DataInputStream (is) .readFully (ba); –

Odpowiedz

3

Jednak użycie tego kodu może trwać do jednej minuty (dla pliku 10 MB), aby utworzyć sklonowane inputstreams i mój Program musi być tak szybki, jak to możliwe.

Dobrze kopiowanie strumienia zajmuje czas i (w zasadzie) jest to jedyny sposób na klonowanie strumienia. Dopóki nie zaostrzysz zakresu problemu, jest niewielka szansa, że ​​wydajność znacznie się poprawi.

Oto kilka okoliczności, w których możliwa jest poprawa:

  • Gdybyś wiedział wcześniej liczbę bajtów w strumieniu następnie można odczytać bezpośrednio do końcowego tablicy bajtów.

  • Jeśli wiesz, że dane pochodzą z pliku, możesz utworzyć bufor odwzorowany w pamięci dla pliku.

Ale podstawowym problemem jest to, że przenoszenie dużej ilości bajtów zajmuje dużo czasu. A fakt, że zajmuje 1 minutę dla pliku 10 MB (przy użyciu kodu w pytaniu) sugeruje, że prawdziwe wąskie gardło nie występuje wcale w Javie.

1

Czy zamierzasz używać oddzielnych metod do uruchamiania równolegle lub sekwencyjnie? Jeśli sekwencyjnie, nie widzę powodu do klonowania strumienia wejściowego, więc muszę założyć, że planujesz wydzielić wątki, aby zarządzać każdym strumieniem.

Nie jestem teraz w pobliżu komputera, aby to przetestować, ale myślę, że lepiej byłoby przeczytać dane wejściowe w kawałkach, powiedzmy 1024 bajty, a następnie przesłać te fragmenty (lub kopie sz fragmenty) na strumienie wyjściowe ze strumieniami wejściowymi dołączonymi do ich końcówek. Mają czytelnicy blokować jeżeli nie są dostępne żadne dane, itp

+0

Dziękujemy za odpowiedź i sugestię. Tak, zamierzam użyć wątków ... gdy tylko wymyślę, jak naprawić tę szyjkę butelki. Spróbuję to zrobić, odczytać fragmenty, ale otrzymuję strumień wejściowy przekazywany z innej metody, więc muszę sprawdzić, czy jest to wykonalne w moim przypadku. – Classified

2

Odnośnie pierwszego podejścia, jeden polegający na umieszczenie wszystkich bajtów w ByteArrayOutputStream:

  • Po pierwsze, podejście to zużywa dużo pamięci. Jeśli nie upewnisz się, że twoja maszyna JVM zaczyna się z przydzieloną wystarczającą ilością pamięci, będzie musiała dynamicznie żądać pamięci podczas przetwarzania twojego strumienia i jest to czasochłonne.
  • Twój ByteArrayOutputStream jest początkowo tworzony z buforem 32 bajtów. Za każdym razem, gdy próbujesz coś w nim umieścić, jeśli nie pasuje do istniejącej tablicy bajtów, tworzona jest nowa większa tablica, a stare bajty są kopiowane do nowej. Ponieważ za każdym razem używasz 2 MB danych, zmuszasz ByteArrayOutputStream do wielokrotnego kopiowania danych do większych tablic, zwiększając rozmiar swojej tablicy o 2 MB za każdym razem.
  • Ponieważ stare tablice są odpadami, prawdopodobne jest, że ich pamięć jest odzyskiwana przez odśmiecnik, co powoduje, że proces kopiowania jest jeszcze wolniejszy.
  • Być może powinieneś zdefiniować ByArrayOutputStream używając konstruktora, który określa początkowy rozmiar bufora. Im dokładniej ustawisz rozmiar, tym szybszy powinien być proces, ponieważ potrzebne będą mniej pośrednie kopie.

Drugie podejście jest fałszywe, nie można udekorować tego samego strumienia wejściowego w ramach innych strumieni i oczekiwać, że wszystko zadziała. Ponieważ bajty są zużywane przez jeden strumień, wewnętrzny strumień jest również wyczerpany i nie może zapewnić innym strumieniom dokładnych danych.

Zanim przedłużam swoją odpowiedź, proszę zapytać, czy inne metody oczekują na otrzymanie kopii strumienia wejściowego działającego w osobnym wątku? Bo jeśli tak, to brzmi jak praca dla PipedOutputStream i PipedInputStream?

+0

Dzięki za odpowiedź. Ponieważ inna metoda przekazuje mi strumień wejściowy, nie wiem, w jakim rozmiarze wchodzi IS. Grałem z tym, że tablica bajtów ma być 8MB, ale to trwało długo. Ktoś zasugerował, że używam BufferedInputStream i domyślam się, że nie używałem tego poprawnie, więc mój zły dla fałszywego użycia =) Mam zamiar używać wątków dla moich innych metod, więc zajrzę do twojej sugestii PipedIS i PipedOS, aby zobaczyć jeśli to pomaga. W tej chwili staram się, aby wszystko działało seryjnie, zanim zacznę grać z wątkami. – Classified

6

jak sklonować wejście, biorąc jak najmniej czasu na tworzenie? Muszę sklonować InputStream wielokrotnie dla wielu metod, aby przetworzyć IS

Można po prostu stworzyć jakąś klasę zwyczaj ReusableInputStream gdzie ty natychmiast również napisać do wewnętrznej ByteArrayOutputStream na 1. pełnego odczytu, następnie zapakuj go w ByteBuffer po odczytaniu ostatniego bajtu i ostatecznie ponownie używaj tego samego ByteBuffer na kolejnych pełnych odczytach, które są automatycznie odwracane po osiągnięciu limitu. To pozwala zaoszczędzić od jednego pełnego czytania, tak jak w pierwszej próbie.

Oto prosty przykład kickoff:

public class ReusableInputStream extends InputStream { 

    private InputStream input; 
    private ByteArrayOutputStream output; 
    private ByteBuffer buffer; 

    public ReusableInputStream(InputStream input) throws IOException { 
     this.input = input; 
     this.output = new ByteArrayOutputStream(input.available()); // Note: it's resizable anyway. 
    } 

    @Override 
    public int read() throws IOException { 
     byte[] b = new byte[1]; 
     read(b, 0, 1); 
     return b[0]; 
    } 

    @Override 
    public int read(byte[] bytes) throws IOException { 
     return read(bytes, 0, bytes.length); 
    } 

    @Override 
    public int read(byte[] bytes, int offset, int length) throws IOException { 
     if (buffer == null) { 
      int read = input.read(bytes, offset, length); 

      if (read <= 0) { 
       input.close(); 
       input = null; 
       buffer = ByteBuffer.wrap(output.toByteArray()); 
       output = null; 
       return -1; 
      } else { 
       output.write(bytes, offset, read); 
       return read; 
      } 
     } else { 
      int read = Math.min(length, buffer.remaining()); 

      if (read <= 0) { 
       buffer.flip(); 
       return -1; 
      } else { 
       buffer.get(bytes, offset, read); 
       return read; 
      } 
     } 

    } 

    // You might want to @Override flush(), close(), etc to delegate to input. 
} 

(zauważ, że rzeczywista praca jest wykonywana w int read(byte[], int, int) zamiast w int read() a więc to oczekuje się, że będzie szybciej, gdy sam rozmówca jest także strumieniowe stosując bufor byte[])

można użyć go w następujący sposób:

InputStream input = new ReusableInputStream(getFileFromBucket(path,filename)); 
IOUtils.copy(input, new FileOutputStream("/copy1.ext")); 
IOUtils.copy(input, new FileOutputStream("/copy2.ext")); 
IOUtils.copy(input, new FileOutputStream("/copy3.ext")); 

Co do wydajności, 1 minuta na 10 MB jest bardziej prawdopodobnym problemem sprzętowym, a nie problemem z oprogramowaniem. Mój twardy dysk do laptopa 7200 obr./min robi to w czasie krótszym niż 1 sekunda.

+0

Dzięki za fragment kodu. Wypróbuję to razem z innymi sugestiami! – Classified