导航
以字节为单位获取数据的流,称为字节流,它们都继承于InputStream/OutputStream抽象类。
常用的字节流有:
- FileInputStream/FileOutputStream,文件字节流(节点流)
- ByteArrayInputStream/ByteArrayOutputStream,字节数组流(节点流)
- BufferedInputStream/BufferedOutputStream,字节缓存流(处理流)
- DataInputStream/DataOutputStream,数据字节流(处理流)
- ObjectInputStream/ObjectOutputStream,对象流(处理流)
下面分别介绍一下这五组字节流及用法。
FileInputStream/FileOutputStream
FileInputStream/FileOutputStream是最常用的文件字节流,当数据源为文件对象时,选择这个数据流进行处理。我们可以通过构造器快速了解它的操作数据类型:
public FileInputStream(File file)
public FileOutputStream(File file)
可以看到,这组数据流的操作对象为File类型。
关于数据流的操作,总是分为四个步骤:
- 1.指定数据源
- 2.选择合适的数据流
- 3.进行数据操作
- 4.关闭流,释放资源
来一个文件的拷贝实例,了解其用法:
public class FileStream {
public static void main(String[] args) {
//1.指定数据源
File in_scr = new File("a.txt"); // 输入数据为文件
File out_scr = new File("a-copy.txt"); // 输出数据也为文件
InputStream is = null;
OutputStream os = null;
try {
//2.选择合适的数据流
is = new FileInputStream(in_scr);
os = new FileOutputStream(out_scr);
//3.进行数据操作
byte[] flush = new byte[1024]; //字节流,数据以字节数组为单位
int len = -1;
while( (len = is.read(flush)) != -1 ) {
// for(int i=0;i<len;i++) {
// System.out.println(flush[i]); 可以将输入流直接打印出来观察
// }
os.write(flush,0,len);
}
os.flush(); //将缓存内容输出
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//4.关闭流,释放资源
//先打开的先关闭
try {
if(is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(os != null) {
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
需要注意的点有:
- 1.flush为byte[]数组,与形参不同,数组传入的是地址,所以在is.read(flush)方法中操作flush,外面的flush也会变换,read方法就是给flush赋值的一个方法,并且有返回值:读取到的元素个数,未读到时返回-1。
- 2.输出流中是有缓存的,就是说当输出流的数据量到达一定程度,才会输出。所以在结尾,我们需要手动将未到达缓存量的数据输出,使用输出流的flush()方法。
ByteArrayInputStream/ByteArrayOutputStream
ByteArrayInputStream/ByteArrayOutputStream,字节数组流,也是一个常用的节点流。当数据对象字节数组byte[ ]时,使用这种流进行操作。观察一下它们的构造器:
public ByteArrayInputStream(byte buf[])
public ByteArrayOutputStream()
可以看到,输入流的操作对象未byte[ ]字节数组;输出流无参数,并不能直接将流中数据输出为文件等,但ByteArrayOutputStream有toString()等方法,来方便对输出流进行操作。
还是来一个文件的拷贝实例,了解其用法:
public class ByteArrayStream {
public static void main(String[] args) {
//1.指定数据源
byte[] in_scr = new byte[] { -28, -67, -96, -26, -120, -111, -28, -69, -106 };//输入数据
ByteArrayInputStream bais = null;
ByteArrayOutputStream baos = null;
try {
//2.选择合适的数据流
bais = new ByteArrayInputStream(in_scr);
baos = new ByteArrayOutputStream();
//3.进行数据操作
byte[] flush = new byte[1024];
int len = -1;
System.out.print("输入流:");
while ((len = bais.read(flush)) != -1) {
for (int i = 0; i < len; i++) {
System.out.print(flush[i]+","); //读取输入流
}
baos.write(flush, 0, len);
}
System.out.println();
baos.flush(); //将缓存内容输出,到此,输出流的数据写入已完成
byte[] byteArray = baos.toByteArray(); //将输入流转换为字节数组
System.out.print("输出流:");
for (byte b : byteArray) {
System.out.print(b+",");
}
System.out.println();
System.out.println(baos.size());//输出流的元素个数
System.out.println(baos.toString("UTF-8"));//注意,与byte[]的toString()不同
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭流,释放资源
try {
if (bais != null) {
bais.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (baos != null) {
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
---------------------------------------------------
输出结果为:
输入流:-28,-67,-96,-26,-120,-111,-28,-69,-106,
输出流:-28,-67,-96,-26,-120,-111,-28,-69,-106,
9
你我他
BufferedInputStream/BufferedOutputStream
BufferedInputStream/BufferedOutputStream,字节缓存流,是一种处理流,使用了装饰设计模式,通过内部缓存数组来提高操作流的效率。构造器源码:
public BufferedInputStream(InputStream in)
public BufferedOutputStream(OutputStream out)
可以看到BufferedInputStream/BufferedOutputStream只能对流进行操作,而不能直接操作数据,所以我们称它为处理流。
当对文件或者其他数据源进行频繁的读写操作时,效率比较低,这时如果使用缓冲流就能够更高效的读写信息。因为缓冲流是先将数据缓存起来,然后当缓存区存满后或者手动刷新时再一次性的读取到程序或写入目的地。
缓存区的大小默认是8192字节(8K,1024*8),也可以使用其它的构造方法自己指定大小。
因此,缓冲流还是很重要的,我们在IO操作时都可以加上缓冲流来提升性能。
来一个文件复制的效率对比实例,了解用法与性能的提升:
public class BufferedStream {
public static void main(String[] args) {
// 使用缓冲字节流实现复制
long time1 = System.currentTimeMillis();
bufferedCopy("a.mp4", "a-copy1.mp4");
long time2 = System.currentTimeMillis();
System.out.println("缓冲字节流花费的时间为:" + (time2 - time1));
// 使用普通字节流实现复制
long time3 = System.currentTimeMillis();
fileCopy("a.mp4", "a-copy2.mp4");
long time4 = System.currentTimeMillis();
System.out.println("普通字节流花费的时间为:" + (time4 - time3));
}
public static void bufferedCopy(String in_path, String out_path) {
//1.指定数据源
File in_scr = new File(in_path);
File out_scr = new File(out_path);
InputStream is = null;
OutputStream os = null;
try {
//2.选择合适的数据流
is = new BufferedInputStream(new FileInputStream(in_scr));
os = new BufferedOutputStream(new FileOutputStream(out_scr));
//3.进行数据操作
byte[] flush = new byte[1024];
int len = -1;
while ((len = is.read(flush)) != -1) {
os.write(flush, 0, len);
}
os.flush(); // 将缓存内容输出
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭流,释放资源
// 先打开的先关闭
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (os != null) {
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void fileCopy(String in_path, String out_path) {
//1.指定数据源
File in_scr = new File(in_path);
File out_scr = new File(out_path);
InputStream is = null;
OutputStream os = null;
try {
//2.选择合适的数据流
is = new FileInputStream(in_scr);
os = new FileOutputStream(out_scr);
//3.进行数据操作
byte[] flush = new byte[1024];
int len = -1;
while ((len = is.read(flush)) != -1) {
os.write(flush, 0, len);
}
os.flush(); // 将缓存内容输出
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭流,释放资源
// 先打开的先关闭
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (os != null) {
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
-----------------------------------------------
输出结果为:
缓冲字节流花费的时间为:224
普通字节流花费的时间为:972
DataInputStream/DataOutputStream
DataInputStream/DataOutputStream,数据字节流(处理流),也是操作流的处理流,构造器如下:
public DataInputStream(InputStream in)
public DataOutputStream(OutputStream out)
除了对流的包装处理外,DataInputStream/DataOutputStream还可以实现直接对Java基础数据类型(int、double、String等)直接进行读写操作。
需要注意的是,使用数据流时,读取的顺序一定要与写入的顺序一致,否则不能正确读取数据。
先来一个对流的处理实例,还是复制为功能:
public class DataStream1 {
public static void main(String[] args) {
//1.指定数据源
File in_scr = new File("a.txt");
File out_scr = new File("a-copy.txt");
DataInputStream dis = null;
DataOutputStream dos = null;
try {
//2.选择合适的数据流
dis = new DataInputStream(new FileInputStream(in_scr));
dos = new DataOutputStream(new FileOutputStream(out_scr));
//3.进行数据操作
byte[] flush = new byte[1024];
int len = -1;
while( (len = dis.read(flush)) != -1 ) {
// for(int i=0;i<len;i++) {
// System.out.print(flush[i]);
// }
dos.write(flush, 0, len);
}
dos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭流,释放资源
//先打开的先关闭
try {
if(dis != null) {
dis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(dos != null) {
dos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
再来一个直接读写基本数据类型的实例:
public class DataStream2 {
public static void main(String[] args) {
//1.指定数据源
File in_scr = new File("a.txt");
File out_scr = new File("a.txt");//输入数据源与输出数据源一致,先写再读
DataInputStream dis = null;
DataOutputStream dos = null;
try {
//2.选择合适的数据流
dis = new DataInputStream(new FileInputStream(in_scr));
dos = new DataOutputStream(new FileOutputStream(out_scr));
//3.进行数据操作
//将如下数据写入到文件中
dos.writeChar('a');
dos.writeInt(10);
dos.writeDouble(Math.random());
dos.writeBoolean(true);
dos.writeUTF("你我他");//Stirng类型
//手动刷新缓冲区:将流中数据写入到文件中
dos.flush();
//直接读取数据:读取的顺序要与写入的顺序一致,否则不能正确读取数据。
System.out.println("char: " + dis.readChar());
System.out.println("int: " + dis.readInt());
System.out.println("double: " + dis.readDouble());
System.out.println("boolean: " + dis.readBoolean());
System.out.println("String: " + dis.readUTF());
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭流,释放资源
//先打开的先关闭
try {
if(dis != null) {
dis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(dos != null) {
dos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
-----------------------------------------
输出结果为:
char: a
int: 10
double: 0.8627686668141537
boolean: true
String: 你我他
ObjectInputStream/ObjectOutputStream
DataInputStream/DataOutputStream可以直接读写Java的基本数据类型。但Java中除了基本数据类型,还有引用数据类型,那么我们自己定义类,能不能也以类的方式放入IO流中呢?
ObjectInputStream/ObjectOutputStream就是Java提供的操作对象的流,也叫对象流(处理流),构造器源码:
public ObjectInputStream(InputStream in)
public ObjectOutputStream(OutputStream out)
也是需要对流进行操作,但使用它后,可以直接读写任意的数据类型。
需要注意的是,使用数据流时,读取的顺序一定要与写入的顺序一致,否则不能正确读取数据。
序列化与反序列化
需要注意的是,对象流中传输的对象必须支持进行序列化与反序列化操作:
当两个进程远程通信时,彼此可以发送各种类型的数据。 无论是何种类型的数据,都会以二进制序列的形式在网络上传送。比如,我们可以通过http协议发送字符串信息;我们也可以在网络上直接发送Java对象。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象才能正常读取。
把Java对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为Java对象的过程称为对象的反序列化。
对象序列化的作用有如下两种:
-
1.持久化: 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中,比如:休眠的实现。以后服务器session管理,hibernate将对象持久化实现。
-
2.网络通信:在网络上传送对象的字节序列。比如:服务器之间的数据通信、对象传递。
只有实现了Serializable接口的类的对象才能被序列化。 Serializable接口是一个空接口,只起到标记作用。
序列化中需要注意的是:
- 为了防止读和写的序列化ID不一致,一般指定一个固定的序列化ID。
- static属性不参与序列化。
- 对象中的某些属性如果不想被序列化,使用transient修饰。
来一个支持序列化类的实例:
//实现Serializable接口
class Student implements Serializable{
// 添加序列化ID,它决定着是否能够成功反序列化!
private static final long serialVersionUID = 1L;
private transient int num;
private String name;
private int age;
public Student(int num,String name, int age) {
super();
this.num = num;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student [num=" + num + ", name=" + name + ", age=" + age + "]";
}
}
有了可以序列化的自定义类后,我们来试试使用ObjectInputStream/ObjectOutputStream流读写它:
public class ObjectStream {
public static void main(String[] args) {
//1.指定数据源
File in_scr = new File("a.txt");
File out_scr = new File("a.txt"); //输入数据源与输出数据源一致,先写再读
ObjectInputStream ois = null;
ObjectOutputStream oos = null;
try {
//2.选择合适的数据流
ois = new ObjectInputStream(new FileInputStream(in_scr));
oos = new ObjectOutputStream(new FileOutputStream(out_scr));
//3.进行数据操作
//将如下数据写入到文件中
oos.writeChar('a');;
oos.writeInt(10);
oos.writeDouble(Math.random());
oos.writeBoolean(true);
oos.writeUTF("你我他");
//实例化对象
Student student = new Student(1,"小猪",18);
oos.writeObject(student);
//手动刷新缓冲区:将流中数据写入到文件中
oos.flush();
//直接读取数据:读取的顺序要与写入的顺序一致,否则不能正确读取数据。
System.out.println("char: " + ois.readChar());
System.out.println("int: " + ois.readInt());
System.out.println("double: " + ois.readDouble());
System.out.println("boolean: " + ois.readBoolean());
System.out.println("String: " + ois.readUTF());
System.out.println("Object: " + ois.readObject());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
//4.关闭流,释放资源
//先打开的先关闭
try {
if(oos != null) {
oos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(ois != null) {
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
----------------------------------------------------
输出结果为:
char: a
int: 10
double: 0.6826249100838067
boolean: true
String: 你我他
Object: Student [num=0, name=小猪, age=18]
2085




被折叠的 条评论
为什么被折叠?



