IO流
1.IO流初识
IO就是input和output,所以IO流就可以理解为输入输出流。
而流是指一连串流动的字符,是以先进先出方式发送信息的通道。
IO流的作用就是当我们的Java程序需要和外界(文件,程序…)进行通信时为我们提供一种机制,而这种机制就是IO流。
1.1IO流分类
按流向分:输出流:OutputStream和Writer为基类
输入流:InputStream和Reader为基类
按处理数据单元划分:
字节流:
字节输入流:InputStream基类
字节输出流:OutputStream基类
字符流:
字符输入流:Reader基类
字节输出流:Writer基类
(字节流是 8 位通用字节流,字符流是16位Unicode字符流)
2.字节流
分为,字节输出流(Outputstream)和字节输入流(Inputstream)
2.1字节输出流(OutputStream)
java.io.OutputStream是一个抽象类,也是所有字节输出流的父类。作用是将指定的内容写入到目的地。
关于字节输出流的基本方法
- public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
- public void flush():刷新此输出流并强制任何缓冲的输出字节被写出。
- public void write(byte[] b):将 b.length个字节从指定的字节数组写入此输出流。
- public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。 也就是说从off个字节数开始读取一直到len个字节结束
- public abstract void write(int b) :将指定的字节输出流。
FileOutputStream
我们学习io流最主要的就是为了文件的传输与接受,所以我们以FileOutputStream为例。首先是FileOutputStream的构造方法
- public FileOutputStream(File file):根据File对象为参数创建对象。
- public FileOutputStream(String name): 根据名称字符串为参数创建对象。
FileOutputStream outputStream = new FileOutputStream("abc.txt");//1
File fil = new File("abc.txt");
FileOutputStream outputStream = new FileOutputStream(fil);//2
注意:在创建一个输出流对象时就是在为你的内容创建一个目的地,而且即使你没有该文件,系统也会为你自动创建出该文件。同时如果该文件里已经有数据则该数据将会被清空。
FileOutputStream写出数据的方法write();
- public void write(int b)
- public void write(byte[] b)
- public void write(byte[] b,int off,int len) //从
off
索引开始,len
个字节
前面两个我就不写代码了,就是正常的调用,我写一下第三种的。
public static void main(String[] args) throws IOException {
FileOutputStream f = new FileOutputStream("abc.txt");
byte[] b = "abcde".getBytes();
f.write(b,2,2);
f.close();
}
结果为:cd(也就是从下标为2的开始,取两个字节)
注意:io流使用完毕后,必须调用close方法关闭,否则会造成系统资源浪费。
FileOutputStream实现数据追加续写
因为每次创建一个输出流都会对目标文件的内容进行清空,所以有两种方法可以实现续写。
public FileOutputStream(File file, boolean append)
public FileOutputStream(String name, boolean append)
代码
public static void main(String[] args) throws IOException {
FileOutputStream f = new FileOutputStream("abc.txt",true);
byte[] b = "abcde".getBytes();
f.write(b);
f.close();
}
原文件:cd
现文件:cdabcde
也就是说true代表保留原文件内容,false(默认)清空原文件内容
2.2字节输入流(InputStream)
java.io.InputStream是一个抽象类,也是所有字节输入流的父类。作用是将读取目标文件的内容。
另外,对于所有的输入流而言,你所写的文件名必须存在,否则会报异常。
构造方法与字节输出流相同。
关于字节输入流的基本方法
- public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
- public abstract int read(): 从输入流读取数据的下一个字节,读取不到返回-1
- public int read(byte[] b): 该方法返回的int值代表的是读取了多少个字节,读到几个返回几个,读取不到返回-1
主要讲一下后面两个方法
read():
public static void main(String[] args) throws IOException{
FileInputStream fil = new FileInputStream("abc.txt");
int b ;
while ((b = fil.read())!=-1) {
System.out.println((char)b);
}
fil.close();
}
结果:cda
read(byte[ ] b):
public static void main(String[] args) throws IOException{
FileInputStream fil = new FileInputStream("abc.txt");
int len ;
byte[] b = new byte[3];
while (( len= fil.read(b))!=-1) {
System.out.println(new String(b));
}
fil.close();
}
结果:cda
bcd
ecd
注意:原文件abc.txt是cdabcde,多出来的cd是因为在打印是输出的是数组,当达到e后数组中的值都是已经存在的,是后面的数顶掉了前面的,由于e后面已经没有字节了,就会保留原来的字节。所以我们用就会用到另一种方法
public static void main(String[] args) throws IOException{
FileInputStream fil = new FileInputStream("abc.txt");
int len ;
byte[] b = new byte[3];
while (( len= fil.read(b))!=-1) {
System.out.println(new String(b,0,len));
}
fil.close();
}
结果为;cda
bcd
e
在Java中使用数组读取文件的效率更高
3.字符流(Reader和Writer)
字符流的由来:因为数据编码的不同,因而有了对字符进行高效操作的流对象,字符流本质其实就是基于字节流读取时,去查了指定的码表,而字节流直接读取数据会有乱码的问题(读中文会乱码),必须借用String类才能正常输出。
构造方法都是:
- 类名(File file)创建一个新的 FileReader ,给定要读取的File对象
- 类名 (String fileName): 创建一个新的 FileReader ,给定要读取的文件的字符串名称。
3.1Reader(字符输入流)
java.io.Reader抽象类是字符输入流的所有类的超类(父类),作用是读取字符信息到内存中。
常见的方法
- public void close() :关闭此流并释放与此流相关联的任何系统资源。
- public int read():从输入流读取一个字符。
- public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组cbuf中
上面两个同InputStream用法相同,只是读取的从字节变成了字符
read(char[] cbuf):
3.2Writer(字符输出流)
java.io.Writer抽象类是字符输出流的所有类的父类,作用是将指定的字符信息写出到目的地。
字符输出流的基本方法:
1、void write(int c) 写入单个字符。
2、void write(char[] cbuf)写入字符数组。
3、 abstract void write(char[] cbuf, int off, int len)写入字符数组的某一部分,off数组的开始索引,len写的字符个数。
4、 void write(String str)写入字符串。
5、void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
6、void flush()刷新该流的缓冲。
7、void close() 关闭此流,但要先刷新它。
以FileWriter为例
字符输出流与字节输出流不同之处在于。字节输出流如果没有close资源,内容可以保存到文件,但是字符输出流不会保存到文件。而是在缓冲区。
关于close和flush
因为存在内置的缓冲区,所以我们不关闭数据是无法保存到文件中,此时我们就需要用到flush方法——刷新,意思就是,将缓冲区的内容保存到文件中,使得缓冲区清空。此时就可以实现同时使用流和保存数据。
以图为例
这里报出异常,显示流已经关闭
在文件中内容和之前是一样的。
写在最后:字符流,只能操作文本文件,不能操作图片,视频等非文本文件。当我们单纯读或者写文本文件时 使用字符流 其他情况使用字节流
4.转换流
由来:因为字符编码的种类很多,所以经常会出现编码表不匹配导致数据乱码的情况,所以出现了转换流
4.1InputStreamReader类——字节流到字符流
构造方法
- InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。
- InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。
以代码为例
我的文件编码为UTF-8,默认编码也是UTF-8,所以可以正常读取,而当使用GBK是就是乱码。同理当你电脑的文件编码为其他时,使用默认也会出现乱码。
4.2OutputStreamWriter类——字符流到字节流
构造方法
OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。
作用就是对目标输出指定编码的数据
以代码为例
uzi文件
uzi2文件
5.对象流和对象序列化、持久化
5.1对象流
有的时候,我们可能需要将内存中的对象持久化到硬盘上,或者将硬盘中的对象信息读到内存中,这个时候我们需要使用对象输入输出流。
5.2对象序列化与反序列化、持久化
用一个字节序列可以表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象。
持久化就是将对象永久保存,借助于序列化。
5.2.1ObjectOutputStream类
java.io.ObjectOutputStream 类,作用时将Java对象的原始数据类型写出到文件,实现对象的持久存储。
构造方法:
public ObjectOutputStream(OutputStream out): 创建一个指定OutputStream的ObjectOutputStream。
实现序列化:
以代码为例
public class People implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
public String name;
public String birthday;
public transient int age;
public void getname() {
System.out.println(name);
}
}
public class Test{
public static void main(String [] args) {
People p = new People();
p.name = "zhangsan";
p.birthday= "5th July";
p.age = 20;
try {
FileOutputStream fileOut = new FileOutputStream("people.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(p);
out.close();
fileOut.close();
System.out.println("已被序列化");
} catch(IOException i) {
i.printStackTrace();
}
}
}
- transient瞬态修饰成员,不会被序列化
- 一个对象要想序列化,必须满足两个条件:
a.该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException 。
b.该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。
5.2.2ObjectInputStream类
ObjectInputStream反序列化流,作用是将之前使用ObjectOutputStream序列化的原始数据恢复为对象
构造方法
public ObjectInputStream(InputStream in): 创建一个指定InputStream的ObjectInputStream
实现反序列化
以代码为例
public static void main(String [] args) {
People p = null;
try {
FileInputStream fileIn = new FileInputStream("people.txt");
ObjectInputStream in = new ObjectInputStream(fileIn);
p = (People) in.readObject();
in.close();
fileIn.close();
}catch(IOException i) {
i.printStackTrace();
return;
}catch(ClassNotFoundException c) {
System.out.println("没有找到People ");
c.printStackTrace();
return;
}
System.out.println("Name: " + p.name);
System.out.println("birthday: " + p.birthday);
System.out.println("age: " + p.age);
}
- public final Object readObject () : 读取一个对象。
- 在进行反序列化时,它必须是能够找到class文件的类。如果找不到该类的class文件,则会抛出一个 ClassNotFoundException 异常。
- 当class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。
- Serializable 接口给需要序列化的类,提供了一个序列版本号。serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。