Java—IO
1. IO: java的IO是实现输入和输出的基础,可以方便地实现数据的输入和输出操作。在java中把不同的输入/输出源(键盘,文件,网络连接等)抽象的表述为 “流”(Stream)。通过流的形式允许java程序使用相同的方式来访问不同的输入/输出源。
2.IO分类:
1> 按流向分:输入流,输出流
- 输入流:只能从中读取数据,不能写入数据
输出流,只能向其写入数据,不能读取数据
这里的输入输出是相对而言的,在本地用程序读写文件时,我们始终以程序为中心,即程序从文件中读取时就是硬盘向内存中输入,程序向文件中写入就是内存向硬盘输出
但对于服务器和客户端来说,服务器将数据输出到网络中,这是Sever端程序的输出流。客户端从网络中读取数据,这是Client端的输入流
Java的输入流主要是InputStream和Reader作为基类,而输出流则主要是由OutputStream和Writer作为基类,他们都是一些抽象基类,无法直接创建实例
2> 按操作单元分:字节流和字符流
- 字节流:以InputStream和OutputStream作为基类
字符流:以Reader和Writer作为基类
其实现子类:FileInputStream和FileOutputStream可以对任意类型的文件进行读写操作,但 FileReader和FileWriter只能对纯文本文件进行操作
3> 按照流的角色可以分为节点流和处理流。
- 可以从/向一个特定的IO设备中读/写数据的流,称之为节点流。
- 处理流则对一个已存在的流进行连接和封装,通过封装后的流来实现数据的读/写功能。
- 从图中可以看出当使用处理流进行输入/输出时,程序不会直接连接到实际的数据源,没有和实际的输入和输出节点连接。实际上,处理流时 “连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更强大的读写功能
常用的处理流有:
- 缓冲流:在读入或写出时,对数据进行缓存,以减少I/O的次数:BufferedReader与BufferedWriter,BufferedInputStream和BufferedOutputStream
- 对象流:可以对实例对象进行读写操作。ObjectInputStream和ObjectOutputStream
- 数据流:按基本类型和String类型读写。DataInputStream和DataOutputStream
IO应用
1> IO体系的基类(InputStream/Reader,OutputStream/Writer)
InputStream和Reader是所有输入流的抽象基类,本身并不能创建实例来执行输入,但它们将成为所有输入流的模板,所以他们的方法是所有输入流都可使用的方法。
InputStream:
int read():从输入流中读取单个字节,返回所读取的字节数据(字节数据可直接转为int型)
- int read(byte []):从输入流中最多读取b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数。
- int read(byte[], int off, int len): 从输入流中最多读取len个字节的数据,并将其存储在数组b中,放入b数组时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字节数
Reader:
- int read():从输入流中读取单个字符,返回所读取的字符数据(可直接转为int类型)
- int read(char[]):从输入流中最多读取b.length个字符的数据,并将其存储在数组b中,返回实际读取的字符数。
- int read(char[] b, int off, int len):从输入流中最多读取len个字符的数据,并将其存储在数组b中,放入数组b时,并不是从数组七点开始,而是从off位置开始,返回实际读取的字符数。
对比InputStream和Reader所提供的方法,就不难发现这两个基类的功能基本是一样的。如何判断输入流的终点呢?当read()方法返回-1,就表明到了输入流的结束点
另外InputStream和Reader中还有一些移动指针的方法:
- void mark(int readAheadLimit); 在记录指针当前位置记录一个标记(mark)。
- boolean markSupported();
- 判断此输入流是否支持mark()操作,即是否支持记录标记。
- void reset(); 将此流的记录指针重新定位到上一次记录标记(mark)的位置。
- long skip(long n); 记录指针向前移动n个字节/字符。
OutputStream:
- void write(int c):将指定的字节/字符输出到输出流中,c可以代表字节,也可以代表字符
- void write(byte[]/byte[] buf):将字节数组,字符数组的数据输出到指定输出流中
- void write(byte[]/byte[] buf, int off, int len):将字节数组/字符数组从off位置开始,长度为len的字节/字符数组输出到输出流中
因为字符流直接以字符为操作单位,所以Writer可以用字符串代替字符数组,即以String对象为参数,Writer中还包括这两个方法:
- void write(String str):将str字符串里包含的字符输出到指定输出流中
- void write(String str, int off, int len):将str字符串里从off位置开始,长度为len的字符输出到指定输出流中
2> IO体系的基类文件流的使用(FileInputStream/FileReader,FileOutputStream/FileWriter)
- 前面说到的InputStream和Reader都是抽象类,本身不能创建实例,但他们分别有一个用于读取文件的输入流:FileInputStream和FileReader,他们都是节点流——会直接和指定文件关联,下面是二者使用的例子
- FileInputStream:
public class FileInputDemo {
public static void main(String[] args) throws IOException {
InputStream is = new FileInputStream(new File("d:\\test.txt"));
byte[] flush = new byte[1024];
int len;
while(-1 != (len = is.read(flush))){
System.out.println(new String(flush));
}
is.close();
}
}
- Reader
public class ReaderTest {
public static void main(String[] args) throws IOException {
Reader r = new FileReader(new File("d:\\test.txt"));
char[] flush = new char[1024];
int len;
while (-1 != (len = r.read(flush))){
System.out.println(new String(flush));
}
r.close();
}
}
上面程序最后用close()方法关闭该文件的输入流,与JDBC编程一样,程序里面打开的文件IO资源不属于内存的资源,垃圾回收机制无法回收该资源,所以应该显示地关闭打开的IO资源。JAVA 7 改写了所有的IO资源类,他们都实现了AutoCloseable接口,因此都可以通过自动关闭资源的try语句来关闭这些IO流
- FileOutputStream
public class FileOutputDemo {
public static void main(String[] args) throws IOException {
OutputStream os = new FileOutputStream(new File("d:\\test.txt"));
Scanner scan = new Scanner(System.in);
String str = scan.nextLine();
byte[] flush = str.getBytes();
os.write(flush);
os.close();
}
}
- Writer
public class WriterDemo {
public static void main(String[] args) throws IOException {
Writer writer = new FileWriter(new File("d:\\test.txt"));
Scanner scan = new Scanner(System.in);
String str = scan.nextLine();
writer.write(str);
//char[] chars = str.toCharArray();
//writer.write(chars);
writer.close();
}
}
3> 转换流的使用
转换流作用:
- 1 . 是字符流和字节流之间的桥梁
- 2 . 可对读取到的字节数据经过指定编码换成字符
- 3 . 可对读取到的字符数据经过指定编码转成字节
示例:1.读取键盘中的每一行内容,并写到文本中
public class InputDemo {
public static void main(String[] args) throws IOException {
String file = "d:\\test.txt";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(new File(file)),"utf-8");
osw.write("这是要保存的字符");
osw.close();
InputStreamReader isr = new InputStreamReader(new FileInputStream(new File(file)),"utf-8");
char[] buf = new char[1024];
isr.read(buf);
System.out.println(buf);
}
}
4> 对象流(ObjectInputStream/ObjectOutputStream)
- 思考一个问题,如果我们要写入文件内的数据不是基本数据类型(使用DataInputStream),也不是字符型或字节型数据,而是一个对象,应该怎么写?这个时候就用到了处理流中的对象流。下面是一个对象的写入文件和从文件中读取
- 反序列化 输入流:ObjectInputStream
- 序列化 输出流 ObjectOutputStream
注意:
- 先序列化后反序列化; 反序列化顺序必须与序列化一致
- 不是所有对象都可以序列化 必须实现 java.io.Serializable
- 不是所有属性都需要序列化 不需要序列化的属性 要加 transient
public class Person implements Serializable {
private String name;
private int age;
private transient String intro; //不需要序列化
public Person(String name, int age, String intro) {
this.name = name;
this.age = age;
this.intro = intro;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getIntro() {
return intro;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setIntro(String intro) {
this.intro = intro;
}
public String toString(){
return name + " " + age + " " + intro;
}
}
进行序列化和反序列化后:
public class WriteObject {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(
new File("d:\\test.txt"))));
oos.writeObject(new Person("shi",13,"A"));
oos.writeObject(new Person("zhang",15,"B"));
oos.writeObject(new Person("hao",18,"C"));
oos.close();
ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(
new FileInputStream(new File("d:\\test.txt"))));
Person p1 = (Person) ois.readObject();
Person p2 = (Person) ois.readObject();
Person p3 = (Person) ois.readObject();
ois.close();
System.out.println(p1);
System.out.println(p2);
System.out.println(p3);
}
}
- 序列化后文件内容:
反序列化后读到的对象:
我们可以发现,自定义类中被transient 标记的数据将不被序列化和反序列化,而且自定义类也必须要实现Serializable接口。另外要注意,序列化时的参数类型和反序列化时的返回类型都是Object类型,所以在反序列化接收类对象数据时要用强制类型转换
5> 缓冲流(BufferedInputStream/BufferedReader,BufferedOutputStream/BufferedWriter)
在原有的节点流对象外部包装缓冲流,为IO流增加了内存缓冲区,增加缓冲区的两个目的:
- 1 .允许IO一次不止操作一个字符,这样提高整个系统的性能
- 2 .由于有缓冲区,使得在流上执行skip,mark和reset方法都成为可能
缓冲流要套接在节点流之上,对读写的数据提供了缓冲功能,增加了读写的效率,同时增加了一些新的方法。例如:BufferedReader中的readLine方法,BufferedWriter中的newLine方法
- 四种换缓存流,常用的构造方法:
//字符输入流
BufferedReader(Reader in)//创建一个32字节的缓冲区
BufferedReader(Reader in, int size)//size为自定义缓存区的大小
//字符输出流
BufferedWriter(Writer out)
BufferedWriter(Writer out, int size)
//字节输入流
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)
//字节输出流
BufferedOutputStream(OutputStream in)
BufferedOutputStream(OutputStream in, int size)
对于输出的缓冲流,BufferedWriter和BufferedOutputStream会先在内存中缓存,使用flush方法会使内存中的数据立刻写出
- 示例:
public class BufferedDemo {
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(new File("d:\\test.txt")));
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(new File("d:\\test_01.txt")));
byte[] buf = new byte[64];
int len = 0;
while (-1 != (len = bis.read(buf))){
bos.write(buf, 0, len);
bos.flush();
}
bis.close();
bos.close();
}
}