คุณอ่านอินพุตสตรีมเดียวกันสองครั้งได้อย่างไร สามารถคัดลอกได้หรือไม่?
ฉันต้องการรับภาพจากเว็บบันทึกในเครื่องแล้วส่งคืนรูปภาพที่บันทึกไว้ ฉันคิดว่ามันจะเร็วกว่าถ้าใช้สตรีมเดียวกันแทนที่จะเริ่มสตรีมใหม่ไปยังเนื้อหาที่ดาวน์โหลดแล้วอ่านอีกครั้ง
คุณอ่านอินพุตสตรีมเดียวกันสองครั้งได้อย่างไร สามารถคัดลอกได้หรือไม่?
ฉันต้องการรับภาพจากเว็บบันทึกในเครื่องแล้วส่งคืนรูปภาพที่บันทึกไว้ ฉันคิดว่ามันจะเร็วกว่าถ้าใช้สตรีมเดียวกันแทนที่จะเริ่มสตรีมใหม่ไปยังเนื้อหาที่ดาวน์โหลดแล้วอ่านอีกครั้ง
คำตอบ:
คุณสามารถใช้org.apache.commons.io.IOUtils.copy
เพื่อคัดลอกเนื้อหาของ InputStream ไปยังอาร์เรย์ไบต์จากนั้นอ่านซ้ำ ๆ จากอาร์เรย์ไบต์โดยใช้ ByteArrayInputStream เช่น:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
org.apache.commons.io.IOUtils.copy(in, baos);
byte[] bytes = baos.toByteArray();
// either
while (needToReadAgain) {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
yourReadMethodHere(bais);
}
// or
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
while (needToReadAgain) {
bais.reset();
yourReadMethodHere(bais);
}
ขึ้นอยู่กับว่า InputStream มาจากที่ใดคุณอาจไม่สามารถรีเซ็ตได้ คุณสามารถตรวจสอบmark()
และreset()
ได้รับการสนับสนุนการใช้markSupported()
ได้รับการสนับสนุนการใช้
หากเป็นเช่นนั้นคุณสามารถเรียกreset()
InputStream เพื่อกลับไปที่จุดเริ่มต้นได้ ถ้าไม่คุณต้องอ่าน InputStream จากแหล่งที่มาอีกครั้ง
InputStream
@ayahuasca เช่นBufferedInputStream
รองรับ 'mark'
ถ้าคุณInputStream
สนับสนุนการใช้เครื่องหมายแล้วคุณสามารถmark()
inputStream แล้วของคุณreset()
มัน หากคุณInputStrem
ไม่สนับสนุนเครื่องหมายคุณสามารถใช้ชั้นเรียนjava.io.BufferedInputStream
ได้ดังนั้นคุณสามารถฝังสตรีมของคุณไว้ในBufferedInputStream
ลักษณะนี้ได้
InputStream bufferdInputStream = new BufferedInputStream(yourInputStream);
bufferdInputStream.mark(some_value);
//read your bufferdInputStream
bufferdInputStream.reset();
//read it again
BufferedInputStream.fill()
มีส่วน "เติบโต buffer" ที่ขนาดของบัฟเฟอร์ใหม่จะเทียบเฉพาะกับและmarklimit
MAX_BUFFER_SIZE
คุณสามารถตัดกระแสข้อมูลเข้าด้วย PushbackInputStream PushbackInputStream อนุญาตให้ยังไม่อ่าน (" เขียนกลับ ") ไบต์ที่อ่านไปแล้วดังนั้นคุณสามารถทำได้ดังนี้:
public class StreamTest {
public static void main(String[] args) throws IOException {
byte[] bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
InputStream originalStream = new ByteArrayInputStream(bytes);
byte[] readBytes = getBytes(originalStream, 3);
printBytes(readBytes); // prints: 1 2 3
readBytes = getBytes(originalStream, 3);
printBytes(readBytes); // prints: 4 5 6
// now let's wrap it with PushBackInputStream
originalStream = new ByteArrayInputStream(bytes);
InputStream wrappedStream = new PushbackInputStream(originalStream, 10); // 10 means that maximnum 10 characters can be "written back" to the stream
readBytes = getBytes(wrappedStream, 3);
printBytes(readBytes); // prints 1 2 3
((PushbackInputStream) wrappedStream).unread(readBytes, 0, readBytes.length);
readBytes = getBytes(wrappedStream, 3);
printBytes(readBytes); // prints 1 2 3
}
private static byte[] getBytes(InputStream is, int howManyBytes) throws IOException {
System.out.print("Reading stream: ");
byte[] buf = new byte[howManyBytes];
int next = 0;
for (int i = 0; i < howManyBytes; i++) {
next = is.read();
if (next > 0) {
buf[i] = (byte) next;
}
}
return buf;
}
private static void printBytes(byte[] buffer) throws IOException {
System.out.print("Reading stream: ");
for (int i = 0; i < buffer.length; i++) {
System.out.print(buffer[i] + " ");
}
System.out.println();
}
}
โปรดทราบว่า PushbackInputStream เก็บบัฟเฟอร์ภายในของไบต์ดังนั้นจึงสร้างบัฟเฟอร์ในหน่วยความจำซึ่งเก็บไบต์ไว้ "เขียนกลับ"
เมื่อรู้แนวทางนี้แล้วเราสามารถไปต่อและรวมเข้ากับ FilterInputStream FilterInputStream เก็บสตรีมอินพุตดั้งเดิมเป็นผู้รับมอบสิทธิ์ สิ่งนี้ช่วยให้สามารถสร้างนิยามคลาสใหม่ซึ่งอนุญาตให้ " ยังไม่ได้อ่าน " ข้อมูลเดิมโดยอัตโนมัติ คำจำกัดความของคลาสนี้มีดังต่อไปนี้:
public class TryReadInputStream extends FilterInputStream {
private final int maxPushbackBufferSize;
/**
* Creates a <code>FilterInputStream</code>
* by assigning the argument <code>in</code>
* to the field <code>this.in</code> so as
* to remember it for later use.
*
* @param in the underlying input stream, or <code>null</code> if
* this instance is to be created without an underlying stream.
*/
public TryReadInputStream(InputStream in, int maxPushbackBufferSize) {
super(new PushbackInputStream(in, maxPushbackBufferSize));
this.maxPushbackBufferSize = maxPushbackBufferSize;
}
/**
* Reads from input stream the <code>length</code> of bytes to given buffer. The read bytes are still avilable
* in the stream
*
* @param buffer the destination buffer to which read the data
* @param offset the start offset in the destination <code>buffer</code>
* @aram length how many bytes to read from the stream to buff. Length needs to be less than
* <code>maxPushbackBufferSize</code> or IOException will be thrown
*
* @return number of bytes read
* @throws java.io.IOException in case length is
*/
public int tryRead(byte[] buffer, int offset, int length) throws IOException {
validateMaxLength(length);
// NOTE: below reading byte by byte instead of "int bytesRead = is.read(firstBytes, 0, maxBytesOfResponseToLog);"
// because read() guarantees to read a byte
int bytesRead = 0;
int nextByte = 0;
for (int i = 0; (i < length) && (nextByte >= 0); i++) {
nextByte = read();
if (nextByte >= 0) {
buffer[offset + bytesRead++] = (byte) nextByte;
}
}
if (bytesRead > 0) {
((PushbackInputStream) in).unread(buffer, offset, bytesRead);
}
return bytesRead;
}
public byte[] tryRead(int maxBytesToRead) throws IOException {
validateMaxLength(maxBytesToRead);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); // as ByteArrayOutputStream to dynamically allocate internal bytes array instead of allocating possibly large buffer (if maxBytesToRead is large)
// NOTE: below reading byte by byte instead of "int bytesRead = is.read(firstBytes, 0, maxBytesOfResponseToLog);"
// because read() guarantees to read a byte
int nextByte = 0;
for (int i = 0; (i < maxBytesToRead) && (nextByte >= 0); i++) {
nextByte = read();
if (nextByte >= 0) {
baos.write((byte) nextByte);
}
}
byte[] buffer = baos.toByteArray();
if (buffer.length > 0) {
((PushbackInputStream) in).unread(buffer, 0, buffer.length);
}
return buffer;
}
private void validateMaxLength(int length) throws IOException {
if (length > maxPushbackBufferSize) {
throw new IOException(
"Trying to read more bytes than maxBytesToRead. Max bytes: " + maxPushbackBufferSize + ". Trying to read: " +
length);
}
}
}
คลาสนี้มีสองวิธี หนึ่งสำหรับการอ่านลงในบัฟเฟอร์ที่มีอยู่ (นิยามนั้นคล้ายคลึงกับการโทรpublic int read(byte b[], int off, int len)
คลาส InputStream) ประการที่สองซึ่งส่งคืนบัฟเฟอร์ใหม่ (ซึ่งอาจมีประสิทธิภาพมากขึ้นหากไม่ทราบขนาดของบัฟเฟอร์ที่จะอ่าน)
ตอนนี้เรามาดูการใช้งานคลาสของเรา:
public class StreamTest2 {
public static void main(String[] args) throws IOException {
byte[] bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
InputStream originalStream = new ByteArrayInputStream(bytes);
byte[] readBytes = getBytes(originalStream, 3);
printBytes(readBytes); // prints: 1 2 3
readBytes = getBytes(originalStream, 3);
printBytes(readBytes); // prints: 4 5 6
// now let's use our TryReadInputStream
originalStream = new ByteArrayInputStream(bytes);
InputStream wrappedStream = new TryReadInputStream(originalStream, 10);
readBytes = ((TryReadInputStream) wrappedStream).tryRead(3); // NOTE: no manual call to "unread"(!) because TryReadInputStream handles this internally
printBytes(readBytes); // prints 1 2 3
readBytes = ((TryReadInputStream) wrappedStream).tryRead(3);
printBytes(readBytes); // prints 1 2 3
readBytes = ((TryReadInputStream) wrappedStream).tryRead(3);
printBytes(readBytes); // prints 1 2 3
// we can also call normal read which will actually read the bytes without "writing them back"
readBytes = getBytes(wrappedStream, 3);
printBytes(readBytes); // prints 1 2 3
readBytes = getBytes(wrappedStream, 3);
printBytes(readBytes); // prints 4 5 6
readBytes = ((TryReadInputStream) wrappedStream).tryRead(3); // now we can try read next bytes
printBytes(readBytes); // prints 7 8 9
readBytes = ((TryReadInputStream) wrappedStream).tryRead(3);
printBytes(readBytes); // prints 7 8 9
}
}
หากคุณใช้การนำไปใช้งานInputStream
คุณสามารถตรวจสอบผลลัพธ์InputStream#markSupported()
ที่บอกคุณได้ว่าคุณสามารถใช้วิธีการmark()
/reset()
/
หากคุณสามารถทำเครื่องหมายสตรีมเมื่อคุณอ่านแล้วreset()
ให้โทรกลับเพื่อเริ่มต้น
หากทำไม่ได้คุณจะต้องเปิดสตรีมอีกครั้ง
อีกวิธีหนึ่งคือการแปลง InputStream เป็นไบต์อาร์เรย์จากนั้นทำซ้ำในอาร์เรย์หลาย ๆ ครั้งตามที่คุณต้องการ คุณสามารถค้นหาโซลูชันต่างๆได้ในโพสต์นี้แปลง InputStream เป็นไบต์อาร์เรย์ใน Javaโดยใช้ libs ของบุคคลที่สามหรือไม่ ข้อควรระวังหากเนื้อหาที่อ่านมีขนาดใหญ่เกินไปคุณอาจประสบปัญหาหน่วยความจำบางอย่าง
สุดท้ายหากคุณต้องการอ่านภาพให้ใช้:
BufferedImage image = ImageIO.read(new URL("http://www.example.com/images/toto.jpg"));
การใช้ImageIO#read(java.net.URL)
ยังช่วยให้คุณสามารถใช้แคช
ImageIO#read(java.net.URL)
: เว็บเซิร์ฟเวอร์และ CDNs อาจปฏิเสธสายเปลือย (เช่นโดยไม่ต้องมีตัวแทนของผู้ใช้ที่ทำให้เซิร์ฟเวอร์เชื่อว่าโทรมาจากเว็บเบราเซอร์) ImageIO#read
ที่ทำโดย ในกรณีนั้นการใช้URLConnection.openConnection()
การตั้งค่าตัวแทนผู้ใช้เป็นการเชื่อมต่อนั้น + โดยใช้ ImageIO.read (InputStream) ส่วนใหญ่จะทำเคล็ดลับ
InputStream
ไม่ใช่อินเทอร์เฟซ
เกี่ยวกับ:
if (stream.markSupported() == false) {
// lets replace the stream object
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IOUtils.copy(stream, baos);
stream.close();
stream = new ByteArrayInputStream(baos.toByteArray());
// now the stream should support 'mark' and 'reset'
}
สำหรับการแยกเป็นInputStream
สองส่วนในขณะที่หลีกเลี่ยงการโหลดข้อมูลทั้งหมดในหน่วยความจำแล้วประมวลผลอย่างอิสระ:
OutputStream
อย่างแม่นยำ:PipedOutputStream
PipedInputStream
คือสิ่งที่ส่งคืนInputStream
จะถูกส่งกลับOutputStream
จัดหาเพิ่งสร้าง ดังนั้นทุกอย่างที่อ่านจากการจัดหาInputStream
จะเขียนเป็นทั้งสองOutputStream
อย่าง ไม่จำเป็นต้องใช้สิ่งนั้นเพราะมันเสร็จแล้วในTeeInputStream
(commons.io)ภายในเธรดที่แยกจากกันอ่าน inputStream การจัดหาทั้งหมดและโดยปริยายข้อมูลอินพุตจะถูกโอนไปยัง inputStreams เป้าหมาย
public static final List<InputStream> splitInputStream(InputStream input)
throws IOException
{
Objects.requireNonNull(input);
PipedOutputStream pipedOut01 = new PipedOutputStream();
PipedOutputStream pipedOut02 = new PipedOutputStream();
List<InputStream> inputStreamList = new ArrayList<>();
inputStreamList.add(new PipedInputStream(pipedOut01));
inputStreamList.add(new PipedInputStream(pipedOut02));
TeeOutputStream tout = new TeeOutputStream(pipedOut01, pipedOut02);
TeeInputStream tin = new TeeInputStream(input, tout, true);
Executors.newSingleThreadExecutor().submit(tin::readAllBytes);
return Collections.unmodifiableList(inputStreamList);
}
โปรดทราบว่าให้ปิด inputStreams หลังจากถูกใช้งานและปิดเธรดที่รัน: TeeInputStream.readAllBytes()
ในกรณีนี้คุณต้องแบ่งออกเป็นหลาย ๆInputStream
แทนที่จะเป็นเพียงสองอย่าง แทนที่ในส่วนก่อนหน้าของโค้ดคลาสTeeOutputStream
สำหรับการใช้งานของคุณเองซึ่งจะห่อหุ้ม a List<OutputStream>
และแทนที่OutputStream
อินเทอร์เฟซ:
public final class TeeListOutputStream extends OutputStream {
private final List<? extends OutputStream> branchList;
public TeeListOutputStream(final List<? extends OutputStream> branchList) {
Objects.requireNonNull(branchList);
this.branchList = branchList;
}
@Override
public synchronized void write(final int b) throws IOException {
for (OutputStream branch : branchList) {
branch.write(b);
}
}
@Override
public void flush() throws IOException {
for (OutputStream branch : branchList) {
branch.flush();
}
}
@Override
public void close() throws IOException {
for (OutputStream branch : branchList) {
branch.close();
}
}
}
แปลงอินพุตสตรีมเป็นไบต์จากนั้นส่งต่อไปยังฟังก์ชัน savefile ที่คุณประกอบเข้าด้วยกันในอินพุตสตรีม นอกจากนี้ในฟังก์ชันดั้งเดิมยังใช้ไบต์เพื่อใช้สำหรับงานอื่น ๆ
ในกรณีที่ใครก็ตามกำลังใช้งานแอป Spring Boot และคุณต้องการอ่านเนื้อหาการตอบสนองของไฟล์ RestTemplate
(ซึ่งเป็นเหตุผลว่าทำไมฉันถึงต้องการอ่านสตรีมสองครั้ง) มีวิธีที่สะอาด (er) ในการทำเช่นนี้
ก่อนอื่นคุณต้องใช้ Spring StreamUtils
เพื่อคัดลอกสตรีมไปยัง String:
String text = StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()))
แต่นั่นไม่ใช่ทั้งหมด คุณต้องใช้โรงงานที่ร้องขอที่สามารถบัฟเฟอร์สตรีมให้คุณได้เช่น:
ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
RestTemplate restTemplate = new RestTemplate(factory);
หรือถ้าคุณใช้ถั่วโรงงาน (นี่คือ Kotlin แต่อย่างไรก็ตาม):
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
fun createRestTemplate(): RestTemplate = RestTemplateBuilder()
.requestFactory { BufferingClientHttpRequestFactory(SimpleClientHttpRequestFactory()) }
.additionalInterceptors(loggingInterceptor)
.build()
หากคุณใช้ RestTemplate เพื่อโทร http เพียงเพิ่มตัวดักฟัง เนื้อหาการตอบกลับถูกแคชโดยการนำ ClientHttpResponse ไปใช้ ตอนนี้ inputstream สามารถดึงข้อมูลจาก respose กี่ครั้งก็ได้ตามที่เราต้องการ
ClientHttpRequestInterceptor interceptor = new ClientHttpRequestInterceptor() {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
ClientHttpResponse response = execution.execute(request, body);
// additional work before returning response
return response
}
};
// Add the interceptor to RestTemplate Instance
restTemplate.getInterceptors().add(interceptor);