实现一个自动关闭InputStream的类

本文介绍了Apache HttpClient中的一种自动关闭输入流的实现方式,通过AutoCloseInputStream类,在读取结束时自动关闭输入流,简化了资源管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在日志操作IO时候,经常忘记关闭输入输出流,如果能在读取结束之后自动关闭就好了,发现httpclient内部实现了一个。

/*
 * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/AutoCloseInputStream.java,v 1.9 2004/04/18 23:51:34 jsdever Exp $
 * $Revision: 505890 $
 * $Date: 2007-02-11 12:25:25 +0100 (Sun, 11 Feb 2007) $
 *
 * ====================================================================
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */

package org.apache.commons.httpclient;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Closes an underlying stream as soon as the end of the stream is reached, and
 * notifies a client when it has done so.
 *
 * @author Ortwin Glueck
 * @author Eric Johnson
 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
 *
 * @since 2.0
 */
class AutoCloseInputStream extends FilterInputStream {

    /** 
     * True if this stream is open.  Assume that the underlying stream 
     * is open until we get an EOF indication.
     */
    private boolean streamOpen = true;

    /** True if the stream closed itself. */
    private boolean selfClosed = false;

    /** 
     * The watcher is notified when the contents of the stream have
     * been  exhausted
     */ 
    private ResponseConsumedWatcher watcher = null;

    /**
     * Create a new auto closing stream for the provided connection
     *
     * @param in the input stream to read from
     * @param watcher   To be notified when the contents of the stream have been
     *  consumed.
     */
    public AutoCloseInputStream(
            final InputStream in, final ResponseConsumedWatcher watcher) {
        super(in);
        this.watcher = watcher;
    }

    /**
     * Reads the next byte of data from the input stream.
     *
     * @throws IOException when there is an error reading
     * @return the character read, or -1 for EOF
     */
    public int read() throws IOException {
        int l = -1;

        if (isReadAllowed()) {
            // underlying stream not closed, go ahead and read.
            l = super.read();
            checkClose(l);
        }

        return l;
    }

    /**
     * Reads up to <code>len</code> bytes of data from the stream.
     *
     * @param b a <code>byte</code> array to read data into
     * @param off an offset within the array to store data
     * @param len the maximum number of bytes to read
     * @return the number of bytes read or -1 for EOF
     * @throws IOException if there are errors reading
     */
    public int read(byte[] b, int off, int len) throws IOException {
        int l = -1;

        if (isReadAllowed()) {
            l = super.read(b,  off,  len);
            checkClose(l);
        }

        return l;
    }

    /**
     * Reads some number of bytes from the input stream and stores them into the
     * buffer array b.
     *
     * @param b a <code>byte</code> array to read data into
     * @return the number of bytes read or -1 for EOF
     * @throws IOException if there are errors reading
     */
    public int read(byte[] b) throws IOException {
        int l = -1;

        if (isReadAllowed()) {
            l = super.read(b);
            checkClose(l);
        }
        return l;
    }

    /**
     * Obtains the number of bytes that can be read without blocking.
     *
     * @return  the number of bytes available without blocking
     * @throws IOException in case of a problem
     */
    public int available() throws IOException {
        int a = 0; // not -1

        if (isReadAllowed()) {
            a = super.available();
            // no checkClose() here, available() can't trigger EOF
        }

        return a;
    }

    /**
     * Close the stream, and also close the underlying stream if it is not
     * already closed.
     * @throws IOException If an IO problem occurs.
     */
    public void close() throws IOException {
        if (!selfClosed) {
            selfClosed = true;
            notifyWatcher();
        }
    }

    /**
     * Close the underlying stream should the end of the stream arrive.
     *
     * @param readResult    The result of the read operation to check.
     * @throws IOException If an IO problem occurs.
     */
    private void checkClose(int readResult) throws IOException {
        if (readResult == -1) {
            notifyWatcher();
        }
    }

    /**
     * See whether a read of the underlying stream should be allowed, and if
     * not, check to see whether our stream has already been closed!
     *
     * @return <code>true</code> if it is still OK to read from the stream.
     * @throws IOException If an IO problem occurs.
     */
    private boolean isReadAllowed() throws IOException {
        if (!streamOpen && selfClosed) {
            throw new IOException("Attempted read on closed stream.");
        }
        return streamOpen;
    }

    /**
     * Notify the watcher that the contents have been consumed.
     * @throws IOException If an IO problem occurs.
     */
    private void notifyWatcher() throws IOException {
        if (streamOpen) {
            super.close();
            streamOpen = false;

            if (watcher != null) {
                watcher.responseConsumed();
            }
        }
    }
}

 

<think>我们正在处理一个关于Java中合并多个InputStream一个InputStream的问题。 根据引用资料,我们可以使用SequenceInputStream实现这个功能。SequenceInputStreamJava IO库中的一个,它可以将多个输入流按顺序连接起来,形成一个单一的输入流。 在引用[1]中提到:SequenceInputStream可以将两个或多个其他InputStream合并为一个。首先,SequenceInputStream将读取第一个InputStream中的所有字节,然后读取第二个InputStream中的所有字节。这就是它被称为SequenceInputStream的原因,因为InputStream实例是按顺序读取的。 引用[2]展示了如何合并两个文件:它创建了两个FileInputStream(分别对应a.txt和b.txt),然后使用SequenceInputStream将它们合并,最后写入到c.txt中。 但是,如果我们需要合并多于两个的输入流,我们可以使用SequenceInputStream的另一个构造函数,它接受一个Enumeration<InputStream>。引用[3]展示了如何将多个InputStream添加到一个List中,然后使用SequenceInputStream(通过将List转换为Enumeration)来合并它们。 此外,引用[4]中提到了从网络获取InputStream,但合并的原理是一样的。 因此,我们可以总结出两种方法: 1. 合并两个InputStream:直接使用SequenceInputStream(InputStream s1, InputStream s2) 2. 合并多个InputStream:使用SequenceInputStream(Enumeration<? extends InputStream> e) 由于Enumeration是一个接口,我们可以将多个InputStream放入一个Vector(它提供了elements()方法返回Enumeration)或者使用Collections.enumeration()方法将List转换为Enumeration。 下面我们分别给出示例代码: 方法1:合并两个InputStream ```java InputStream input1 = new FileInputStream("file1.txt"); InputStream input2 = new FileInputStream("file2.txt"); InputStream combined = new SequenceInputStream(input1, input2); ``` 方法2:合并多个InputStream(超过两个) ```java List<InputStream> streams = new ArrayList<>(); streams.add(new FileInputStream("file1.txt")); streams.add(new FileInputStream("file2.txt")); streams.add(new FileInputStream("file3.txt")); // 将List转换为Enumeration Enumeration<InputStream> enumeration = Collections.enumeration(streams); InputStream combined = new SequenceInputStream(enumeration); ``` 需要注意的是,在读取完合并的输入流后,我们需要关闭它。关闭SequenceInputStream将会关闭所有包含的输入流(在读取完成后)。但为了确保资源被正确释放,我们通常使用try-with-resources语句。 另外,引用[3]中还展示了使用PdfCopy来合并PDF文件,这属于特定格式的合并,而我们这里讨论的是任意二进制流的顺序合并。 因此,针对用户的问题“Java 合并多个 InputStream一个”,我们可以给出以下实现步骤: 步骤: 1. 创建一个包含所有需要合并的InputStream的列表(List<InputStream>)。 2. 将列表转换为Enumeration<InputStream>。 3. 创建SequenceInputStream实例,传入这个Enumeration。 4. 使用这个SequenceInputStream进行读取操作。 5. 使用完毕后关闭SequenceInputStream(它会逐个关闭包含的输入流)。 示例代码(合并多个文件): ```java import java.io.*; import java.util.*; public class MergeInputStreams { public static void main(String[] args) throws IOException { // 创建输入流列表 List<InputStream> inputStreams = new ArrayList<>(); inputStreams.add(new FileInputStream("file1.txt")); inputStreams.add(new FileInputStream("file2.txt")); inputStreams.add(new FileInputStream("file3.txt")); // 转换为Enumeration Enumeration<InputStream> enumeration = Collections.enumeration(inputStreams); try (SequenceInputStream combined = new SequenceInputStream(enumeration); FileOutputStream out = new FileOutputStream("combined.txt")) { byte[] buffer = new byte[1024]; int length; while ((length = combined.read(buffer)) != -1) { out.write(buffer, 0, length); } } // 使用try-with-resources确保流关闭 } } ``` 注意:SequenceInputStream会按顺序读取每个输入流,直到最后一个输入流的末尾。在读取过程中,当前输入流结束时,SequenceInputStream自动切换到下一个输入流。 此外,如果输入流来自不同的来源(如网络、内存等),同样适用。 但是,如果合并的输入流中有异常处理的需要,我们需要注意:如果在读取某个输入流时发生异常,那么整个SequenceInputStream的读取就会中断,并且后续的输入流将不会被读取。因此,在实际应用中,可能需要更健壮的异常处理机制。 最后,我们根据要求生成相关问题。</think>在 Java 中合并多个 `InputStream` 为一个,可以使用 `SequenceInputStream` ,它按顺序读取多个输入流,形成一个逻辑上的连续流。以下是实现方法: ### 实现步骤 1. **创建输入流列表**:将需要合并的 `InputStream` 对象放入集合中 2. **转换为枚举型**:使用 `Collections.enumeration()` 将集合转为 `Enumeration<InputStream>` 3. **创建合并流**:实例化 `SequenceInputStream` 并传入枚举对象 4. **读取数据**:从合并流中读取数据(按添加顺序自动衔接) 5. **关闭资源**:关闭合并流时会自动关闭所有包含的输入流 ### 代码示例 ```java import java.io.*; import java.util.*; public class InputStreamMerger { public static void main(String[] args) throws IOException { // 1. 创建输入流集合 List<InputStream> streams = new ArrayList<>(); streams.add(new FileInputStream("file1.txt")); // 文件流 streams.add(new ByteArrayInputStream("内存数据".getBytes())); // 内存流 streams.add(new FileInputStream("file2.txt")); // 2. 转换为枚举型 Enumeration<InputStream> enumeration = Collections.enumeration(streams); // 3. 创建合并流 try (SequenceInputStream combinedStream = new SequenceInputStream(enumeration); FileOutputStream output = new FileOutputStream("merged.txt")) { // 4. 读取合并后的数据并写入文件 byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = combinedStream.read(buffer)) != -1) { output.write(buffer, 0, bytesRead); } } // 5. 自动关闭所有流 (try-with-resources) } } ``` ### 关键说明 1. **顺序读取机制**: - `SequenceInputStream` 按枚举顺序逐个读取输入流 - 前一个流读取到 `EOF` 后自动切换到下一个流 - 示例流程:`文件1 → 内存数据 → 文件2 → 结束` 2. **资源管理**: - 使用 **try-with-resources** 确保所有流自动关闭 - 关闭 `SequenceInputStream` 会递归关闭其包含的所有输入流[^1] 3. **适用场景**: - 合并多个文件(如日志分割后重组) - 拼接网络流与本地缓存流 - 组合二进制数据与文本数据(如图片+元信息) ### 注意事项 - **大文件处理**:建议使用缓冲流包装(如 `BufferedInputStream`)提升性能 ```java InputStream buffered = new BufferedInputStream(combinedStream); ``` - **异常处理**:某个流读取失败会导致整个合并中断,需添加异常捕获 - **空流检测**:添加 `null` 检查避免 `NullPointerException` > 通过 `SequenceInputStream` 实现流合并是 Java IO 的标准方案,可确保数据按顺序无缝衔接[^2][^3]。对于特定格式(如 PDF),可能需要使用专用库(如 `iText`)处理格式结构[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值