流的概念
流(Stream)是指在计算机的输入输出操作中各部件之间的数据流动。按照数据的传输方向,流可分为输入流和输出流。Java语言里的流序列中的数据既可以时未经过加工的原始二进制数据,也可以是经过一i的那个编码处理后符合某种特定格式的数据。
流的分类
按大小
按大小分为字节流与字符流。字节流:读取的最小单位是一个字节(byte)。字符流:字符流以此可以读取一个字符(char)。
字节流与字符流的区别
java.io包中操作文件内容的主要有两大类:字节流、字符流,两类都有输入和输出操作。在字节流中,输出数据是依靠OutputStream完成,输入使用InputStream;在字符流中,输出使用Writer类完成,输入使用Reader类完成。
字符流处理的单元为两个字节的Unicode字符,分为操作字符、字符数组或字符串,而字节流处理单元为一个字节,操作字节和字节数组。字节流时JVM将字节转化为2个字节的Unicode字符而成的,所以它对多国语言支持性能较好!如果是音频文件、图片、歌曲等 就用字节流,如果是关系到中文(文本)的,就用字符流。
所有文件的存储都是字节(byte)的存储,在磁盘上保存的并不是文件的字符而是先把字符编码成字节,再储存字节到磁盘。读取文件时,也是一个一个字节地读取以形成字节序列。
按传输方向
分为输入流和输出流
按等级
分为:节点流(低级流)和处理流(高级流或过滤流)
IO流
IO指的是输出与输出
I即Input:输入,从文件到程序
O即Output:输出,从程序到文件,写出数据
字节流
1.FIS和FOS
即 FileInputStream(文件输入流) 和 FileOutputStream(文件输出流)
直接上FileInputStream的源码:
public
class FileInputStream extends InputStream
/* File Descriptor - handle to the open file */
private final FileDescriptor fd;
/**
* The path of the referenced file
* (null if the stream is created with a file descriptor)
*/
private final String path;
private FileChannel channel = null;
private final Object closeLock = new Object();
private volatile boolean closed = false;
大部分看不懂,截取关键的部分,由源码可以得到 FileInputStream 是继承自InputStream 这个父类的
再来看看它里面的方法:
可见方法有点小多,但是现阶段就read(读文件)和close(关闭流)两个方法有用
再上 FileOutputStream的原码和方法列表
public
class FileOutputStream extends OutputStream
{
/**
* The system dependent file descriptor.
*/
private final FileDescriptor fd;
/**
* True if the file is opened for append.
*/
private final boolean append;
/**
* The associated channel, initialized lazily.
*/
private FileChannel channel;
/**
* The path of the referenced file
* (null if the stream is created with a file descriptor)
*/
private final String path;
private final Object closeLock = new Object();
private volatile boolean closed = false;
类似地,FileOutputStream继承自抽象类OutputStream,方法主要是write(写入)和close(关闭流)。
下面用两个流实现对文件地读写以及复制地操作:
public class TestFISAndFOS {
File file;
FileInputStream fis;
FileOutputStream fos;
@Before //在所有test运行之前先运行的内容
public void getFile(){
file = new File("src\\com\\jmn\\io\\testio\\fisfos.txt");
}
@Test//写
public void test() throws IOException {
/*
write(in)
*/
fos = new FileOutputStream(file);
fos.write("helloworld".getBytes());
}
@Test//读
public void read() throws IOException {
fis = new FileInputStream(file);
/*
int read(): 返回值就是读取出来的字符,如果没有字符则返回-1
// */
// int i = -1;
// while ((i = fis.read()) != -1){
// System.out.println((char) i);
// }
/*
int read(byte[] bus):读取文件内容到byte数组中
*/
int i = -1;
byte[] bus = new byte[1024];
if((i = fis.read(bus)) != -1){
System.out.println(new String(bus));
}
}
@Test//文件的复制 ,将fisfos.txt 在相同路径下复制一个副本fisfos_copy.txt
public void fileCopy() throws Exception{
//1.先读
fis = new FileInputStream(file);
file = new File("src\\com\\jmn\\io\\testio\\fisfos_copy.txt");
fos = new FileOutputStream(file);
int i = -1;
//读单个
// while ((i = fis.read()) != -1){
// fos.write(i);
// }
//读byte[]
byte[] bytes = new byte[1024];
if((i = fis.read(bytes)) != -1){
System.out.println(new String(bytes));
}
}
@After //每运行一个test之后必须要运行的内容
public void close() throws IOException {
//非空判断 关闭流,释放资源
if(fis != null) fis.close();
if(fos != null) fos.close();
}
}
2.BIS与BOS(带缓存的输入输出流)
BIS:BufferedInputStream
BOS:BufferedOutputStream
缓冲流: 内部定义了一个缓冲区
就不看源码了,这里我们看看他们的继承关系:
值得一提的是,带缓存的流中需要用flush方法来让缓存区的数据读入或取出,因为缓冲区存在着一个机制,即缓冲区满了才会读入或写出。那么多少大小才会让缓冲区满呢?我们来看看源码!
可以清晰地看见默认缓冲区大小被设置成了8k,但是在日常练习中一般是不会溢出的,所以我们常用flush方法来使数据强制从缓冲区里面刷出。
3.对象序列化
思考一个问题,如何将对象通过流存入磁盘呢?很容易想到方法:将对象转化为字节,然后存入。对象序列化就是来干这件事情的。那么什么是对象序列化呢?其实,就是刚刚提出问题的内容:可以将对象表示为一个字节序列,该字节序列包括对象的数据、对象的类型信息,和对象中数据的类型,将序列化的对象保存在文件中。同样,也可以逆行:从文件中读取对象的的字节序列,然后将对象创建出来,这被称为:反序列化。
总结:
序列化:对象转化为字节序列
反序列化:字节序列转化为对象
那么,如何进行对象序列化?
必须实现序列化和反序列化的对象必须实现 Serializable(1.没有任何方法需要实现, 2.仅表示当前对象可以序列化和反序列化) 序列化使用writeObject(Object o) 反序列化用readObject()
对象序列化的注意事项:必须实现Serializable接口,该接口没有方法,建议为对象添加类版本号,避免版本升级后造成的不兼容问题 transient关键字:修饰属性可以对没必要的属性值忽略,从而对对象序列化后的字节序列“瘦身”。瘦身即 transient关键字,它的作用是专门针对对象瘦身,表不需要序列化的对象可以省略属性的序列化,减少字节序列的生成。注:Person类在下面给出
在进行对象序列化时会遇到对象序列化兼容性问题:对旧接口添加字段,但旧接口得request对象或response对象未添加serializableuid,这样如果强行加字段就会导致调用方不兼容导致出错。即最好给对象加入uid,如下:
package com.jmn.io.testio;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = -1496217388455779693L;
private String name = "张三";
public Integer age = 18;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
下面来介绍一种对象的输入输出流。
4.OIS和OOS
即对象输入流(ObjectInputStream)和对象输出流(ObjectOutputStream)。
举一个实例:
package com.jmn.io.testio;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.*;
public class TestOOSAndOIS {
File file;
ObjectOutputStream oos;
ObjectInputStream ois;
@Before
public void getFile() {
file = new File("src/com/fs/jmn/io/testio/person.txt");
}
@Test
public void testPerson() throws Exception {
//序列化
oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(new Person());
//System.out.println(new Person());
//反序列化
ois = new ObjectInputStream(new FileInputStream(file));
Object o = ois.readObject();
System.out.println(o);
}
@Test
public void restPerson2() throws IOException, ClassNotFoundException {
oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(new Person());
//System.out.println(new Person());
ois = new ObjectInputStream(new FileInputStream(file));
Object o = ois.readObject();
System.out.println(o);
}
@After
public void close() throws Exception {
if (oos != null) oos.close();
if (ois != null) ois.close();
}
}
注:Person类已在上文给出。
来看看输出到Person文件中储存的字节序列(序列化)
�� sr com.jmn.io.testio.Person�<^6fT�� L aget Ljava/lang/Integer;L namet Ljava/lang/String;xpsr java.lang.Integer⠤���8 I valuexr java.lang.Number������ xp t 张三
再来看看控制台(反序列化)
Person{name='张三', age=18}
Person{name='张三', age=18}
是不是很有意思!
4.字符流(介绍一种ISR和OSW)
ISR和OSW
即:InputStreamReader(字符输入流) 和 OutputStreamWriter(字符输出流)。
来看看他们的继承关系
字符流可以很方便的往文件中输出字符,而不用去转换类型。