দুবার স্ট্রিম পড়ুন


127

একই ইনপুটস্ট্রিমটি আপনি দু'বার কীভাবে পড়বেন? এটি কি কোনওভাবে অনুলিপি করা সম্ভব?

আমার ওয়েব থেকে একটি চিত্র পেতে হবে, এটি স্থানীয়ভাবে সংরক্ষণ করুন এবং তারপরে সংরক্ষিত চিত্রটি ফিরিয়ে আনতে হবে। আমি কেবল ভেবেছিলাম ডাউনলোড করা সামগ্রীতে নতুন স্ট্রিম শুরু করার পরিবর্তে একই স্ট্রিমটি ব্যবহার করা আরও দ্রুত হবে এবং তারপরে এটি আবার পড়তে হবে।


1
সম্ভবত চিহ্ন এবং পুনরায় সেট করুন
ভায়াসস্লাভ শাইলকিন

উত্তর:


113

আপনি org.apache.commons.io.IOUtils.copyইনপুট স্ট্রিমের সামগ্রীগুলি বাইট অ্যারেতে অনুলিপি করতে ব্যবহার করতে পারেন এবং তারপরে বার বার বাইটআরআরেপুট স্ট্রিম ব্যবহার করে বাইট অ্যারে থেকে বার বার পড়তে পারেন। উদাহরণ:

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

1
আমি মনে করি এটি একমাত্র বৈধ সমাধান কারণ চিহ্নটি সমস্ত ধরণের জন্য সমর্থিত নয়।
ওয়ারপজিট

3
@ পল গ্রিম: আইইউটিস. টোবাইআরে অভ্যন্তরীণভাবে অভ্যন্তরীণভাবে অনুলিপি পদ্ধতিতে কল করে।
অঙ্কিত

4
@ অঙ্কিত যেমন বলেছেন, এই সমাধানটি আমার পক্ষে বৈধ নয়, যেহেতু ইনপুটটি অভ্যন্তরীণভাবে পড়ে এবং পুনরায় ব্যবহার করা যায় না।
এক্সট্রিম বাইকার

30
আমি জানি যে এই মন্তব্যটি সময়ের বাইরে গেছে, তবে, এখানে প্রথম বিকল্পে, আপনি যদি ইনপুটস্ট্রিমটি বাইট অ্যারে হিসাবে পড়ে থাকেন, তবে এর অর্থ এই নয় যে আপনি সমস্ত ডেটা মেমোরিতে লোড করছেন? যদি আপনি বড় ফাইলগুলির মতো কিছু লোড করেন তবে একটি বড় সমস্যা হতে পারে?
jaxkodex

2
এক কলে একটি বাইট অ্যারে পেতে আইউইটিস. টোবাইটআর্রে (ইনপুটস্ট্রিম) ব্যবহার করতে পারে।
দরকারী

30

ইনপুটস্ট্রিমটি কোথা থেকে আসছে তার উপর নির্ভর করে আপনি এটিকে পুনরায় সেট করতে সক্ষম হবেন না। যদি পরীক্ষা করতে পারবেন mark()এবং reset()ব্যবহার সমর্থিত markSupported()

যদি এটি হয় তবে আপনি reset()ইনপুটস্ট্রিমে শুরুতে ফিরে আসতে কল করতে পারেন। যদি তা না হয় তবে আপনাকে উত্স থেকে আবার ইনপুট স্ট্রিম পড়তে হবে।


1
ইনপুট স্ট্রিম 'চিহ্ন' সমর্থন করে না - আপনি কোনও আইএস-তে কল কল করতে পারেন তবে এটি কিছুই করে না। তেমনি, আইএস-তে কলিং রিসেট ব্যতিক্রম ছুঁড়ে ফেলবে।
আয়ুয়াসকা

4
@ InputStreamইয়াহুয়াসকা সাবস্ক্লাসগুলি BufferedInputStream'চিহ্ন' সমর্থন করে
দিমিত্রি বোগদানোভিচ

10

যদি আপনার InputStreamসমর্থন চিহ্ন ব্যবহার করে, তবে আপনি mark()নিজের ইনপুট স্ট্রিম করতে পারেন এবং তারপরে reset()এটি করতে পারেন । যদি আপনার InputStremচিহ্নটি সমর্থন করে না তবে আপনি ক্লাসটি ব্যবহার করতে পারেন java.io.BufferedInputStream, যাতে আপনি এর BufferedInputStreamমতো আপনার স্ট্রিমটি এম্বেড করতে পারেন

    InputStream bufferdInputStream = new BufferedInputStream(yourInputStream);
    bufferdInputStream.mark(some_value);
    //read your bufferdInputStream 
    bufferdInputStream.reset();
    //read it again

1
একটি বাফার ইনপুট স্ট্রিমটি কেবল বাফার আকারে ফিরে চিহ্নিত করতে পারে, তাই যদি উত্সটি ফিট না হয় তবে আপনি প্রথম দিকে আর যেতে পারবেন না।
এল ব্ল্যাঙ্ক

@ এল.ব্ল্যাঙ্ক দুঃখিত তবে এটি সঠিক বলে মনে হচ্ছে না। একবার দেখুন BufferedInputStream.fill(), সেখানে "গ্রোথ বাফার" বিভাগ রয়েছে, যেখানে নতুন বাফার আকারটি কেবল marklimitএবং এর সাথে তুলনা করা হয় MAX_BUFFER_SIZE
ইউজিনি 82

8

আপনি পুশব্যাক ইনপুট স্ট্রিমের সাথে ইনপুট স্ট্রিমটি মোড়ানো করতে পারেন। পুশব্যাক ইনপুট স্ট্রিম ইতিমধ্যে পঠিত বাইটগুলি অপঠিত (" ফিরে লিখুন ") করতে দেয়, সুতরাং আপনি এটি করতে পারেন:

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


}

দয়া করে নোট করুন যে পুশব্যাক ইনপুট স্ট্রিম বাইটের অভ্যন্তরীণ বাফার সংরক্ষণ করে তাই এটি মেমরির সত্যিকার অর্থে একটি বাফার তৈরি করে যা বাইট "লিখিত ব্যাক" ধারণ করে।

এই পদ্ধতির বিষয়টি জানতে পেরে আমরা আরও যেতে পারি এবং এটি ফিল্টার ইনপুট স্ট্রিমের সাথে একত্রিত করতে পারি। ফিল্টার ইনপুট স্ট্রিম একটি প্রতিনিধি হিসাবে আসল ইনপুট স্ট্রিম সংরক্ষণ করে। এটি নতুন শ্রেণীর সংজ্ঞা তৈরি করতে দেয় যা মূল ডেটা স্বয়ংক্রিয়ভাবে " অপঠিত " করতে দেয় । এই শ্রেণীর সংজ্ঞাটি নিম্নলিখিত:

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)ইনপুটস্ট্রিম ক্লাসে কল করার অনুরূপ )। দ্বিতীয়টি যা নতুন বাফার দেয় (এটি পড়ার জন্য বাফারের আকারটি অজানা থাকলে এটি আরও কার্যকর হতে পারে)।

এখন আসুন আমাদের ক্লাসটি কার্যকরভাবে দেখি:

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


  }



}

5

আপনি যদি এর বাস্তবায়ন ব্যবহার করে থাকেন তবে আপনি সেই পদ্ধতিটি / ব্যবহার করতে পারবেন কি না তা আপনি InputStreamতার ফলাফল পরীক্ষা InputStream#markSupported()করতে পারেন ।mark()reset()

আপনি যখন পড়ছেন তখন আপনি যদি স্ট্রিমটিকে চিহ্নিত করতে পারেন তবে reset()শুরু করতে ফিরে যেতে কল করুন ।

আপনি না পারলে আপনাকে আবার একটি স্ট্রিম খুলতে হবে।

আর একটি সমাধান হ'ল ইনপুটস্ট্রিমটিকে বাইট অ্যারেতে রূপান্তর করা হবে, তারপরে আপনার প্রয়োজন অনুসারে অ্যারে থেকে পুনরাবৃত্তি করুন। আপনি এই পোস্টে জাভাতে তৃতীয় পক্ষের libs বা না ব্যবহার করে বাইট অ্যারে রূপান্তর করতে ইনপুট স্ট্রিমের বেশ কয়েকটি সমাধান খুঁজে পেতে পারেন । সতর্কতা, পঠিত সামগ্রীটি যদি খুব বড় হয় তবে আপনি কিছু স্মৃতি স্মরণ করতে পারেন।

অবশেষে, আপনার যদি চিত্রটি পড়ার প্রয়োজন হয় তবে ব্যবহার করুন:

BufferedImage image = ImageIO.read(new URL("http://www.example.com/images/toto.jpg"));

ব্যবহার ImageIO#read(java.net.URL)আপনাকে ক্যাশে ব্যবহার করতে দেয়।


1
সতর্কতার শব্দটি ব্যবহার করার সময় ImageIO#read(java.net.URL): কিছু ওয়েবসার্ভার এবং সিডিএনগুলি বেয়ার কলগুলি প্রত্যাখ্যান করতে পারে (অর্থাত্ কোনও ব্যবহারকারী এজেন্ট ব্যতীত যে সার্ভারকে বিশ্বাস করে যে কলটি একটি ওয়েব ব্রাউজার থেকে এসেছে) ImageIO#read। যে ক্ষেত্রে ব্যবহার URLConnection.openConnection()করে সংযোগ + + `ImageIO.read (InputStream) ব্যবহার করে, বার বেশিরভাগ কৌতুক করতে হবে ইউজার এজেন্ট সেটিং।
ক্লিন্ট ইস্টউড

InputStreamকোনও ইন্টারফেস নয়
ব্রাইস

3

কেমন:

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'

    }

5
এটি একটি ভয়ানক ধারণা। আপনি পুরো স্ট্রিমের বিষয়বস্তুগুলিকে মেমোরিতে রাখেন।
নীলস ডাউসেট

3

একটি InputStreamইন দুটি বিভক্ত করার জন্য , মেমরিতে সমস্ত ডেটা লোড করা এড়ানো এবং তারপরে সেগুলি স্বাধীনভাবে প্রক্রিয়া করুন:

  1. একটি দম্পতি তৈরি করুন OutputStream, অবিকল:PipedOutputStream
  2. প্রতিটি পাইপড আউটপুট স্ট্রিমটিকে একটি পাইপড ইনপুট স্ট্রিমের সাথে সংযুক্ত করুন, এগুলি PipedInputStreamপ্রত্যাবর্তিত InputStream
  3. সৃস্টিং ইনপুট স্ট্রিমটি সদ্য তৈরির সাথে সংযুক্ত করুন OutputStream। সুতরাং, সবকিছুই এটি সোর্সিং থেকে পড়ে InputStream, উভয়ই লেখা থাকবে OutputStream। এটি বাস্তবায়নের দরকার নেই, কারণ এটি ইতিমধ্যে TeeInputStream(কমন্স.আইও) -তে সম্পন্ন হয়েছে ।
  4. একটি পৃথক থ্রেডের মধ্যে পুরো সোর্সিং ইনপুট স্ট্রিমটি পড়ুন এবং স্পষ্টতই ইনপুট ডেটা লক্ষ্য ইনপুট স্ট্রিমগুলিতে স্থানান্তরিত হয়।

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

গ্রাস হওয়ার পরে ইনপুট স্ট্রিমগুলি বন্ধ করতে সচেতন হোন এবং যে থ্রেডটি চলছে তা বন্ধ করুন: TeeInputStream.readAllBytes()

ক্ষেত্রে, আপনি প্রয়োজন একাধিক সেটিকে বিভক্তInputStream মাত্র দুই পরিবর্তে। TeeOutputStreamআপনার নিজের প্রয়োগের জন্য ক্লাসের কোডটির আগের টুকরোটি প্রতিস্থাপন করুন , যা একটিটিকে আবদ্ধ করে 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();
        }
    }
}

দয়া করে, আপনি আরও কিছু ধাপ 4 ব্যাখ্যা করতে পারেন? কেন আমাদের ম্যানুয়ালি পড়া ট্রিগার করতে হবে? পাইপডিনপুট স্ট্রিমের কোনও পড়া উত্স ইনপুট স্ট্রিমের পাঠকে কেন ট্রিগার করে না? এবং কেন আমরা কলটি অ্যাসিক্রোনাসলি করি?
Кулешов

2

ইনপুটস্প্রিমটি বাইটে রূপান্তর করুন এবং তারপরে সেভফিল ফাংশনে পাস করুন যেখানে আপনি একই ইনপুটস্ট্রিমে একত্রিত হন। অন্যান্য ফাংশন ব্যবহারের জন্য মূল ফাংশন ব্যবহার করুন


5
আমি এটি সম্পর্কে খারাপ ধারণা বলি, ফলস্বরূপ অ্যারেটি বিশাল হতে পারে এবং স্মৃতির ডিভাইসটি ছিনিয়ে নেবে।
কেভিন পার্কার

0

যদি কেউ স্প্রিং বুট অ্যাপে চলছে, এবং আপনি কোনওটির প্রতিক্রিয়া বডিটি পড়তে চান RestTemplate(এজন্য আমি দুটি বার স্ট্রিমটি পড়তে চাই) তবে এটি করার একটি পরিষ্কার (উপায়) উপায় রয়েছে।

সবার আগে, আপনার StreamUtilsস্ট্রিংটিতে অনুলিপি করতে স্প্রিংস ব্যবহার করতে হবে :

String text = StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()))

কিন্তু এখানেই শেষ নয়. আপনার অনুরোধ কারখানাটিও ব্যবহার করা উচিত যা আপনার জন্য প্রবাহকে বাফার করতে পারে:

ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
RestTemplate restTemplate = new RestTemplate(factory);

অথবা, আপনি যদি কারখানার শিমটি ব্যবহার করেন, তবে (এটি কোটলিন তবে তবুও):

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
fun createRestTemplate(): RestTemplate = RestTemplateBuilder()
  .requestFactory { BufferingClientHttpRequestFactory(SimpleClientHttpRequestFactory()) }
  .additionalInterceptors(loggingInterceptor)
  .build()

সূত্র: https://objectpartners.com/2018/03/01/log-your-resttemplate-request-and-response-without-destroying-the-body/


0

আপনি যদি এইচটিপি কল করতে রেস্টটেম্পলেট ব্যবহার করছেন তবে কেবল একটি ইন্টারসেপ্টার যুক্ত করুন। প্রতিক্রিয়া সংস্থা ক্লায়েন্ট এইচটিপিআরস্পোনস প্রয়োগের মাধ্যমে ক্যাশে করা হয় is এখন ইনপুটস্ট্রিম রিসোস থেকে আমাদের যতবার প্রয়োজন পুনরুদ্ধার করা যায়

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); 
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.