1. 基本概念
- 流:代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象。
- 流的本质:数据传输时的不同特征,将流分为不同的种类。更方便对传输的数据进行操作。
数据传输是需要通道的,而流恰恰就是这数据传输的通道。 - 输入输出:将电脑硬盘上的数据读到程序中的过程为输入。(Read、Input)
将程序中的数据写入到外部设备中的过程为输出。(Write、Output)
2.流的分类
-
按照编码格式:
字节流:可以读写二进制文件,主要处理音频、图片、歌曲、字节流,处理单元为1个字节。
字节流将读取到的字节数据,去指定的编码表中获取对应文字。
InputStream和OutputStream的子类都是字节流。
常用类:FileInputStream、OutputStream(文件输入字节流、文件输出字节流)。
字符流:主要处理字符或字符串,字符流处理单元为2个字节。
(UTF-8中汉字为三个字节,Unicode里面汉字为两个字节。)
Reader和Writer的子类都是字符流。
常用类:FileReader、FileWriter
-
按照数据传输方向:
输入流:往程序中读叫输入流。
所有输入流都是InputStream类或者Reader类的子类。
输出流:从程序中往外写叫输出流。
所有输出流都是OutputStream类或者Writer类的子类。
-
根据封装类型:
节点流:如果流封装的是某种特定的数据源,如文件、字符串、字符串数组等,则称为节点流。
常用类:字节输入流 FileInputStream
字节输出流 FileOutputStream
字符输入流 FileReader
字符输出流 FileWriter处理流:如果流封装的是其他流对象。该流称为处理流。
处理流中提供了缓冲功能,提高了(读写)传输效率。增加了许多新的方法。
常用类:
缓冲字节输出流 BufferedOutputStream
缓冲字节输入流 BufferedInputStream
缓冲字符输入流 BufferedReader
缓冲字符输出流 BufferedWriter
3.字节流
-
字节流:每次读取(写出)一个字节,当传输的资源文件有中文时,就会出现乱码。
-
字节输入流
父类:InputStream
常用的字节输入流:FileInputStream
子类构造方法:
FileInputStream(File file) 通过打开与实际文件的连接创建一个FileInputStream流对象,该文件由file对象命名。
FileInputStream(String name) 通过打开与实际文件的连接创建一个FileInputStream流对象,该文件由 name命名。
方法:
int read();
读取一个字节并以整数的形式返回(0~255),如果返回-1已到输入流的末尾。
int read(byte[]);
从输入流中读取至多一个数组长度的内容,返回实际读取的字节数,到达文件末尾,则返回-1。
int read(byte[],int off,int len)throws IOException
将一个字节类型的数组中的从指定位置(off)开始的len个字节读入到输入流。返回实际读取的字节 数,到达文件末尾,则返回-1。
数组称为缓冲区数组,大小一般取值为1024的整数倍。
void close()throws IOException;
关闭流释放内存资源。
-
字节输出流
父类:OutputStream
常用子类:FileOutputStream 文件字节输出流
方法:
void write (int b) throws IOException
向输出流中写入一个字节数据,该字节数据为参数b的低8位。
void write (byte[] b)
将数组b中的内容按字节写入到输出流。
void write (byte[] b, int off, int len) throws IOException
将一个字节类型的数组中的从指定位置(off)开始的len个字节写入到输出流。
数组称为缓冲区数组,大小一般取值为1024的整数倍。
void close() throws IOException
关闭流释放内存资源。
public static void main(String[] args) throws IOException { //输入流向程序中读入数据 FileInputStream in = new FileInputStream("E:\\demo.txt" ); //输出流 从程序向外输出数据 FileOutputStream out = new FileOutputStream("F:\\demo.txt"); //FileInputStream read(int b)//一次读一个字节,当内容读完之后返回-1 //FileInputStream read(byte[] b)//每次从输入流中读入ygbyte数组个字节,返回的是数组中实际装入的字节数量 //FileOutStream write(int b) 一次向外写一个字节 // int b = 0; // while((b = in.read())!= -1){ // out.write(b); // } byte[] b = new byte[1024]; int length= 0; while((length = in.read(b))!=-1) { System.out.println(length); out.write(b,0,length); } in.close(); out.close(); }
- 通过比较 read() 与 read(byte[]) 的方法复制文件的时间的长短,可以看出,带缓冲区的读写文件的速度快,java 上提供了专门带缓冲区的字节流,用以提高读写速度。
- 带缓冲区的字节流读取速度大于普通字节流。
public static void main(String[] args) throws IOException { /* 节点流:直接包含的是文件,字符串等数据 处理流:包含的是其他的流对象 BufferedInputStream,BufferedOutputStream内部提供了一个缓冲数组,提高读写效率 */ //输入流 向程序中读入数据 节点流 包含的是文件,字符串等数据 FileInputStream in = new FileInputStream("E:\\demo.txt"); BufferedInputStream bin = new BufferedInputStream(in); //输出流 从程序向外输出数据 FileOutputStream out = new FileOutputStream("F:\\demo.txt"); BufferedOutputStream bou = new BufferedOutputStream(out); //bin.read(); byte[] b = new byte[1024]; int length = 0; while((length = in.read(b))!=-1){ bou.write(b,0,length); } bin.close(); bou.flush();//刷新缓冲输出流 bou.close(); }
- 通过将缓冲处理流和的read(byte[])进行比较,说明了,带缓冲区的字节流 读取速度高于字节流。
-
4.字符流
-
字符流主要处理字符或字符串,字符流处理单元为2个字节。使用字符流可以传输并显示字符,否则只会复制字符对应的编码。
-
字符输出流
-
所有的字符输出流都是Writer的子类。Writer是一个抽象类。
-
CharArrayWriter、StringWriter 是两种基本的介质流,它们分别向Char 数组、String 中写入数据。PipedWriter 是向与其它线程共用的管道中写入数据。
-
BufferedWriter 是一个装饰器为Writer 提供缓冲功能。
-
PrintWriter是字符打印流。
-
OutputStreamWriter 是OutputStream 到Writer 转换的桥梁,它的子类FileWriter 其实就是一个实现此功能的具体类(具体可以研究一SourceCode)。功能和使用和OutputStream 极其类似,后面会有它们的对应图。
public static void main(String[] args) throws IOException { //字符流,每次直接读到一个字符,底层有转换流InputStreamReader。 //字符流只能读取纯文本文件。因为读取其他格式的文件,转换流不同,会读坏文件。 FileReader reader = new FileReader("E:\\test1.txt"); FileWriter writer = new FileWriter("E:\\test2.txt"); /* System.out.println( reader.read()); System.out.println(reader.read()); System.out.println(reader.read()); System.out.println(reader.read());//会直接输出字符对应的unicode编码。*/ int length = 0; while((length = reader.read()) != -1){ writer.write(length); System.out.println(length); } reader.close(); writer.close(); }
public static void main(String[] args) throws IOException { //字符流,每次直接读到一个字符,底层有转换流InputStreamReader。 //字符流只能读取纯文本文件。因为读取其他格式的文件,转换流不同,会读坏文件。 FileReader reader = new FileReader("E:\\test1.txt"); BufferedReader reader1 = new BufferedReader(reader); FileWriter writer = new FileWriter("E:\\test2.txt"); BufferedWriter writer1 = new BufferedWriter(writer); char[] c = new char[1024]; String line; while((line = reader1.readLine())!=null){ writer1.write(line); writer1.newLine(); } reader1.close(); writer1.flush(); writer1.close(); }
-
5.转换流
文件在硬盘上是以字节流形式存储的,通过InputStreamReader将字节流转化为字符流交给程序处理,程序将处理后的字节流通过OutputStreamWriter转化为字节流,保存。
-
何时使用转换流
- 当字节和字符之间有转换动作时。
- 流操作的数据对象需要解码、编码时。(当使用字节流读取到字符时,会默认调用转换流的方法。)
-
这两个流对象是字符体系中的成员,它们有转换作用,本身又是字符流,所以在构造的时候需要传入字节流对象进来。
InputStreamReader(InputStream in);将字节流以字符流输入。
OutputStreamWriter(OutputStream out);将字节流以字符流输出。
6.字节流与字符流的区别
-
字节流没有缓冲区,是直接输出的,而字符流是输出到缓冲区的。因此在输出时,字节流不调用colse()方法
-
时,信息已经输出了,而字符流只有在调用close()方法关闭缓冲区时,信息才输出。要想字符流在未关闭时输
出信息,则需要手动调用flush()方法。
-
读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
-
处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
结论:只要是处理纯文本数据,就优先考虑使用字符流。除此之外都使用字节流。
7.打印流
-
打印流顾名思义,只做输出没有输入。
-
其分为:字节打印流(PrintOutputStream)、字符打印流(PrintWriter)。
-
print方法可以打印各种类型的数据。
public static void main(String[] args) throws IOException {
PrintWriter out = new PrintWriter("E:\\test1.html");
out.println("<b>lalala</b>");
out.close();
}
8.对象输入输出流
-
对象的输入输出流 : 主要的作用是用于写入对象信息与读取对象信息。 对象信息一旦写到文件上那么对象的信息就可以做到持久化了。
-
对象的输出流: ObjectOutputStream
对象的输入流: ObjectInputStream
-
要将序列化之后的对象保存下来,需要通过对象输出流(ObjectOutputStream)将对象状态保存,之后再通过对象输入流(ObjectInputStream)将对象状态恢复。在ObjectInputStream 中用readObject()方法可以直接读取一个对象,ObjectOutputStream中用writeObject()方法可以直接将对象保存到输出流中。
public static void main(String[] args) throws IOException {
//将对象信息通过对象输出流写入到文件中长期保存的过程叫做对象序列化。
String s = "abc";
FileOutputStream fileOutputStream = new FileOutputStream("E:\\test2.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(s);//该方法可以对参数指定的obj对象进行序列化,将得到的字节序列写入到目标输出流中。
}
9.对象序列化与反序列化
-
对象序列化的前提
对象的寿命通常随着生成该对象的程序的终止而终止。 有时候,可能需要将对象的状态保存下来,在需要时再将对象恢复。
-
基本概念
对象的输出流将指定的对象写入到文件的过程,就是将对象序列化的过程。
对象的输入流将指定序列化好的文件读出来的过程,就是对象反序列化的过程。
既然对象的输出流将对象写入到文件中称之为对象的序列化,所以必须要实现Serializable接口。
Serializable接口中没有任何方法。当一个类声明实现Serializable接口后,表明该类可被序列化。
-
在类中可以生成一个编号private static final long serialVersionUID *= -5974713180104013488L;随机生成 唯一的serialVersionUID 用来表明实现序列化类的不同版本间的兼容性(如果实现接口不显式的生成序列化版本号,类的信息一旦改变,序列化id也会随之改变。 transient 所修饰的属性(敏感信息,如密码等)不被序列化到文件中。)。某个类在与之对应的对象已经序列化出去后做了修改,该对象依然可以被正确反序列化。
-
以下为两个对象序列化和反序列化的例子。
案例一
public class Person implements Serializable {
private static final long serialVersionUID = 5342503031867452777L;
private int age;
private String name;
public Person() {}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Test {
public static void main(String[] args) {
//序列化
Person person = new Person();
person.setName("张宏发");
person.setAge(29);
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream("E:\\test1.dat");
oos = new ObjectOutputStream(fos);
oos.writeObject(person);
System.out.println("序列化成功。");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//反序列化
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("E:\\test1.dat"));
} catch (IOException e) {
System.out.println("文件不存在。");
e.printStackTrace();
}
try {
Person person1 = (Person)ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
案例二
/*
序列化:将对象中的状态转化为字节流,以后可以通过这些值再生成相同状态的对象。
反序列化:根据序列化信息重建对象过程。
优点:数据持久化,即将数据保存至硬盘。
利用序列化实现远程通讯,在网络上传送对象的字节序列。
*/
public class PrintDemo {
public static void main(String[] args) throws Exception{
String s = "abc";//因为String只有一个属性,且被写死,所以其类的内容种类不会变化所以不需要序列化号码。
//序列化
FileOutputStream fileOutputStream = new FileOutputStream("E:\\test2.txt");
ObjectOutputStream oos = new ObjectOutputStream(fileOutputStream);
oos.writeObject(s);//该方法可以对参数指定的obj对象进行序列化,将得到的字节序列写入到目标输出流中。
//反序列化。
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\test2.txt"));
String s1 = (String)ois.readObject();
System.out.println(s1);
}
}