Java 学习笔记 - IO篇:常见的IO流Stream以及相互关系

Posted 笑虾

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 学习笔记 - IO篇:常见的IO流Stream以及相互关系相关的知识,希望对你有一定的参考价值。

Java 学习笔记 - IO篇:常见的【IO流Stream】以及相互关系

1. 常见流

分类字节输入流字节输出流字符输入流字符输出流
流-抽象基类InputStreamOutputStreamReaderWriter
节点流(文件流)FileInputStream
RandomAccessFile
FileOutputStream
RandomAccessFile
FileReaderFileWriter
节点流(数组流)ByteArrayInputStreamByteArrayOutputStreamCharArrayReaderCharArrayWriter
节点流(管道流PipedInputStreamPipedOutputStreamPipedReaderPipedWriter
节点流(字符流)  StringReaderStringWriter
🌘🌗🌖🌔🌒
处理流-抽象基类FilterInputStreamFilterOutputStreamFilterReaderFilterWriter
包装流(缓冲流)BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter
转换流InputStreamReaderOutputStreamWriter
数据流DataInputStreamDataOutputStream 
打印流 PrintStream PrintWriter
对象流ObjectInputStreamObjectOutputStream  
序列流SequenceInputStream   
ParsingPushbackInputStream
StreamTokenizer
 PushbackReader
LineNumberReader
 
  • Deprecated 官方已宣布废弃的流
名称说明
StringBufferInputStream该类不能正确地将字符转换为字节。从JDK 1.1开始,从字符串创建流的方法官方推荐 StringReader 类。

1.1 流的分类

分类方式说明
OOP层面流的抽象基类:作为所有流的抽象基类,定义操作的API规范;
处理流抽象基类:所有方法直接调用父类。从类继承结构上区分节点流处理流,所有处理流应当继承它,然后以装饰器模式封装那个我们相要扩展其功能的
实现流:实现具体功能,比如各种节点流处理流
按方向输入流:将数据读进内存;
输出流:从内存将数据向外写出到目标位置。如:磁盘、网络。
按数据类型字节流:读写字节数据;
字符流:读写字符数据。(底层还是字节流,将字节数据按特定字符集编码解析后就是字符了)
按功能责任节点流:从特定数据源(创建流)读写数据;
处理流:从处理流抽象基类 可以得知,所有处理流的工作就是为其它流扩展功能。并且从他们的构造函数可以看出,它们都能接受最顶层 流的抽象基类 的类型。里式替换:所有流都能往里丢。

1.1.1 节点流

  • 文件流:从文件生成流。
  • 随机读写文件流:从文件生成流,并且能任意指定读写位置。(当初为何译成随机
  • 数组流:从数组生成流。
  • 管道流:从管道生成流。
  • 字符流:其源为一个字符串的字符流。为字符串提供流式读写的能力。

1.1.2 处理流

  • 包装流(缓冲流):封装抽象基类流实现
    1. 默认带一个8192字节字符数组作为缓冲区
    2. 继承处理流-抽象基类,并且包装一个流-抽象基类的实现类。
    3. 因为有缓冲区,获得mark、reset能力。
  • 转换流:把字节流封装为字符流,此过程可以指定字符集
  • -------------------------------------------------------------------------------
  • 数据流:可以将Java基础类型数据向流进行读取写入。
  • 打印流:增强其他输出流,提供格式化的能力。此流不抛异常,可.checkError()查错误状态。
  • 对象流:Java基本数据和对象序列化、反序列化。需要实现

1.1.3 流的特点小结

  1. 从流的名字可以看出:大多数流有方向。很多流是成对出现的,它们经常会配合使用。
  2. 从设计角度看,类之间通过装饰器模式层层嵌套增强功能。
  3. 覆盖 or 追加,通常是输出流的构造函数中用一个boolean append来控制。
  4. RandomAccessFile 因为可读可写,所以需要构造时传入mode
  5. 常规读字节我们一般用BufferedInputStream。底层是FileInputStream
FileInputStream fis = new FileInputStream("E:\\\\in.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
  1. 常规读字符我们一般用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. 流的基本操作

读操作

流-抽象基类InputStreamorOutputStream定义的标准读操作所有读取流都支持。
其他处理流,不过是在此基础上提供针对特定操作的便捷方法。比如:ObjectInputStream支持按类型读取变量。BufferedReader支持按行读取

操作说明
abstract int read()从输入流的数据中读取下一个字节/字符。返回实际读取到的数据
int read(T[] b)从输入流中读取一定数量的字节/字符,并将其存储在缓冲区数组 b 中。返回实际读取字节数
int read(T[] b, int off, int len)从输入流中读取最多 len字节/字符数组off位置开始存储。返回实际读取字节数
  1. 正常情况就是填满数组,当所剩数据不足填满一个数组 b时就有多少放多少。
  2. 到达流末尾返回-1,这也是我们循环读取时常用的退出条件
  3. 当读取了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
  1. write 的常规协定是:向输出流写入一个字节。写入参数 b 的八个低位忽略参数 b 的24 个高位
  2. 如果 b == null,则抛出 NullPointerException
  3. 如果 off 为负,或 len 为负,或者 off+len > b.length,则抛出 IndexOutOfBoundsException

跳过指定长度开始读

操作说明
long skip(long n)跳过和丢弃此输入流中数据的 n字节/字符

mark、reset 操作

mark、reset 是针对缓冲区来工作的,所以只有存在缓冲区的流才可能支持此操作。可以通过markSupported()检测。
我们都听过只能读一次。但是有了缓冲区配合mark、reset就可以反复读取了。(其实就是在缓冲区中折腾)
比如:加载一个文件流时,先mark一下,然后读它的文件头做检测,做完判断后需要reset一下,此完璧依旧了。

操作说明
boolean markSupported()测试此输入流是否支持 markreset 方法。
void mark(int readlimit)在此输入流中标记当前的位置。
void reset()将此流重新定位最后一次对此输入流调用 mark 方法时的位置。

检测

操作说明
int available()返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节/字符数。

关闭

操作说明
void close()关闭此输入流并释放与该流关联的所有系统资源。
关闭外层流会自动关闭它包装的内层流
其实Java7之后,建议直接放 try(Closeable) 里。
  1. 注意:有些流即使调用了 close() 它的底层流也不会关闭。具体使用的时候自己留意看一眼。

3. 流示例

  • 测试文件位置
public static final String path = "E:\\\\test.txt";

文件流

1. FileInputStream、FileOutputStream

FileInputStreamFileOutputStream

文件流:读写文件。

  1. 从文件系统的文件获取输入字节。哪些文件可用取决于主机环境。用于读取原始字节流,比如图像数据。对于读取字符流,考虑使用FileReader。
  2. getChannel() 可返回与此文件输入流有关的唯一 FileChannel 对象。NIO另外讲。

2. RandomAccessFile

RandomAccessFile

文件随机访问流:(随机的意思是指可以指定任意位置进行访问)

  1. 同时具有读写能力,当然在调用构造函数时,需要自己指定读写模式mode
    1.1. "r" :只读模式。任何 写操作都将抛 IOException
    1.2. "rw":读写模式。如果该文件尚不存在,则尝试创建该文件。
    1.3. "rwd" :读写模式。并要求对内容的每个更新都同步写入到底层存储设备。
    1.4. "rws" :读写模式。并要求对内容元数据的每个更新都同步写入到底层存储设备。
  2. 除了基本读写功能,另外增加了按类型读取的能力。
  3. 既然能力出重,行为怪点也能理解,所以它直接继承自Object ,并且与上面的FileXXXStream 不同,它还实现了 DataInput, DataOutput
  4. 它通过自由移动文件指针,实现从随机位置开始读写的能力。为此它也多了一些方法来实现目的:
    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
  5. 正常情况下,每读写多少字节数据,文件指针会自动向前移动多少。
  6. 其他按类型进行读写的方法无需赘述。关于 nio 的部分见:[Java 学习笔记 - NIO篇](javascript::alert(‘aaaa’))
  7. 下面我们读取一个位图文件数据。把原本的红色,改成原谅色。相关信息参考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数组,算不算是个场景。
这家伙不需要关闭,自己也没异常。

  1. ByteArrayInputStream 还是基本的读写。
  2. 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 |

  1. CharArrayReader 还是基本的读写。
  2. CharArrayWriter 增加了:
    2.1. append(char c) :将指定字符添加到此 writer。(另外还有两个重载版本)
    2.2. toCharArray() :返回输入数据的副本。
    2.3. toString():将输入数据转换为字符串。
    2.4. writeTo(Writer out) :将缓冲区的内容写入另一个字符流。
略。。。

管理流

1. PipedInputStream、PipedOutputStream

PipedInputStream | PipedOutputStream |

字节管道流:主要用途是链接管道两端,进行通信。应用场景:实现线程间通信。

  1. PipedInputStream 除基本的读取外,增加了:
    1.1. connect(PipedOutputStream src):用于链接管道输出流
    1.2. receive(int b):接收数据字节。
  2. 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 | PipedWriter |

字符管道流:道理差不多。

  1. PipedReader 除基本的读取外,增加了:
    1.1. connect(PipedWriter src):用于链接PipedWriter
    1.2. ready():用于判断是否已有数据可读。返true下次读取不阻塞,返回false不保证。
  2. 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 |

字节缓冲流: 提供缓冲区,优化性能。

  1. BufferedInputStream 除了构造函数上多个了参数,允许指定缓冲区大小。基本读取。
  2. BufferedOutputStream 除了构造函数上多个了参数,允许指定缓冲区大小。基本写入。

2. BufferedReader、BufferedWriter

BufferedReader | BufferedWriter |

字符缓冲流: 提供缓冲区,优化性能。

  1. BufferedReader :构造函数上多个了参数,允许指定缓冲区大小。除基本读取外:
    1.1. readLine():按行读取文本。
  2. BufferedWriter:构造函数上多个了参数,允许指定缓冲区大小。除基本读取外:
    2.1. newLine():写入一个行分隔符。

数据流

DataInputStream、DataOutputStream

DataInputStream | DataOutputStream |

数据流: 支持按java基本类型读写数据。

  1. DataInputStream:除支持基本读取外,还支持各种类型读取
  2. 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

ObjectInputStream

  1. ObjectOutputStream:能将 javaBean对象(序列化)写入给定输出流
  2. 被写入的 javaBean 需要实现Serializable 接口。
  3. 对象输出流writeObject() 方法会尝试调用 javaBean 中的 writeObject(ObjectOutputStream out) 方法。
  4. javaBeanwriteObject(ObjectOutputStream out) 中可用 out.putFields()获取对象属性并设置。

2. ObjectOutputStream

ObjectOutputStream

  1. ObjectInputStream:可以反序列化 ObjectInputStream 输出的对象。
  2. 对象输入流readObject() 方法会尝试调用 javaBean 中的 readObject(ObjectInputStream in) 方法。
  3. javaBeanreadObject(ObjectInputStream in) 中可用 in.readFields()获取原对象属性并设置当前对象属性。

序列流

SequenceInputStream

序列流:是合并文件的一把好手。

把一张位图按每份1MB大小拆分成多份,再用SequenceInputStream合并回来。

  • 拆分文件
@Test
public void splitFileTest() throws IOException 
    String path = "e:\\\\test.bmp";
    try Java 学习笔记 - IO篇:对象流 ObjectInputStreamObjectOutputStream

Java 学习笔记 - IO篇:对象流 ObjectInputStreamObjectOutputStream

Java 学习笔记 - IO篇:读写文本文件txt

Java的第一遍学习笔记 IO流

Java基础——iO

Java IO流 学习笔记