Java InputStream 原理用法源码分析
原理
InputStream
是 Java 中用于读取字节流的抽象类,它是输入流层次结构的基类。InputStream
提供了一组方法来从输入源中读取字节数据。
原理:
InputStream
通过底层的具体实现类(如FileInputStream
、ByteArrayInputStream
等)提供了对不同类型输入源的访问。- 当我们使用某个具体的
InputStream
实现时,该实现会负责读取底层输入源的字节数据并提供给调用者。
用法示例
使用 InputStream
的基本步骤如下:
-
创建
InputStream
对象:根据需要读取的数据源类型(如文件、网络连接等),选择合适的具体实现类创建InputStream
对象。常见的实现类有FileInputStream
、ByteArrayInputStream
、Socket.getInputStream()
等。InputStream inputStream = new FileInputStream("path/to/file.txt");
-
读取字节数据:
-
使用
read()
方法逐个字节地读取数据,直到达到文件末尾(即返回值为 -1)。int byteValue; while ((byteValue = inputStream.read()) != -1) { // 处理读取到的字节数据 }
-
使用
read(byte[])
方法批量读取数据到字节数组。byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { // 处理读取到的字节数组数据 }
-
-
关闭
InputStream
:在读取完毕后,应该关闭InputStream
来释放资源。inputStream.close();
以下是一个示例,展示了如何使用 InputStream
从文件中读取数据并进行处理:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class InputStreamExample {
public static void main(String[] args) {
try {
// 创建 FileInputStream 对象
InputStream inputStream = new FileInputStream("path/to/file.txt");
// 读取文件内容并打印
int byteValue;
while ((byteValue = inputStream.read()) != -1) {
// 处理读取到的字节数据
System.out.print((char) byteValue);
}
// 关闭 InputStream
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述示例中,我们使用 FileInputStream
创建了一个 InputStream
对象来读取指定文件的内容。然后,通过循环调用 read()
方法逐个字节地读取文件内容,并将每个字节转换为字符进行打印。最后,关闭 InputStream
来释放资源。
需要注意的是,在实际应用中,需要处理可能抛出的异常以及适当地关闭 InputStream
。此示例仅演示了基本的 InputStream
用法。
方法总结
这个抽象类InputStream
,是所有字节输入流类的超类。它定义了一些抽象方法和实现方法,用于读取字节数据。
-
read()
方法:从输入流中读取下一个字节的数据,并以整数形式返回。如果已经达到流的末尾,则返回-1。该方法会阻塞,直到有可用的输入数据、检测到流的末尾或者抛出异常。 -
read(byte b[])
方法:从输入流中读取一些字节数据,并将其存储到指定的字节数组b
中。返回实际读取的字节数。如果字节数组的长度为0,则不会读取任何字节,返回值为0。如果已经达到流的末尾,则返回-1。首次读取的字节存储在b[0]
元素中,依次类推。最多读取的字节数等于字节数组的长度。该方法会阻塞,直到有可用的输入数据、检测到流的末尾或者抛出异常。 -
read(byte b[], int off, int len)
方法:从输入流中读取最多len
个字节的数据,并将其存储到指定的字节数组b
中,从偏移量off
开始。返回实际读取的字节数。如果已经达到流的末尾,则返回-1。该方法会阻塞,直到有可用的输入数据、检测到流的末尾或者抛出异常。 -
skip(long n)
方法:跳过并丢弃输入流中的n
个字节数据。返回实际跳过的字节数。如果n
为负数,则始终返回0,不会跳过任何字节。该方法会创建一个字节数组,并重复读取直到跳过了n
个字节或者达到流的末尾。子类可以提供更高效的实现方式。 -
available()
方法:返回从此输入流中可以读取(或跳过)而不会阻塞的字节数的估计值。该方法总是返回0。 -
close()
方法:关闭输入流并释放与该流关联的系统资源。 -
mark(int readlimit)
方法:在当前位置标记输入流。调用reset()
方法可以将流重新定位到最后标记的位置,以便重新读取相同的字节。 -
reset()
方法:将流重新定位到最后标记的位置,以便重新读取相同的字节。 -
markSupported()
方法:测试此输入流是否支持mark
和reset
方法。InputStream
的实现总是返回false
。
需要注意的是,这只是一个抽象类,其中的方法大部分都没有具体的实现,而是留给子类去实现。每个子类必须提供返回下一个字节的方法的实现。
中文源码
/**
* 这个抽象类是所有表示字节输入流的类的超类。
*
* <p>需要定义<code>InputStream</code>的子类必须提供一个返回下一个输入字节的方法。
*
* @author Arthur van Hoff
* @see java.io.BufferedInputStream
* @see java.io.ByteArrayInputStream
* @see java.io.DataInputStream
* @see java.io.FilterInputStream
* @see java.io.InputStream#read()
* @see java.io.OutputStream
* @see java.io.PushbackInputStream
* @since JDK1.0
*/
public abstract class InputStream implements Closeable {
// MAX_SKIP_BUFFER_SIZE用于确定在跳过时使用的最大缓冲区大小。
private static final int MAX_SKIP_BUFFER_SIZE = 2048;
/**
* 从输入流中读取下一个字节的数据。返回值为<code>0</code>到<code>255</code>之间的<code>int</code>类型的字节值。
* 如果因为已经达到流的末尾而没有可用的字节,则返回<code>-1</code>。
* 该方法会阻塞,直到有可用的输入数据、检测到流的末尾或者抛出异常。
*
* <p>子类必须提供此方法的实现。
*
* @return 下一个字节的数据,如果已经达到流的末尾则返回<code>-1</code>。
* @exception IOException 如果发生I/O错误。
*/
public abstract int read() throws IOException;
/**
* 从输入流中读取一些字节的数据,并将其存储到缓冲区字节数组<code>b</code>中。返回实际读取的字节数。
* 该方法会阻塞,直到有可用的输入数据、检测到流的末尾或者抛出异常。
*
* <p>如果缓冲区字节数组的长度为0,则不会读取任何字节,返回值为0。
* 如果因为已经达到流的末尾而没有可用的字节,则返回<code>-1</code>。
* 首次读取的字节存储在<code>b[0]</code>元素中,依次类推。
* 最多读取的字节数等于缓冲区字节数组的长度。
*
* <p><code>read(b)</code>方法与下面的调用等效:<pre><code> read(b, 0, b.length) </code></pre>
*
* @param b 存储读取数据的缓冲区字节数组。
* @return 实际读取的字节数,如果已经达到流的末尾则返回<code>-1</code>。
* @exception IOException 如果除了文件结束之外不能读取第一个字节,如果输入流已经关闭,或者发生其他I/O错误。
* @exception NullPointerException 如果<code>b</code>是<code>null</code>。
* @see java.io.InputStream#read(byte[], int, int)
*/
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
/**
* 从输入流中读取最多<code>len</code>个字节的数据,并将其存储到缓冲区字节数组<code>b</code>中,从偏移量<code>off</code>开始。
* 返回实际读取的字节数。
* 该方法会阻塞,直到有可用的输入数据、检测到流的末尾或者抛出异常。
*
* <p>如果<code>len</code>为0,则不会读取任何字节,返回值为0。
* 如果因为已经达到流的末尾而没有可用的字节,则返回<code>-1</code>。
* 首次读取的字节存储在<code>b[off]</code>元素中,依次类推。
* 最多读取的字节数等于<code>len</code>。
*
* <p>无论如何,元素<code>b[0]</code>到<code>b[off]</code>和元素<code>b[off+len]</code>到<code>b[b.length-1]</code>都不受影响。
*
* <p><code>read(b,</code> <code>off,</code> <code>len)</code>方法对于<code>InputStream</code>类具有相同的效果:<pre><code> read(b, off, len) </code></pre>
*
* @param b 存储读取数据的缓冲区字节数组。
* @param off 数据写入缓冲区字节数组<code>b</code>的起始偏移量。
* @param len 最多读取的字节数。
* @return 实际读取的字节数,如果已经达到流的末尾则返回<code>-1</code>。
* @exception IOException 如果除了文件结束之外不能读取第一个字节,输入流已经关闭,或者发生其他I/O错误。
* @exception NullPointerException 如果<code>b</code>是<code>null</code>。
* @exception IndexOutOfBoundsException 如果<code>off</code>为负数,<code>len</code>为负数,或者<code>len</code>大于<code>b.length - off</code>。
* @see java.io.InputStream#read()
*/
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
/**
* 跳过并丢弃输入流中的<code>n</code>个字节的数据。跳过的实际字节数将被返回。
* 如果<code>n</code>为负数,则<code>skip</code>方法总是返回0,不会跳过任何字节。子类可以根据需要处理负值。
*
* <p><code>skip</code>方法会创建一个字节数组,并重复读取直到跳过了<code>n</code>个字节或者达到流的末尾。
* 子类可以提供更高效的实现方式,例如依赖于寻址能力。
*
* @param n 要跳过的字节数。
* @return 实际跳过的字节数。
* @exception IOException 如果流不支持寻址,或者发生其他I/O错误。
*/
public long skip(long n) throws IOException {
long remaining = n;
int nr;
if (n <= 0) {
return 0;
}
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
byte[] skipBuffer = new byte[size];
while (remaining > 0) {
nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
if (nr < 0) {
break;
}
remaining -= nr;
}
return n - remaining;
}
/**
* 返回从此输入流中可以读取(或跳过)而不会阻塞的字节数的估计值。
* 下一次调用此输入流的方法可能是同一个线程或另一个线程。
* 一次读取或跳过的字节数不会阻塞,但可能读取或跳过的字节数较少。
*
* <p>注意,虽然一些<code>InputStream</code>的实现可能会返回流中的总字节数,但很多实现并不会这样做。
* 使用该方法的返回值来分配一个缓冲区以容纳流中的所有数据是错误的。
*
* <p>子类的实现可以选择在输入流已经关闭的情况下抛出<code>IOException</code>。
*
* <p><code>available</code>方法对于<code>InputStream</code>类总是返回<code>0</code>。
*
* <p>子类应该重写此方法。
*
* @return 可以读取(或跳过)而不会阻塞的字节数的估计值,或者在到达输入流的末尾时返回<code>0</code>。
* @exception IOException 如果发生I/O错误。
*/
public int available() throws IOException {
return 0;
}
/**
* 关闭此输入流并释放与流关联的任何系统资源。
*
* <p><code>InputStream</code>的<code>close</code>方法不执行任何操作。
*
* @exception IOException 如果发生I/O错误。
*/
public void close() throws IOException {}
/**
* 标记此输入流的当前位置。随后调用<code>reset</code>方法可以将流重新定位到最后标记的位置,
* 以便后续读取可以重新读取相同的字节。
*
* <p><code>readlimit</code>参数告诉此输入流在标记位置失效之前允许读取多少个字节。
*
* <p><code>mark</code>的一般约定是,如果方法<code>markSupported</code>返回<code>true</code>,
* 则流以某种方式记住在调用<code>mark</code>之后读取的所有字节,并且在每次调用<code>reset</code>时准备好再次提供这些相同的字节。
* 然而,如果在调用<code>reset</code>之前从流中读取了超过<code>readlimit</code>个字节,流不需要记住任何数据。
*
* <p>关闭的流的标记不应该产生任何影响。
*
* <p><code>InputStream</code>的<code>mark</code>方法不执行任何操作。
*
* @param readlimit 在标记位置变为无效之前可以读取的最大字节数。
* @see java.io.InputStream#reset()
*/
public synchronized void mark(int readlimit) {}
/**
* 将此流重新定位到调用<code>mark</code>方法时的位置。
*
* <p><code>reset</code>的一般约定是:
*
* <ul>
* <li> 如果方法<code>markSupported</code>返回<code>true</code>,则:
*
* <ul><li> 如果自从创建流以来未调用<code>mark</code>方法,
* 或者自从上次调用<code>mark</code>方法后读取的字节数大于那次调用时的参数值,
* 则可能抛出<code>IOException</code>。
*
* <li> 如果没有抛出<code>IOException</code>,则流将重置为某个状态,
* 以便再次调用<code>read</code>方法时提供自最近调用<code>mark</code>方法以来读取的所有字节
* (或自文件开始以来读取的所有字节),接下来应该是作为调用<code>reset</code>时的下一个输入数据的任何字节。 </ul>
*
* <li> 如果方法<code>markSupported</code>返回<code>false</code>,则:
*
* <ul><li> 调用<code>reset</code>可能会抛出<code>IOException</code>。
*
* <li> 如果没有抛出<code>IOException</code>,则流将重置为一个固定的状态,
* 具体取决于特定类型的输入流及其创建方式。
* 将提供给后续调用<code>read</code>方法的字节取决于特定类型的输入流。 </ul></ul>
*
* <p><code>InputStream</code>类的<code>reset</code>方法不执行任何操作,只会抛出<code>IOException</code>。
*
* @exception IOException 如果未标记此流或者标记已失效。
* @see java.io.InputStream#mark(int)
* @see java.io.IOException
*/
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
/**
* 测试此输入流是否支持<code>mark</code>和<code>reset</code>方法。
* <code>mark</code>和<code>reset</code>方法是否受支持是特定输入流实例的不变属性。
* <code>InputStream</code>类的<code>markSupported</code>方法返回<code>false</code>。
*
* @return 如果此流实例支持mark和reset方法,则返回<code>true</code>;否则返回<code>false</code>。
* @see java.io.InputStream#mark(int)
* @see java.io.InputStream#reset()
*/
public boolean markSupported() {
return false;
}
}