Java 学习笔记 - IO篇:常见的IO流Stream以及相互关系
Posted 笑虾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 学习笔记 - IO篇:常见的IO流Stream以及相互关系相关的知识,希望对你有一定的参考价值。
Java 学习笔记 - IO篇:常见的【IO流Stream】以及相互关系
1. 常见流
- Deprecated 官方已宣布废弃的流
名称 | 说明 |
---|---|
StringBufferInputStream | 该类不能正确地将字符转换为字节。从JDK 1.1开始,从字符串创建流的方法官方推荐 StringReader 类。 |
1.1 流的分类
分类方式 | 说明 |
---|---|
OOP层面 | 流的抽象基类 :作为所有流 的抽象基类 ,定义流 操作的API 规范;处理流抽象基类 :所有方法直接调用父类。从类继承结构上区分节点流 与处理流 ,所有处理流 应当继承它,然后以装饰器 模式封装那个我们相要扩展其功能的流 。实现流 :实现具体功能,比如各种节点流 ,处理流 。 |
按方向 | 输入流 :将数据读进内存;输出流 :从内存将数据向外写出到目标位置。如:磁盘、网络。 |
按数据类型 | 字节流 :读写字节数据;字符流 :读写字符数据。(底层还是字节流,将字节数据按特定字符集编码解析后就是字符了) |
按功能责任 | 节点流 :从特定数据源(创建流 )读写数据;处理流 :从处理流抽象基类 可以得知,所有处理流 的工作就是为其它流 扩展功能。并且从他们的构造函数 可以看出,它们都能接受最顶层 流的抽象基类 的类型。里式替换:所有流都能往里丢。 |
1.1.1 节点流
文件流
:从文件
生成流。随机读写文件流
:从文件
生成流,并且能任意指定读写位置。(当初为何译成随机
)数组流
:从数组
生成流。管道流
:从管道
生成流。字符流
:其源为一个字符串
的字符流。为字符串提供流式读写
的能力。
1.1.2 处理流
包装流(缓冲流)
:封装抽象基类流
的实现
。- 默认带一个
8192
的字节
或字符
数组作为缓冲区
。 - 继承
处理流-抽象基类
,并且包装一个流-抽象基类
的实现类。 - 因为有缓冲区,获得
mark、reset
能力。
- 默认带一个
转换流
:把字节流
封装为字符流
,此过程可以指定字符集
。- -------------------------------------------------------------------------------
数据流
:可以将Java基础类型数据
向流进行读取写入。打印流
:增强其他输出流,提供格式化的能力。此流不抛异常,可.checkError()
查错误状态。对象流
:Java基本数据和对象序列化、反序列化。需要实现
1.1.3 流的特点小结
- 从流的名字可以看出:
大多数流有方向
。很多流是成对出现
的,它们经常会配合使用。 - 从设计角度看,类之间通过
装饰器模式
层层嵌套增强功能。 覆盖 or 追加
,通常是输出流
的构造函数中用一个boolean append
来控制。RandomAccessFile
因为可读可写
,所以需要构造时传入mode
- 常规
读字节
我们一般用BufferedInputStream
。底层是FileInputStream
。
FileInputStream fis = new FileInputStream("E:\\\\in.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
- 常规
读字符
我们一般用BufferedReader
。底层用FileReader
更方便,但也可以是FileInputStream
。中间用转换流InputStreamReader
转了一手。
FileInputStream fis = new FileInputStream(new File("E:\\\\in.txt"));
InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr);
2. 流的基本操作
读操作
流-抽象基类InputStream
orOutputStream
定义的标准读操作
所有读取流都支持。
其他处理流
,不过是在此基础上提供针对特定操作的便捷方法。比如:ObjectInputStream
支持按类型读取
变量。BufferedReader
支持按行读取
。
操作 | 说明 |
---|---|
abstract int read() | 从输入流的数据中读取 下一个字节/字符 。返回实际读取到的数据 。 |
int read(T[] b) | 从输入流中读取一定数量的字节/字符 ,并将其存储在缓冲区数组 b 中。返回实际读取 的字节数 。 |
int read(T[] b, int off, int len) | 从输入流中读取最多 len 个字节/字符 向数组 中off 位置开始存储。返回实际读取 的字节数 。 |
- 正常情况就是填满数组,当所剩数据不足填满一个
数组 b
时就有多少放多少。 - 到达流末尾返回
-1
,这也是我们循环读取
时常用的退出条件
。 - 当读取了
5byte
的数据填充长度为10byte
的数组 b
时,数组中后5个位置
还是保持历史数据不会变。(假设off=0
)
写操作
操作 | 说明 |
---|---|
abstract void write(int b) | 将指定的字节/字符 写入此输出流。 |
void write(byte[] b) | 将 b.length 个字节从指定的 字节/字符 数组写入此输出流。 |
void write(byte[] b, int off, int len) | 将指定 字节/字符 数组中从偏移量 off 开始的 len 个字节写入此输出流。 |
void flush() | 刷新 此输出流并强制写出 所有缓冲的字节/字符 。前提是要实现了此方法OutputStream |
- write 的常规协定是:向输出流写入一个字节。
写入
参数 b 的八个低位
。忽略
参数 b 的24 个高位
。 - 如果
b == null
,则抛出NullPointerException
。 - 如果
off 为负
,或len 为负
,或者off+len
>b.length
,则抛出IndexOutOfBoundsException
。
跳过指定长度开始读
操作 | 说明 |
---|---|
long skip(long n) | 跳过和丢弃此输入流中数据的 n 个字节/字符 。 |
mark、reset 操作
mark、reset
是针对缓冲区
来工作的,所以只有存在缓冲区
的流才可能支持此操作。可以通过markSupported()
检测。
我们都听过流
只能读一次
。但是有了缓冲区
配合mark、reset
就可以反复读取了。(其实就是在缓冲区中折腾)
比如:加载一个文件流时,先mark
一下,然后读它的文件头做检测,做完判断后需要reset
一下,此流
就完璧
依旧了。
操作 | 说明 |
---|---|
boolean markSupported() | 测试此输入流是否支持 mark 和 reset 方法。 |
void mark(int readlimit) | 在此输入流中标记当前的位置。 |
void reset() | 将此流重新定位 到最后一次 对此输入流调用 mark 方法时的位置。 |
检测
操作 | 说明 |
---|---|
int available() | 返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节/字符 数。 |
关闭
操作 | 说明 |
---|---|
void close() | 关闭此输入流并释放与该流关联的所有系统资源。 关闭 外层流 会自动关闭它包装的内层流 。其实 Java7 之后,建议直接放 try(Closeable) 里。 |
- 注意:有些流即使调用了
close()
它的底层流也不会关闭。具体使用的时候自己留意看一眼。
3. 流示例
- 测试文件位置
public static final String path = "E:\\\\test.txt";
文件流
1. FileInputStream、FileOutputStream
FileInputStream、FileOutputStream
文件流
:读写文件。
- 从文件系统的文件获取输入字节。哪些文件可用取决于主机环境。用于读取原始字节流,比如图像数据。对于读取字符流,考虑使用FileReader。
getChannel()
可返回与此文件输入流有关的唯一FileChannel
对象。NIO另外讲。
2. RandomAccessFile
文件随机访问流
:(随机的意思是指可以指定任意位置进行访问)
- 同时具有
读写
能力,当然在调用构造函数时,需要自己指定
读写模式mode
:
1.1."r"
:只读模式。任何 写操作都将抛IOException
。
1.2."rw"
:读写模式。如果该文件尚不存在,则尝试创建该文件。
1.3."rwd"
:读写模式。并要求对内容
的每个更新都同步写入到底层存储设备。
1.4."rws"
:读写模式。并要求对内容
或元数据
的每个更新都同步写入到底层存储设备。 - 除了基本
读写
功能,另外增加了按类型读取
的能力。 - 既然能力出重,行为怪点也能理解,所以它直接继承自Object ,并且与上面的
FileXXXStream
不同,它还实现了 DataInput, DataOutput。 - 它通过自由移动
文件指针
,实现从随机位置开始读写
的能力。为此它也多了一些方法来实现目的:
3.1. long getFilePointer( ):获取当前文件指针
的位置。
3.2. void seek(long pos ):设置文件指针
的位置。
3.3. long length():获取文件长度。
3.4. int skipBytes(int n):(文件指针)跳过n
个字节。如果已经到末尾也不勉强,返回实际跳过的字节数。
3.4. void setLength(long newLength):如果length < newLength
文件扩展(新增的部分未定义)否则length > newLength
多出的部分截断
。getFilePointer()
时filePointer > newLength ? newLength : filePointer
。 - 正常情况下,每
读写
多少字节数据,文件指针
会自动向前移动多少。 - 其他
按类型
进行读写
的方法无需赘述。关于nio
的部分见:[Java 学习笔记 - NIO篇](javascript::alert(‘aaaa’)) - 下面我们读取一个位图文件数据。把原本的红色,改成原谅色。相关信息参考BMP图像数据格式详解
@Test
public void RandomAccessFileTest() throws IOException
String path = "E:\\\\test.bmp";
RandomAccessFile raf = new RandomAccessFile(path, "rw");
// 像素数据偏移量的位置和长度查bitmap格式可得。
raf.seek(10); // 跳到偏移信息位置
byte[] offBits = new byte[4]; // 用来取值的数组
raf.readFully(offBits); // 取出像素数据偏移量
byte[] green = 0,(byte)255, 0; // 定义个原谅色
raf.seek(b2L(offBits)); // 跳到像素数据起始位置
while (raf.getFilePointer() < raf.length())
raf.write(green); // 遍历填充原谅色
public long b2L(byte[] bytes)
long l = 0L;
for (int i = 0; i < bytes.length; i++)
l |= (bytes[i] & 0xffffffffL) << i * Long.BYTES;
return l;
数组流
1. ByteArrayInputStream、ByteArrayOutputStream
ByteArrayInputStream | ByteArrayOutputStream |
字节数组流
:从byte数组
生成流,提供以流
的方式访问byte数组
的能力。
不知道应用场景,网上找不到。不知道以流的方式访问byte数组,算不算是个场景。
这家伙不需要关闭
,自己也没异常。
ByteArrayInputStream
还是基本的读写。ByteArrayOutputStream
增加了:
2.1.toString()
:使用平台默认的字符集,通过解码字节将缓冲区内容转换为字符串。
2.2.toString(String charsetName)
:使用指定字符集,解码字符串。
2.3.writeTo(OutputStream out)
:数据全部写入指定输出流中。
String str = "大家好,我是笨笨,笨笨的笨,笨笨的笨,谢谢!";
ByteArrayInputStream bais = new ByteArrayInputStream(str.getBytes());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int c;
while ((c = bais.read()) != -1)
baos.write(c);
System.out.println(baos.toString());
2. CharArrayReader、CharArrayWriter
字符数组流
:与上面类似,只不过是从字符数组
生成流,提供以流
的方式访问字符数组
的能力。
CharArrayReader | CharArrayWriter |
CharArrayReader
还是基本的读写。CharArrayWriter
增加了:
2.1.append(char c)
:将指定字符添加到此 writer。(另外还有两个重载
版本)
2.2.toCharArray()
:返回输入数据的副本。
2.3.toString()
:将输入数据转换为字符串。
2.4.writeTo(Writer out)
:将缓冲区的内容写入另一个字符流。
略。。。
管理流
1. PipedInputStream、PipedOutputStream
PipedInputStream | PipedOutputStream |
字节管道流
:主要用途是链接管道两端,进行通信。应用场景:实现线程间通信。
PipedInputStream
除基本的读取外,增加了:
1.1.connect(PipedOutputStream src)
:用于链接管道输出流
。
1.2.receive(int b)
:接收数据字节。PipedOutputStream
除基本的写外,增加了:
2.1.connect(PipedInputStream snk)
:用于链接管道输入流
。
public class PipedInputOutputStreamTest
public static PipedInputStream pipIn = new PipedInputStream();
public static PipedOutputStream pipOut = new PipedOutputStream();
@Test
public void PipedStreamTest() throws IOException, InterruptedException
pipIn.connect(pipOut);
CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(()->
try
for (int i = 0; i < 10; i++)
pipOut.write("大家好,我是笨笨,笨笨的笨,笨笨的笨,谢谢!".getBytes());
TimeUnit.MILLISECONDS.sleep(200);
countDownLatch.countDown();
catch (Exception e) e.printStackTrace();
).start();
new Thread(()->
try
byte[] arr = new byte[66];
for (int i = 0; i < 10; i++)
pipIn.read(arr);
System.out.println(i + ":" + new String(arr, "utf-8"));
countDownLatch.countDown();
catch (Exception e) e.printStackTrace();
).start();
countDownLatch.await();
2. PipedReader、PipedWriter
字符管道流
:道理差不多。
PipedReader
除基本的读取外,增加了:
1.1.connect(PipedWriter src)
:用于链接PipedWriter
。
1.2.ready()
:用于判断是否已有数据可读。返true
下次读取不阻塞,返回false
不保证。PipedWriter
除基本的写外,增加了:
2.1.append(char c)
:将指定字符添加到此 writer。(另外还有两个重载
版本)
2.2.checkError()
:此流不会抛异常,所以可以用此方法检查。(看到有这种方法的,多半不会抛异常)
2.3.format(String format, Object... args)
:支持格式化输出。(另外还有一个重载
版本)
2.4. 其他就是各种类型专用的println
重载了。
略。。。
包装流
1. BufferedInputStream、BufferedOutputStream
BufferedInputStream | BufferedOutputStream |
字节缓冲流
: 提供缓冲区,优化性能。
BufferedInputStream
除了构造函数上多个了参数,允许指定缓冲区大小。基本读取。BufferedOutputStream
除了构造函数上多个了参数,允许指定缓冲区大小。基本写入。
2. BufferedReader、BufferedWriter
BufferedReader | BufferedWriter |
字符缓冲流
: 提供缓冲区,优化性能。
BufferedReader
:构造函数上多个了参数,允许指定缓冲区大小。除基本读取外:
1.1.readLine()
:按行读取文本。BufferedWriter
:构造函数上多个了参数,允许指定缓冲区大小。除基本读取外:
2.1.newLine()
:写入一个行分隔符。
数据流
DataInputStream、DataOutputStream
DataInputStream | DataOutputStream |
数据流
: 支持按java基本类型
读写数据。
DataInputStream
:除支持基本读取外,还支持按
各种类型读取
。DataOutputStream
:除支持基本写入外,还支持按
各种类型写入
。
@Test
public void DataOutputStreamTest()
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(path)))
byte b = 1; short s = 2; int i = 3; long l = 4;
float f = 5.5f; double d = 6.6; boolean bl = true; char c = '笨';
dos.writeByte(b);dos.writeShort(s);dos.writeInt(i);dos.writeLong(l);
dos.writeFloat(f);dos.writeDouble(d);dos.writeBoolean(bl);dos.writeChar(c);
catch (Exception e) e.printStackTrace();
@Test
public void DataInputStreamTest()
try (DataInputStream dis = new DataInputStream(new FileInputStream(path)))
byte b = dis.readByte(); short s = dis.readShort(); int i = dis.readInt(); long l = dis.readLong();
float f = dis.readFloat(); double d = dis.readDouble(); boolean bl = dis.readBoolean(); char c = dis.readChar();
System.out.println(b); System.out.println(s); System.out.println(i); System.out.println(l);
System.out.println(f); System.out.println(d); System.out.println(bl); System.out.println(c);
catch (Exception e)
e.printStackTrace();
对象流
对象流
:对实现Serializable
接口的对象进行序列化、反序列化。
1. ObjectInputStream
ObjectOutputStream
:能将javaBean
对象(序列化)写入给定输出流
- 被写入的
javaBean
需要实现Serializable
接口。 对象输出流
的writeObject()
方法会尝试调用javaBean
中的writeObject(ObjectOutputStream out)
方法。- 在
javaBean
的writeObject(ObjectOutputStream out)
中可用out.putFields()
获取对象属性并设置。
2. ObjectOutputStream
ObjectInputStream
:可以反序列化ObjectInputStream
输出的对象。对象输入流
的readObject()
方法会尝试调用javaBean
中的readObject(ObjectInputStream in)
方法。- 在
javaBean
的readObject(ObjectInputStream in)
中可用in.readFields()
获取原对象
属性并设置当前对象
属性。
序列流
SequenceInputStream
序列流
:是合并文件的一把好手。
把一张位图
按每份1MB
大小拆分成多份,再用SequenceInputStream
合并回来。
- 拆分文件
@Test
public void splitFileTest() throws IOException
String path = "e:\\\\test.bmp";
try Java 学习笔记 - IO篇:对象流 ObjectInputStreamObjectOutputStream