Java中常见流的分类及简单讲解
流在Java中是指计算中流动的缓冲区。
从外部设备流向中央处理器的数据流成为“输入流”,反之成为“输出流”。
字符流和字节流的主要区别:
1.字节流读取的时候,读到一个字节就返回一个字节;字符流使用了字节流读到一个或多个字节(中文对应的字节数是两个,在UTF-8码表中是3个字节)时。先去查指定的编码表,将查到的字符返回。
2.字节流可以处理所有类型数据,如:图片,MP3,AVI视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。
1.字节流:
字节输入流类:FileInputStream、BufferedInputStream和DataInputStream
FileInputStream:此类用于从本地文件系统中读取文件内容。
构造方法:
·FileInputStream(File file):打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的File对象file指定。
·FileInputStream(String name):打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的路径名name指定。
常用方法:
·int available():返回下一次对此输入流调用的方法不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。
·void close():关闭此文件输入流并释放与该流关联的所有系统资源。
BufferedInputStream:此类本身带有一个缓冲区,在读取数据时,先放到缓冲区中,可以减少对数据源的访问,提高运行的效率。
构造方法:
·BufferedInputStream(InputStream in):创建一个BufferedInputStream并保存其参数,即输入流in,以便将来使用。
·BufferedInputStream(InputStream in,int size):创建一个具有指定缓冲区大小的BufferedInputStream并保存其参数,即输入流in,以便将来使用。
常用方法:
·int available():返回下一次对此输入流调用的方法不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。
·void close():关闭此输入流并释放与该流关联的所有系统资源。
·int read():从输入流中读取数据的下一个字节。
·int read(byte[] b,int off,int len):从此字节输入流中给定偏移量处开始将各字节读取到指定的byte数组中。
DataInputStream:该类提供一些基于多字节读取方法,从而可以读取基本数据类型的数据。
构造方法:
·DataInputStream(InputStream in):使用指定的底层InputStream创建一个DataInputStream。
常用方法:
·int read(byte[] b):从包含的输入流中读取一定数量的字节,并将它们存储到缓冲区数组b中。
·int read(byte[] b,int off,int len):从包含的输入流中将最多len个字节读入一个byte数组中。
字节输出流类:FileOutputStream、BufferedOutputStream和DataOutputStream
FileOutputStream:此类用于从本地文件系统的文件中写入数据。
构造方法:
·FileOutputStream(File file):创建一个向指定File对象表示的文件中写入数据的文件输出流。
·FileOutputStream(String name):创建一个向具有指定名称的文件中写入数据的输出文件流。
常用方法:
·void close():关闭此文件输出流并释放与此流有关的所有系统资源。
·FileDescriptor getFD():返回与此流有关的文件描述符。
·void write(byte[] b):将b.length个字节从指定byte数组写入此文件输出流中。
·void write(byte[] b,int off,int len):将指定byte数组中从偏移量off开始的len个字节写入此文件输出流。
·void write(int b):将指定字节写入此文件输出流。
BufferedOutputStream:此类本身带有一个缓冲区,在写入数据时,先放到缓冲区中,实现缓冲的数据流。
构造方法:
·BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流,来将数据写入指定的底层输入流。
·BufferedOutputStream(OutputStream out,int size):创建一个新的缓冲输出流,来将具有指定缓冲区大小的数据写入指定的底层输出流。
常用方法:
·void flush():刷新此缓冲的输出流。
·void write(byte[] b,int off,int len):将指定byte数组中从偏移量off开始的len个字节写入此缓冲的输出流。
·void write(int b):将指定的字节写入此缓冲的输出流。
DataOutputStream(OutputStream out):创建一个新的数据输出流,将数据写入指定基础输出流。
常用方法:
·void flush():清空此数据输出流。
·int size():返回计数器written的当前值,即到目前为止写入此数据输出流的字节数。
·void write(byte[] b,int off,int len):将指定byte数组中从偏移量off开始的len个字节写入基础输出流。
·void write(int b):将指定字节(参数b的八个低位)写入基础输出流。
2.字符流:
FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。
构造方法:
·FileReader(File file):在给定从中读取数据的File的情况下创建一个新的FileReader。
·FileReader(String fileName):在给定从中读取数据的文件名的情况下创建一个新的FileReader。
BufferedReader类是Reader类的子类,为Reader对象添加字符缓冲器,为数据输入分配内存存储空间,存取数据更为有效。
构造方法:
·BufferedReader(Reader in):创建一个使用默认大小输入缓冲区的缓冲字符输入流。
·BufferedReader(Reader in,int sz):创建一个使用指定大小输入缓冲区的缓冲字符输入流。
操作方法:
·void close():关闭该流并释放与之关联的所有资源。
·void mark(int readAheadLimit):标记流中的当前为止。
·boolean markSupported();判断此流是否支持mark()操作。
·int read():读取单个字符。
·int read(char[] cbuf,int off,int len):将字符读入数组的某一部分。
·String readLine():读取一个文本行。
·boolean ready():判断此流是否已准备好被读取。
·void reset():将流重置到最新的标记。
·long skip(long n):跳过字符。
FileWriter:用来写入字符文件的便捷类,可用于写入字符流。
构造方法:
·FileWriter(File file):根据给定的File对象构造一个FileWriter对象。
·FileWriter(String filename):根据给定的文件名构造一个FileWriter对象。
BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
Java输入/输出流体系中常用的流分类(表内容来自java疯狂讲义)
注:下表中带下划线的是抽象类,不能创建对象。粗体部分是节点流,其他就是常用的处理流。
| 流分类 | 使用分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
| 抽象基类 | InputStream |
OutputStream |
Reader | Writer | |
| 节点流 | 访问文件 | FileInputStream | FileOutStream | FileReader | FileWriter |
| 访问数值 | ByteArrayInputStream | ByteArrayOutStream | CharArrayReader | CharArrayWriter | |
| 访问管道 | PipedInputStream | PipedOutStream | PipedReader | PipedWriter | |
| 访问字符串 | StringReader | StringWriter | |||
| 处理流 | 缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
| 转换流 | InputStreamReader | OutputStreamWriter | |||
| 对象流 | ObjectInputStream | ObjectOutputStream | |||
| 抽象基类(过滤) | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter | |
| 打印流 | PrintStream | PrintWriter | |||
| 推回输入流 | PushbackInputStream | PushbackReader | |||
| 特殊流 | DataInputStream | DataOutputStream |
1.Java IO是采用的是装饰模式,即采用处理流来包装节点流的方式,来达到代码通用性。
2.处理流和节点流的区分方法,节点流在新建时需要一个数据源(文件、网络)作为参数,而处理流需要一个节点流作为参数。
3.处理流的作用就是提高代码通用性,编写代码的便捷性,提高性能。
4.节点流都是对应抽象基类的实现类,它们都实现了抽象基类的基础读写方法。其中read()方法如果返回-1,代表已经读到数据源末尾。
基本IO操作--字节流
一、InputStream与OutputStream
1. 输入与输出
我们编写的程序除了自身会定义一些数据信息外,经常还会引用外界的数据,或是将自身的数据发送到外界。比如,我们编写的程序想读取一个文本文件,又或者我们想将程序中的某些数据写入到一个文件中。这时我们就要使用输入与输出。
输入: 读
输出: 写
流: 单向
什么是输入:输入是一个从外界进入到程序的方向,通常我们需要“读取”外界的数据时,使用输入。所以输入是用来读取数据的。
什么是输出:输出是一个从程序发送到外界的方向,通常我们需要”写出”数据到外界时,使用输出。所以输出是用来写出数据的。
2. 节点流与处理流
低级 高级
按照流是否直接与特定的地方 (如磁盘、内存、设备等) 相连,分为节点流和处理流两类。
节点流:可以从或向一个特定的地方(节点)读写数据。
处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。
处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。
高级流不能独立存在 用来处理其他流的
目的:简化读写操作
3. InputStream与OutputStream常用方法
以字节为单位读取数据的流: 字节输入流
InputStream是所有字节输入流的父类,其定义了基础的读取方法,常用的方法如下:
int read()
读取一个字节,以int形式返回,该int值的”低八位”有效,若返回值为-1则表示EOF。
int read(byte[] d)
尝试最多读取给定数组的length个字节并存入该数组,返回值为实际读取到的字节量。
OutputStream是所有字节输出流的父类,其定义了基础的写出方法,常用的方法如下:
void write(int d)
写出一个字节,写的是给定的int的”低八位”
void write(byte[] d)
void write(byte[] d intoffset intlen)
将给定的字节数组中的所有字节全部写出
这两种流是抽象类 不能实例化
二、 文件流
1. 创建FIS对象
FileInputStream是文件的字节输入流,我们使用该流可以以字节为单位读取文件内容。
FileInputStream有两个常用的构造方法:
FileInputStream(File file):
创建用于读取给定的File对象所表示的文件FIS
例如:
File file = new File("demo.dat");
FileInputStream fis
= new FileInputStream(file);//创建一个用于读取demo.dat文件的输入流
FileInputStream(String name):
创建用于读取给定的文件系统中的路径名name所指定的文件的FIS
例如
FileInputStream fis
//创建一个用于读取demo.dat文件的输入流
= new FileInputStream("demo");
2. 创建FOS对象(重写模式)
FileOutputStream是文件的字节输出流,我们使用该流可以以字节为单位将数据写入文件。
构造方法:
FileOutputStream(File file)
创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
例如:
FIle file = new File("demo.dat");
FileOutputStream fos = new FileOutputStream(file);
FileOutputStream(String filename):
创建一个向具有指定名称的文件中写入数据的输出文 件流。
例如:
FileOutputStream fos = new FileOutputStream("demo.dat");
这里需要注意,若指定的文件已经包含内容,那么当使用FOS对其写入数据时,会将该文件中原有数据全部清除。
3. 创建FOS对象(追加模式)
通过上一节的构造方法创建的FOS对文件进行写操作时会覆盖文件中原有数据。若想在文件的原有数据之后追加新数据则需要以下构造方法创建FOS
构造方法:
FileOutputStream(File file,boolean append)
创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
例如:
File file = new File("demo.dat");
FileOutputStream fos = new FileOutputStream(file,true);
FileOutputStream(String filename,boolean append):
创建一个向具有指定名称的文件中写入数据的输出文 件流。
例如:
FileOutputStream fos = new FileOutputStream("demo.dat",true);
以上两个构造方法中,第二个参数若为true,那么通过该FOS写出的数据都是在文件末尾追加的。
1 /**
2 * 使用文件字节输出流向文件中写出数据
3 * @author Administrator
4 *
5 */
6 public class TestFOS1 {
7 public static void main(String[] args) throws IOException {
8 /*
9 * 创建一个文件字节输出流用于向fos.dat文件中
10 * 写出字节数据
11 */
12 /*
13 * 添加布尔值参数 true 变成追加型操作
14 * FileOutputStream(File file,boolean append)
15 FileOutputStream(String str,boolean append)
16 */
17 FileOutputStream fos = new FileOutputStream("fos.dat",true);
18 //写一个字符串
19 fos.write("hello world".getBytes("GBK"));
20
21 //流用完了要记得关闭
22 fos.close();
23
25 }
26 }
4. read()和write(int d)方法
FileInputStream继承自InputStream,其提供了以字节为单位读取文件数据的方法read。
int read()
从此输入流中读取一个数据字节,若返回-1则表示EOF(End Of File)
FileOutputStream继承自OutputStream,其提供了以字节为单位向文件写数据的方法write。
void write(int d)
将指定字节写入此文件输出流。,这里指写给定的int值的”低八位”
例如
FileOutputStream fos = new FileOutputStream("demo.dat");
fos.write('A');//这里要注意,char占用2个字节,但这里只写入了1个字节。
5. read(byte[] d)和write(byte[] d)方法
FileInputStream也支持批量读取字节数据的方法:
int read(byte[] b)
从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中
FileOutputStream也支持批量写出字节数据的方法:
void write(byte[] d)
将 b.length 个字节从指定 byte 数组写入此文件输出流中。
例如:
FileOutputStream fos = new FileOutputStream("demo.txt");
byte[] data = "HelloWorld".getBytes();
fos.write(data);//会将HelloWorld的所有字节写入文件。
void write(byte[] d,int offset,int len)
将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。
例如:
FileOutputStream fos = new FileOutputStream("demo.txt");
byte[] data = "HelloWorld".getBytes();
fos.write(data,5,5);//只会将world这5个字节写入文件。
**
* 使用文件输入流将数据从文件中读取
* @author Administrator
*
*/
class TestFIS{
public static void main(String[] args) throws IOException {
/*
* 使用文件字节输入流读取fos.dat文件
*/
FileInputStream fis =new FileInputStream("fos.dat");
int d = -1;
while((d=fis.read())!=-1){
char c=(char)d;
System.out.print(c);
}
}
}
/**
* 使用文件的输入流与输出流实现文件复制工作
*/
class TestCopy{
/*
* 1.创建文件输入流用于读取原文件
* 2.创建文件输出流用于向目标文件中写
* 3.循环从源文件中读取每一个字节
* 4.将读取到的每一个字节写入目标文件
* 5.关闭两个流
*/
public static void main(String[] args) throws IOException {
FileInputStream fis =new FileInputStream("fos.dat");
FileOutputStream fos = new FileOutputStream("fos1.dat",true);
int d=-1;
while((d=fis.read())!=-1){
//char c= (char)d;
fos.write(d);
}
fis.close();
fos.close();
}
}
/**
* 使用文件流的批量读和批量写 复制文件
*/
class TestCopy2{
public static void main(String[] args) throws IOException {
FileInputStream fis =new FileInputStream("fos.dat");
FileOutputStream fos = new FileOutputStream("fos1.dat",true);
byte [] b= new byte[10*1024];
int d=-1;
while((d=fis.read(b))!=-1){
fos.write(d);
}
fis.close();
fos.close();
}
}
三、缓冲流
1. BIS基本工作原理
在读取数据时若以字节为单位读取数据,会导致读取次数过于频繁从而大大的降低读取效率。为此我们可以通过提高一次读取的字节数量减少读写次数来提高读取的效率。
BufferedInputStream是缓冲字节输入流。其内部维护着一个缓冲区(字节数组),使用该流在读取一个字节时,该流会尽可能多的一次性读取若干字节并存入缓冲区,然后逐一的将字节返回,直到缓冲区中的数据被全部读取完毕,会再次读取若干字节从而反复。这样就减少了读取的次数,从而提高了读取效率。
BIS是一个处理流,该流为我们提供了缓冲功能。
2. BOS基本工作原理
与缓冲输入流相似,在向硬件设备做写出操作时,增大写出次数无疑也会降低写出效率,为此我们可以使用缓冲输出流来一次性批量写出若干数据减少写出次数来提高写 出效率。
BufferedOutputStream缓冲输出流内部也维护着一个缓冲区,每当我们向该流写数据时,都会先将数据存入缓冲区,当缓冲区已满时,缓冲流会将数据一次性全部写出。
3. 使用BIS与BOS实现缓冲输入输出
使用缓冲流来实现文件复制:
FileInputStream fis = new FileInputStream("java.zip");
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("copy_java.zip");
BufferedOutputStream bos = new BufferedOutputStream(fos);
int d = -1;
while((d = bis.read())!=-1){
bos.write(d);
}
bis.close();//读写完毕后要关闭流,只需要关闭最外层的流即可
bos.close();
4. flush方法
使用缓冲输出流可以提高写出效率,但是这也存在着一个问题,就是写出数据缺乏即时性。有时我们需要需要在执行完某些写出操作后,就希望将这些数据确实写出,而非在缓冲区中保存直到缓冲区满后才写出。这时我们可以使用缓冲流的一个方法flush。
void flush()
清空缓冲区,将缓冲区中的数据强制写出。
BufferedOutputStream bos
= new BufferedOutputStream(
new FileOutputStream("demo.dat")
);
bos.write('a');//并没有向磁盘写出,而是写入到了BOS的缓存中
bos.flush();//强制将缓存中的数据一次性写出,这时‘a’才会被写入磁盘
bos.close();//实际上,close()方法在变比缓冲流前也调用了flush()
class TestFlow2{
public static void main(String[] args) throws IOException {
FileOutputStream fos =new FileOutputStream("1.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
String str = "hello";
bos.write(str.getBytes());
bos.flush(); //清楚缓存区
bos.close();
}
}
四、对象流
对象是存在于内存中的。有时候我们需要将对象保存到硬盘上,又有时我们需要将对象传输到另一台计算机上等等这样的操作。这时我们需要将对象转换为一个字节序列,而这个过程就称为对象序列化。相反,我们有这样一个字节序列需要将其转换为对应的对象,这个过程就称为对象的反序列化。
1. 使用OOS实现对象序列化
ObjectOutputStream是用来对对象进行序列化的输出流。
其实现对象序列化的方法为:
void writeObject(Object o)
该方法可以将给定的对象转换为一个字节序列后写出。
例如:
Emp emp = new Emp("张三",12,"男");
FileOutputStream fos = new FileOutputStream("Emp.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(emp);//将emp对象序列化后写入文件
oos.close();
2. 使用OIS实现对象反序列化
ObjectInputStream是用来对对象进行反序列化的输入流。
其实现对象反序列化的方法为:
Object readObject()
该方法可以从流中读取字节并转换为对应的对象。
例如:
FileInputStream fis = new FileInputStream("Emp.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Emp emp = (Emp)ois.readObject();//将Emp对象从文件中读取并反序列
....
ois.close();
3. Serializable接口
ObjectOutputStream在对对象进行序列化时有一个要求,就是需要序列化的对象所属的类必须实现Serializable接口。
实现该接口不需要重写任何方法。其只是作为可序列化的标志。
通常实现该接口的类需要提供一个常量serialVersionUID,表明该类的版本。若不显示的声明,在对象序列化时也会根据当前类的各个方面计算该类的默认serialVersionUID,但不同平台编译器实现有所不同,所以若向跨平台,都应显示的声明版本号。
如果声明的类序列化存到硬盘上面,之后随着需求的变化更改了类别的属性(增加或减少或改名),那么当反序列化时,就会出现InvalidClassException,这样就会造成不兼容性的问题。
但当serialVersionUID相同时,它就会将不一样的field以type的预设值反序列化,可避开不兼容性问题。
例如:
/**
* 每一个实例用于描述一个人的信息
* @author Administrator
*
*/
// Serializable 实现这个接口就是打标签 无需在重写任何东西
class person implements Serializable{
private static final long serialVersionUID = 1L; //版本号
private String name;
private int age;
private int phoneNumber;
private int sex;
private List<String> otherInfo;
private Date birDay;
public person(String name, int age, int phoneNumber, int sex,
List<String> otherInfo, Date birDay) {
super();
this.name = name;
this.age = age;
this.phoneNumber = phoneNumber;
this.sex = sex;
this.otherInfo = otherInfo;
this.birDay = birDay;
}
@Override
public String toString() {
return "person [age=" + age + ", birDay=" + birDay + ", name=" + name
+ ", otherInfo=" + otherInfo + ", phoneNumber=" + phoneNumber
+ ", sex=" + sex + "]";
}
}
/**
* 使用ObjectOutputStream 将对象写入文件
* @author Administrator
*
*/
class TestOOSDemo{
public static void main(String[] args) throws IOException {
List<String> otherInfo =new ArrayList<String>();
otherInfo.add("其他信息1");
otherInfo.add("其他信息2");
otherInfo.add("其他信息3");
person ps =new person("张三",25,1364951,1,otherInfo, null);
System.out.println(ps);
/*
* 将Person序列化后写入文件中
* 1、向文件中写数据的流:FileOutputStream
* 2、将对象序列化的流:ObjectOutputStream
*/
FileOutputStream fos =new FileOutputStream("ps.obj");
ObjectOutputStream oos =new ObjectOutputStream(fos);
/*
* 将对象转为字节
将数据写入磁盘的过程称之为:持久化
*/
oos.writeObject(ps);
}
}
/**
* 实现数据的反序列化
* @author Administrator
*
*/
class TestBisDemo{
public static void main(String[] args) throws IOException, ClassNotFoundException {
/*
* 从文件中读取字节数据后再转换为对象
* 1.FIS 读取文件
* 2.OIS 反序列化
*/
FileInputStream fis = new FileInputStream("ps.obj");
ObjectInputStream ois =new ObjectInputStream(fis);
/*
* 反序列的方法
* Object readObject();
*/
person ps = (person)ois.readObject();
System.out.println(ps);
ois.close();
}
4. transient关键字
对象在序列化后得到的字节序列往往比较大,有时我们在对一个对象进行序列化时可以忽略某些不必要的属性,从而对序列化后得到的字节序列”瘦身”。
关键字 transient
被该关键字修饰的属性在序列化时其值将被忽略
public class Emp implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
private transient int age;//该属性在序列化时会被忽略
private String gender;
//getter and setter and other
...
}
参考链接:https://www.cnblogs.com/xiohao/p/8997401.html
参考链接:https://www.cnblogs.com/manue1/p/4500767.html