Mam InputStream, który przekazuję do metody, aby wykonać pewne przetwarzanie. Użyję tego samego InputStream w innej metodzie, ale po pierwszym przetworzeniu InputStream wydaje się być zamknięty wewnątrz metody.
Jak mogę sklonować InputStream, aby wysłać go do metody, która go zamyka? Czy jest inne rozwiązanie?
EDYCJA: metody, które zamykają InputStream to metoda zewnętrzna z biblioteki. Nie mam kontroli nad zamykaniem, czy nie.
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";
}
}
java
clone
inputstream
Renato Dinhani
źródło
źródło
Odpowiedzi:
Jeśli wszystko, co chcesz zrobić, to przeczytać te same informacje więcej niż raz, a dane wejściowe są wystarczająco małe, aby zmieścić się w pamięci, możesz skopiować dane ze swojego
InputStream
do ByteArrayOutputStream .Następnie możesz uzyskać powiązaną tablicę bajtów i otworzyć tyle „sklonowanych” ByteArrayInputStream, ile chcesz.
Ale jeśli naprawdę chcesz pozostawić oryginalny strumień otwarty, aby otrzymywać nowe dane, będziesz musiał śledzić ten zewnętrzny
close()
metodę i w jakiś sposób zapobiec jej wywołaniu.AKTUALIZACJA (2019):
Od wersji Java 9 środkowe bity można zastąpić
InputStream.transferTo
:źródło
TeeInputStream
jak opisano w odpowiedzi tutaj .Chcesz użyć Apache
CloseShieldInputStream
:To jest opakowanie, które zapobiegnie zamknięciu strumienia. Zrobiłbyś coś takiego.
źródło
CloseShield
nie działa, ponieważ pierwotnyHttpURLConnection
strumień wejściowy jest gdzieś zamknięty. Czy twoja metoda nie powinna wywoływać IOUtils z chronionym strumieniemIOUtils.toString(csContent,charset)
?close()
wezwanie, ale fakt, że Stream jest odczytywany do końca. Ponieważmark()
ireset()
może nie być najlepszą metodą połączeń http, może powinieneś przyjrzeć się podejściu do tablicy bajtów opisanej w mojej odpowiedzi.Nie możesz go sklonować, a sposób rozwiązania problemu zależy od źródła danych.
Jednym z rozwiązań jest odczytanie wszystkich danych z InputStream do tablicy bajtów, a następnie utworzenie ByteArrayInputStream wokół tej tablicy bajtów i przekazanie tego strumienia wejściowego do metody.
Edycja 1: to znaczy, jeśli druga metoda również musi odczytać te same dane. To znaczy chcesz „zresetować” strumień.
źródło
Jeśli dane odczytane ze strumienia są duże, polecam użycie TeeInputStream z Apache Commons IO. W ten sposób możesz zasadniczo powielić dane wejściowe i przekazać potok t'd jako klon.
źródło
To może nie działać we wszystkich sytuacjach, ale oto, co zrobiłem: rozszerzyłem klasę FilterInputStream i wykonałem wymagane przetwarzanie bajtów, gdy zewnętrzna biblioteka odczytuje dane.
Następnie po prostu przekazujesz wystąpienie miejsca, w
StreamBytesWithExtraProcessingInputStream
którym przeszedłbyś w strumieniu wejściowym. Z oryginalnym strumieniem wejściowym jako parametrem konstruktora.Należy zauważyć, że działa to bajt po bajcie, więc nie używaj tego, jeśli wymagana jest wysoka wydajność.
źródło
UPD. Sprawdź komentarz wcześniej. To nie jest dokładnie to, o co pytano.
Jeśli używasz
apache.commons
, możesz kopiować strumienie za pomocąIOUtils
.Możesz użyć następującego kodu:
Oto pełny przykład odpowiedni dla Twojej sytuacji:
Ten kod wymaga pewnych zależności:
MAVEN
GRADLE
Oto odniesienie DOC dla tej metody:
Więcej na ten temat można znaleźć
IOUtils
tutaj: http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/IOUtils.html#toBufferedInputStream(java.io.InputStream)źródło
Poniżej znajduje się rozwiązanie z Kotlinem.
Możesz skopiować swój InputStream do ByteArray
Jeśli chcesz przeczytać
byteInputStream
kilka razy, zadzwońbyteInputStream.reset()
przed ponownym przeczytaniem.https://code.luasoftware.com/tutorials/kotlin/how-to-clone-inputstream/
źródło
Poniższa klasa powinna załatwić sprawę. Po prostu utwórz instancję, wywołaj metodę „multiply” i podaj źródłowy strumień wejściowy oraz wymaganą liczbę duplikatów.
Ważne: musisz używać wszystkich sklonowanych strumieni jednocześnie w oddzielnych wątkach.
źródło
Klonowanie strumienia wejściowego może nie być dobrym pomysłem, ponieważ wymaga to dogłębnej wiedzy na temat szczegółów klonowanego strumienia wejściowego. Aby obejść ten problem, należy utworzyć nowy strumień wejściowy, który ponownie odczytuje dane z tego samego źródła.
Tak więc przy użyciu niektórych funkcji Java 8 wyglądałoby to tak:
Ta metoda ma pozytywny wpływ na to, że ponownie użyje kodu, który jest już na miejscu - utworzenie strumienia wejściowego hermetyzowanego w
inputStreamSupplier
. Nie ma też potrzeby utrzymywania drugiej ścieżki kodu do klonowania strumienia.Z drugiej strony, jeśli odczytywanie ze strumienia jest drogie (ponieważ odbywa się to przez połączenie o niskiej przepustowości), ta metoda podwoi koszty. Można to obejść, używając określonego dostawcy, który najpierw zapisze zawartość strumienia lokalnie i zapewni
InputStream
dla tego teraz lokalny zasób.źródło
is
?java.io.File
lub,java.net.URL
na przykład, do utworzenia nowego strumienia wejściowego za każdym razem, gdy jest on wywoływany.