概念
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即流的本质是数据传输。
IO流的分类
IO流基础四大类:InputStream/OutputStream ,Reader/Writer
对这四类及其子类分类如下:
根据处理数据类型的不同分为:字节流与字符流
根据数据流向的不同分为:输入流与输出流
根据是否直接处理数据:节点流与处理流/实体流与装饰流
流式部分
java.io包中除了以上四大类(流式部分),还包括File,RandomAccessFile类与Serializable接口(非流式部分—辅助流式)。
//如果不同语法糖一定要记得close!!!!!
节点流与处理流
节点流
直接对数据进行操作,可使用文件路径/File类进行构造
按照操作对象的不同又分为:
文件流:FileInputStream,FileOutputStrean,FileReader,FileWriter,它们都会直接操作文件,直接与 OS 底层交互。因此他们被称为节点流 ,注意:使用这几个流的对象之后,需要关闭流对象,因为 java 垃圾回收器不会主动回收。不过在 Java7 之后,可以在 try() 括号中打开流,最后程序会自动关闭流对象,不再需要显示地 close。
数组流:ByteArrayInputStream,ByteArrayOutputStream,CharArrayReader,CharArrayWriter,对数组进行处理的节点流。
字符串流:StringReader,StringWriter,其中 StringReader 能从 String 中读取数据并保存到 char 数组。
管道流:PipedInputStream,PipedOutputStream,PipedReader,PipedWrite,对管道进行处理的节点流,主要用于线程通信
处理流
处理流是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写,其构造方法总是要带一个其他的流对象做参数。
常用处理流(通过关闭处理流里面的节点流来关闭处理流):
缓冲流 :BufferedInputStream,BufferedOutputStream,BufferedReader ,BufferedWriter,需要父类作为参数构造,增加缓冲功能,避免频繁读写硬盘,可以初始化缓冲数据的大小,由于带了缓冲功能,所以就写数据的时候需要使用 flush 方法,另外,BufferedReader 提供一个 readLine( ) 方法可以读取一行,而 FileInputStream 和 FileReader 只能读取一个字节或者一个字符,因此 BufferedReader 也被称为行读取器。
转换流:InputStreamReader,OutputStreamWriter,要 inputStream 或 OutputStream 作为参数,实现从字节流到字符流的转换,我们经常在读取键盘输入(System.in)或网络通信的时候,需要使用这两个类。
数据流:DataInputStream,DataOutputStream,提供将基础数据类型写入到文件中,或者读取出来。
字节流与字符流
联系:字节流是字符流的基础,字符流就是基于字节流的读写时去查指定的码表(默认编码为GBK);转换流就是通过根据相应码表编码解码实现字节流字符流相互转换的。
区别:
(1)处理对象
字节流可以处理所有数据,如图片,音频等;
字符流只能处理字符;
(2)读取单位
字节流以字节(8bit)为单位;
字符流以字符为单位,根据码表映射字符,一次可能读多个字节;
(3)有无缓冲区
字节流没有缓冲区,是直接输出的。字节流不调用colse()方法时,信息就已经输出了;
字符流是输出到缓冲区的。只有在调用close()方法关闭缓冲区时,信息才输出。要想字符流在未关闭时输出信息,则需要手动调用flush()方法,我们将在接下来的代码中看到这点;
如何选择:
处理纯文本,首选字符流,其他情况字节流
流分类图结构
InputStream -(输入字节流)
是所有输入字节流的祖先,是抽象类(有一个抽象方法read()),实现了Closeable接口
常用方法:
方法 | 使用 |
---|---|
public abstract int read() throws IOException | 由子类实现, 从输入流中读取数据的下一个字节,返回0到255范围内的int字节值,所以只支持ASCII码范围内的读取,中文字符会出现乱码(一个中文字符占两个字节)如果没有可用的字节,则返回-1。 |
public int read(byte b[]) throws IOException{return read(b, 0, b.length);} | 从输入流中读取一定数量的字节,将其存储在缓冲区数组 b 中。返回实际读取的字节数。如果 b 的长度为 0,则不读取任何字节并返回0;如果没有可用的字节,返回 -1;否则,至少读取一个字节并将其存储在 b 中。 |
public int read(byte b[], int off, int len) throws IOException | 返回0,-1的情况和上个一样,只不过字节从b数组的第off开始存,一次读取len个字节 |
public byte[] readAllBytes() throws IOException | 读取全部字节 |
public long skip(long n) throws IOException | 跳过n字节 |
public void close() throws IOException {} | 释放资源 |
public synchronized void mark(int readlimit) | 在当前位置做标记 |
public synchronized void reset() throws IOException | 返回标记处 |
子类特点:
子类 | 使用 |
---|---|
ByteInputStream | 构造方法有两种,一种为ByteArrayInputStream(byte buf[]),从数组头开始读,一种为ByteArrayInputStream(byte buf[], int offset, int length),从offset开始,最多读length长 |
FileInputStream | 用String(文件路径)或File对象构造,其实使用String本质还是实例了一个File |
– | – |
标准输入,Byte数组输入,文件输入
class test {
public static void main(String[] args) {
// 1,System.in为内置InputStream对象,默认标准输入为控制台//.out为内置OutputStream
try {
System.out.println((char) System.in.read());
} catch (IOException e) {
e.printStackTrace();
}
// 2,从Byte数组读取数据
try (InputStream istream = new ByteArrayInputStream("byteInput".getBytes());) {
byte[] b = istream.readAllBytes();
System.out.println(new String(b));// 注意
// 如果使用toString返回的是类签名
// 因为byte[]继承自Object并且没有实现toString方法
} catch (IOException e) {
e.printStackTrace();
}
// 3,从文件中读取数据
// 方法一
try (InputStream iStream = new FileInputStream("src//text1.txt");) {
byte[] b = new byte[1024];
int len = iStream.read(b);// 返回字节长度
System.out.println(new String(b, 0, len));// 这样做是为了防止多余空格的出现
} catch (IOException e) {
e.printStackTrace();
}
// 方法二,利用File类的length得到文件大小
try (InputStream iStream = new FileInputStream(new File("src//text1.txt"))) {
byte[] b = new byte[(int) new File("src//text1.txt").length()];// length返回值为long类型,需要强制转换
iStream.read(b);
System.out.println(new String(b));
} catch (IOException e) {
e.printStackTrace();
}
}
}
DataInputStream类
构造:
(1)构造方法可分为两类,一是利用InputStream实例,二是利用byte数组
(2)允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型,实现了基本数据类型的读取
关于readInt/Char输出值与期望不相符的问题:
以readInt为例,
输入12,控制台输出825363722 12
原因:
(1)输入其实为:字符’1’、字符’2’、回车、换行,对应的ASCII码是 49 50 13 10
(2)int类型是4个字节的 , 所以 readInt()从流里读出4个字节做位移运算,
(3)一个字节有8位,所以运算过程为492^24 + 502^16 + 13*2^8 + 10 = 825363722
(4)同理,直接在txt文件中存入数字,再读出也存在同样的问题
(5)要"正常"读出数字,存在字符串二进制转为int的问题,或者从二进制文件中读取
(6)readChar存在同样的问题,该方法从二进制流中读取两个字节来,返回的是字符类型的数(char)。设a 为第一个读取字节,b 为第二个读取字节。返回的值度是:(char)((a << 8) | (b & 0xff))(读txt也不正常,读dat正常)
try (DataInputStream iStream = new DataInputStream(System.in);
DataOutputStream oStream = new DataOutputStream(new FileOutputStream("src//text3.dat"));
DataInputStream iStream2 = new DataInputStream(new FileInputStream("src//text3.dat"));) {
oStream.writeInt(12);
System.out.println(iStream.readInt());
System.out.println(iStream2.readInt());
} catch (IOException e) {
e.printStackTrace();
}
readLine方法:
System.out.println(iStream.readLine());
BufferedInputStream类
又称缓冲流,特点就是提供了一个缓冲数组,每次调用read方法的时候,它首先尝试从缓冲区里读取数据,若读取失败(缓冲区无可读数据),则选择从物理数据源(譬如文件)读取新数据(这里会尝试尽可能读取多的字节)放入到缓冲区中,最后再将缓冲区中的内容部分或全部返回给用户.由于从缓冲区里读取数据远比直接从物理数据源(譬如文件)读取速度快。
主要方法:
//构造方法
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)
//下一字节是否可读
synchronized int available()
//关闭
void close()
//标记, readlimit为mark后最多可读取的字节数
synchronized void mark(int readlimit)
//是否支持mark, true
boolean markSupported()
//读取一个字节
synchronized int read()
//读取多个字节到b
synchronized int read(byte[] b, int off, int len)
//重置会mark位置
synchronized void reset()
//跳过n个字节
synchronized long skip(long n)
实例:
public void testBufferedInput() {
try {
/**
* 建立输入流 BufferedInputStream, 缓冲区大小为8
* buffer.txt内容为
* abcdefghij
*/
InputStream in = new BufferedInputStream(new FileInputStream(new File("buff.txt")), 8);
/*从字节流中读取5个字节*/
byte [] tmp = new byte[5];
in.read(tmp, 0, 5);
System.out.println("字节流的前5个字节为: " + new String(tmp));
/*标记测试*/
in.mark(6);
/*读取5个字节*/
in.read(tmp, 0, 5);
System.out.println("字节流中第6到10个字节为: " + new String(tmp));
/*reset*/
in.reset();
System.out.printf("reset后读取的第一个字节为: %c" , in.read());
} catch (Exception e) {
e.printStackTrace();
}
}
输出:
字节流的前5个字节为: abcde
字节流中第6到10个字节为: fghij
reset后读取的第一个字节为: f
OuputStream - (输出字节流)
OutputStream为抽象类,有一个抽象方法void write(int b)
方法 | 使用 |
---|---|
public abstract void write(int b) throws IOException | 写入一个字节,可以看到这里的参数是一个 int 类型,对应上面的读方法,int 类型的 32 位,只有低 8 位才写入(0~255)高位将舍弃 |
public void write(byte b[]) throws IOException | 将数组b所有数据写入,实际调用的是下面这个方法 |
public void write(byte b[], int off, int len) throws IOException | 从off开始写入len个字节 |
public void flush() throws IOException | 强制刷新,将缓冲中的数据写入 |
public void close() throws IOException | 关闭输出流,流被关闭后就不能再输出数据了 |
ByteArrayOutputStream类
字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中;
无参构造方法创建一个大小为32的byte数组,有参数n的构造方法创建大小为n的byte数组;
public String toString()用于将缓冲区的内容转换为字符串,根据平台的默认字符编码将字节转换成字符。
try (OutputStream oStream = new ByteArrayOutputStream();) {
oStream.write("123".getBytes());
System.out.println(oStream.toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
标准输出和文件输出
// 1,System.out为OutputStream类
try {
System.out.write("123".getBytes(), 1, 1);
System.out.write('2');// 注意
System.out.flush();// 如果没有这句,第二个2不会输出,因为存在缓冲区内
} catch (Exception e) {
// 此时异常不仅包括IO异常
e.printStackTrace();
}
// 2,FileOutputStream输出到文件
//构造方法第二个参数为是否为追加,默认为false(默认情况下文件原内容被清空)
try (OutputStream oStream = new FileOutputStream("src//text1.txt", true);) {
// 如果不用try-with-resource语法糖记得close
oStream.write("123".getBytes());
} catch (Exception e) {
// 此时可能出现的异常有两种
// FileNotFoundException,IOException
e.printStackTrace();
}
DataOutputStream
和DataInputStream对应,他的方法除了继承自父类的write,flush,close外,还封装了基础数据类型和String的输出
public final void writeBytes(String s) throws IOException
//将字符串以字节序列写入到底层的输出流,字符串中每个字符都按顺序写入,并丢弃其高八位。
值得注意的一点是JAVA中的char是16位的,一个char存储一个中文字符,直接用writeBytes方法转换会变为8位,直接导致高8位丢失。从而导致中文乱码,解决方法为先使用getBytes转换为字节,再用write方法
try (DataOutputStream oStream = new DataOutputStream(System.out);) {
oStream.writeBytes("中");// 会出现乱码
oStream.write("中".getBytes());// 解决乱码问题
} catch (IOException e) {
e.printStackTrace();
}
BufferedOutputStream
基本方法和父类一样,不同的是带缓冲区
public static void main(String[] args) throws Exception {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt")) ;
bos.write("hello".getBytes());
bos.close();
}
Reader -(输入字符流)
Reader 是所有的输入字符流的父类,它是一个抽象类,子类继承他必须实现read(char[], int, int)与close方法。
常用方法:
方法 | 使用 |
---|---|
public int read() throws IOException | 读取单个字符,返回该字符的ASCII |
public int read(char cbuf[]) throws IOException | 读取字符到指定的 char 数组中 |
public abstract int read(char cbuf[], int off, int len) throws IOException | 从off,读len长 |
public long skip(long n) throws IOException | 跳过n字节 |
public abstract void close() throws IOException | 释放资源 |
char数组读取,文件读取
// 字符读取的时候就不会出现中文乱码问题
// 1,从char数组读取
try (Reader reader = new CharArrayReader("中文".toCharArray());) {
reader.mark(0);// 标记一下//参数不重要,一般设置成0
System.out.println((char) reader.read());// 记得强制转换呀
reader.reset();// 回到标记的地方//如果不回来只读一个文
char[] b = new char[1024];
int len = reader.read(b);
System.out.println("长为" + len);
System.out.println(b);
} catch (IOException e) {
e.printStackTrace();
}
// 2,从文件读//其实都是一样的啦
// txt文件//中文也不会乱码
try (Reader reader = new FileReader(new File("src//text1.txt"));) {
int tmp, cnt = 0;
char[] b = new char[100];
while ((tmp = reader.read()) != -1) {
b[cnt++] = (char) tmp;
}
System.out.print(new String(b, 0, cnt));
} catch (Exception e) {
e.printStackTrace();
}
// dat文件
try (Reader reader = new FileReader(new File("src//text3.dat"));
Writer writer = new FileWriter(new File("src//text3.dat"))) {
writer.write("Write输入");
writer.flush();
//如果不用flush,字符在缓冲区里,即使文件原来有内容,在write的时候清空文件,读出依然为空
int tmp, cnt = 0;
char[] b = new char[100];
while ((tmp = reader.read()) != -1) {
b[cnt++] = (char) tmp;
}
System.out.print(new String(b, 0, cnt));
} catch (Exception e) {
e.printStackTrace();
}
转换流InputStreamReader
一般用它构造一个InputStreamReader,然后可以向上转型(隐式转换)为Reader去构造其他Reader的子类,或者直接用该类
构造方法如果带两个参数,第二个参数指代按照固定编码转换,否则按照默认编码转换
public static void main(String[] args) {
try (BufferedReader iStream = new BufferedReader(new InputStreamReader(System.in));) {
System.out.println(iStream.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
Writer -(输出字符流)
是一个抽象类,子类需要实现void write(char[] cbuf, int off, int len),flush(),close()
常用方法:
方法 | 使用 |
---|---|
public void write(int c) throws IOException | 写一个字符,c为该字符对应ASCII码 |
public void write(char cbuf[]) throws IOException | 将char数组输出(也称写入,都一样啦) |
public abstract void write(char cbuf[], int off, int len) throws IOException | 输出char数组 |
public void write(String str) throws IOException | 输出String |
public void write(String str, int off, int len) throws IOException | 输出部分String |
public Writer append(char c) throws IOException | 输出一个字符,与write不同的是拥有返回值,并且可以写入”null“,我们将在接下来的代码演示 |
public abstract void flush() throws IOException | 刷新流 |
public abstract void close() throws IOException | 关闭流 |
try (Writer writer = new FileWriter("src//text1.txt");) {
writer.write("123");
} catch (IOException e) {
e.printStackTrace();
}
// FileNotFound异常是IO异常的子类,调用 new FileWriter实际调用了new OutputStream
// 此构造方法会抛出的为IO异常
// 对比字节流,就只会抛出FileNotFouond异常
try {
OutputStream oStream = new FileOutputStream("src//text1.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
非流式部分
File - 文件/文件夹
发现File如果要写会写很长,,先留个坑
网上一篇总结
RandomAccessFile - (随机读写文件)
该对象并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区(字符数组),通过内部的指针来操作字符数组中的数据,只能操作文件。
方法 | 使用 |
---|---|
public RandomAccessFile(String name, String mode) | name为文件路径,mode为r/rw,代表是不是只读 |
public RandomAccessFile(File file, String mode) | 同上,只是第一个参数变成File |
private RandomAccessFile(File file, String mode, boolean openAndDelete) | 第三个参数代表写入文件是否删除原内容,注意,三个参数的构造方法,第一个参数只能是File类 |
read | 三种形式的read,不再赘述 |
write | 三种形式的write,不再赘述 |
public void seek(long pos) throws IOException | 设置从文件开头开始测量的文件指针偏移量,下一次读或写发生在该偏移量处 |
public native long length() throws IOException | 返回文件长度 |
public void close() throws IOException | 关闭File类 |
read+基本数据类型 | 类似于DataInputStream的方式实现了一些基础数据类型的输入(字节流) |
public final String readLine() throws IOException | 一次读一行 |
write+基础数据类型 | 类似于DataOutputStream的方式实现了一些输出 |
public final void writeBytes(String s) throws IOException | 写入一串字符 |
try (RandomAccessFile rioFile = new RandomAccessFile("src//text1.txt", "rw");) {
rioFile.writeInt(12);
rioFile.seek(0);// 将文件指针放到开头
System.out.println(rioFile.readInt());
rioFile.seek(rioFile.length());//指针放到文件尾,实现追加
long pos = rioFile.getFilePointer();// 获取指针当前位置
rioFile.write("中".getBytes());// 这里和DataOutputStream一样,要使用write,否则高于8位的失去,乱码
rioFile.seek(pos);
byte[] b = new byte[1024];
int len = rioFile.read(b);
System.out.println(new String(b, 0, len));
} catch (IOException e) {
e.printStackTrace();
}
关于close
关闭顺序
先关闭节点流,后关闭处理流(如果A依赖B,先关闭A)
try-with-resources语法糖
语法糖:在语言中增加的某种语法,在不影响功能的情况下为程序员提供更方便的使用方式。
传统写法:
InputStream tmp = null;
try {
tmp = new FileInputStream(new File("src//text1.txt"));
tmp.read();
} catch (IOException e) {
System.out.println(e);
} finally {
try {
tmp.close();// 需要显式调用close,还要写个 try-catch
} catch (Exception e2) {
// 在close时仍然可能抛出异常,此时上一层异常将被抛弃
System.out.println(e2);
}
}
//如果一次使用多个流,嵌套try-catch更繁琐
try-with-resources:
参考
try(InputStream iStream = new FileInputStream(new File("src//text1.txt"));
InputStream iStream2 = new FileInputStream(new File("src//text2/txt"));)
{
//......
}catch (IOException e) {
System.out.println(e);
}
//做了两件事情:1,添加close,2,压缩传统写法第一层抛出的异常
重定向
方法 | 使用 |
---|---|
static void setErr(PrintStream err) | 重定向”标准”错误输出流,未更改则为屏幕 |
static void setIn(InputStream in) | 重定向”标准”输入流 ,未更改则为键盘 |
static void setOut(PrintStream out) | 重定向”标准”输出流,未更改则为屏幕 |
注:PrintStream为FilterOutputStream的子类,属于装饰类
参考自
public class RedirectOut {
public static void main(String[] args) throws FileNotFoundException {
//一次性创建PrintStream输出流
PrintStream ps=new PrintStream(new FileOutputStream("D://test1.txt"));
//将标准输出重定向到PS输出流
System.setOut(ps);
//向标准输出输出一个字符串
System.out.println("Hello world");
}
}
下面是重定向标准输入,从而可以把System.in重定向到指定文件,而不是键盘输入.首先创建了一个FileInputStream输入流,并使用System的setIn方法将系统标准输入重定向到该文件输入流.运行程序,直接输出的是文本文件的内容,表明程序不再使用键盘作为输入,而是使用文本文件作为标准输入源.
public class RedirectIn {
public static void main(String[] args) throws FileNotFoundException {
FileInputStream fis=new FileInputStream("D://test.txt");
System.setIn(fis);//将标准输入重定向到fis输入流
Scanner sc=new Scanner(System.in);//使用System.in创建Scanner对象,用于获取标准输入
//增加下面一行只把回车作为分隔符
sc.useDelimiter("\n");
while(sc.hasNext()){
//判断是否还有下一个输入项//输出输入项
System.out.println("键盘输入的内容是:"+sc.next());
}
}
}