java标准的IO操作
使用java IO我们可以对外界设备以相同的方式进行读写,完成数据交换
同一套操作,来操作不同的设备
java IO将"读"与"写"按照方向进行了划分:
输入:从外界到程序的方向,用于让程序获取外界数据
因此输入是"读"数据的操作
输出:从程序到外界的方向,用于将数据"写"出的操作.
输入流(InputStream)、输出流(OutputStream),流动的是字节流
java IO以"流"的形式表示读写功能
InputStream是所有字节输入流的父类,其定义了基础的读取方法
通过输入流我们可以连接上外界设备从而读取该设备数据
常用的方法如下:
- int read()
读取一个字节,以int形式返回,该int值的"低八位”有效,若返回值为-1则表示EOF - int read(byte[] d)
尝试最多读取给定数组的length个字节并存入该数组,返回值为实际读取到的字节量。
OutputStream是所有字节输出流的父类,其定义了基础的写出方法
常用的方法如下:
- void write(int d)
写出一个字节写的是给定的in的”低八位” - void write(byte[ d)
将给定的字节数组中的所有字节全部写出
节点流(低级流)与处理流(高级流)
java将流分为两大类:节点流与处理流
按照流是否直接与特定的地方(如磁盘、内存、设备等)相连,分为节点流和处理流两类。
节点流:也称为低级流
可以从或向一一个特定的地方(节点)读写数据。
真实连接程序与数据源的"管道",用于实际搬运数据的流
读写一定是建立在节点流的基础上进行的
处理流:又称为“高级流”或“过滤流”
是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。
高级流不能独立存在,必须连接在其他流上,目的是当数据 流经当前流时对其做某些加工处理,简化我们读写数据时的相应操作
可以理解为:处理水的热水器、净水器,单独存在无意义,连接在其他流上(低级流和高级流)才有用
流的链接:
处理流的构造方法总是要带一个其他的流对象做参数,一个流对象经过其他流的多次包装,称为流的链接。
换种说法
实际使用IO时,我们通常会串联若干的高级流最终连接到低级流上,使得读写数据以流水线式加工处理完成,这个操作称为“流的连接”,这也是IO的精髓所在
相当于把热水器或净水器连接到水管上(高级流连低级流)
把热水器连接到净水器上(高级流连高级流)
流动的是数据
这种设计模式是装饰者模式
在不改变原有类的情况下,增强其能力
文件流(一种低级流)
文件流是一对低级流,作用是连接到文件上,用于读写文件数据
java.io.FileOutputStream:文件输出流
java.io.FileInputStream:文件输入流
文件流提供的构造方法:
FileOutputStream(File file)
FileOutputStream(String path)
以上两种创建方式,默认为覆盖写模式
即:若指定的文件已经存在,那么会将该文件原有数据全部删除,然后再将新数据写入文件
FileOutputStream(File file,boolean append)
FileOutputStream(String path,boolean append)
以上两种构造器允许再传入一个boolean值类型的参数,
如果该值为true时,文件输出流就是追加写模式
即:数据中原有数据都保留,新内容会被追加到文件末尾
文件输出流:
package io;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class FosDemo {
public static void main(String[] args) throws UnsupportedEncodingException, IOException {
FileOutputStream fos = new FileOutputStream("./fos.txt",true);
//String line = "回首,掏~";
//fos.write(line.getBytes("UTF-8"));
//fos.write("鬼刀一开看不见~走位~走位~".getBytes("UTF-8"));
fos.write("手".getBytes("UTF-8"));
System.out.println("写出完毕!");
fos.close();
}
}
文件输入流:
package io;
import java.io.FileInputStream;
import java.io.IOException;
public class FISDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("fos.txt");
byte[] data = new byte[1000]; //提供读取的容器
int len = fis.read(data); //记录读取的实际长度
//String str = new String(data,"utf-8").trim();
//String 还支持另外一种重载(字节数组,起始下标,结束下标,编码格式)
String str = new String(data,0,len,"utf-8");
System.out.println(str);
fis.close();
}
}
文件流与RAF的区别:
RAF是基于指针的随机读写形式
可以对文件任意位置进行读或写的操作,可以做到对文件部分数据覆盖等操作,读写更灵活。
文件流是基于java IO的标准读写,而IO是顺序读写模式
即:只能向后写或读数据,不能回退,没有指针
单从读写灵活度来讲RAF是优于文件流的
但是文件流可以基于java IO的流连接完成一个复杂数据的读写
这是RAF做不到的,选择这两种方法时,根据实际需求不用过于纠结
练习:使用文件流完成文件的复制工具
package io;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 使用文件流完成文件的复制工具
* @author Tian
*
*/
public class CopyDemo {
public static void main(String[] args) throws IOException {
//创建存取数据的字节数组(数据容器,相当于交换变量值时的第三方变量)
byte[] data = new byte[1024*10];
//记录运行开始时间
long start = System.currentTimeMillis();
//读取源文件,创建输入流(注意输入是从文件输入到现在的这个代码程序)
FileInputStream fis = new FileInputStream("./image.jpg");
//写入复制文件,创建输出流(注意输出是从现在这个程序写出到文件)
FileOutputStream fos = new FileOutputStream("./image_copy.jpg");
int len = -1; //记录读取的实际长度
//循环写入每次的10k大小字节数组
while((len = fis.read(data))!=-1){//读取信息并返回读取长度存储到len中
//将字节数组的信息写入复制的文件
fos.write(data,0,len);
}
//读取完毕,关闭输入流
fis.close();
//写出完毕,关闭输出流
fos.close();
//记录运行结束时间
long end = System.currentTimeMillis();
//程序运行结束,输出时间
System.out.println("复制完毕,耗时:"+(end-start)/1000+"s");
}
}
常见的高级流(加工数据的工具)
一、缓冲流(在流连接中的提高速读写效率)
java.io.BufferedOutputStream
java.io.BufferedInputStream
缓冲流是一对高级流,在流连接中的作用是提高速读写效率
使得我们在进行读写操作时用单字节读写也能提高读写的效率
在向硬件设备做写出操作时,增大写出次数无疑会降低写出效率
为此我们可以使用缓冲输出流来一 次性批量写出若干数据,减少写出次数来提高写出效率
BufferedOutputStream缓冲输出流内部维护着一个缓冲区(一个8k字节数组)
每当我们向该流写数据时,都会先将数据存入缓冲区
当缓冲区已满时,缓冲流会将数据一次性全部写出
无论我们使用缓冲流进行何种读写(单字节或块读写),最终都会被缓冲流转换为块读写,来提高效率
package io;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyDemo2 {
public static void main(String[] args) throws IOException {
//创建输入流
FileInputStream fis = new FileInputStream("./image.jpg");
//创建输入流的缓冲流,传入参数为输入流
BufferedInputStream bis= new BufferedInputStream(fis);
//创建输出流
FileOutputStream fos = new FileOutputStream("./image_copy.jpg");
//创建输出流的缓冲流,传入参数为输出流
BufferedOutputStream bos = new BufferedOutputStream(fos);
//复制时,一次一字节
int d = -1;
//对缓冲流进行操作就行了
while((d = bis.read())!=-1){
bos.write(d);
}
//关闭时,关闭缓冲流会自动关闭其附身的低级流
bis.close();
bos.close();
}
}
缓冲输出流的缓冲区问题:
使用缓冲输出流可以提高写出效率,但是这也存在着一个问题,就是写出数据缺乏即时性。
有时我们需要在执行完某些写出操作后,就希望将这些数据确实写出
而非在缓冲区中保存直到缓冲区满后才写出
这时我们可以使用缓冲流的一个方法flush
void flush() 清空缓冲区,将缓冲区中的数据强制写出
flush方法是OutputStream中定义的方法
所有的输出流都具有该方法,但是只有缓冲流的该方法实现了,有实际意义
其他的流具有该方法的目的是在流连接中传递缓冲操作给缓冲流
flush的作用是将缓冲流中已经缓存的数据一次性写出
频繁的调用flush方法会提高写出次数从而降低写出效率,但是能保证数据写出的及时性
所以需要根据实际需求选择,比如文件复制不用,但是聊天需要用flush
package io;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class BOS_flush {
public static void main(String[] args) throws UnsupportedEncodingException, IOException {
FileOutputStream fos = new FileOutputStream("./bos.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write("此剑抚平天下不平事,此剑无愧世间有愧人".getBytes("utf-8"));
bos.flush();
System.out.println("写出完毕!");
//close时也会自动调用flush,class方法中调用了flush方法
bos.close();
}
}
二、对象流(在流连接中读写java对象)
java.io.ObjectOutputStream
java.io.ObjectInputStream
对象流是一种高级流,在流连接中的作用是方便读写java对象(对象与字节的转换由对象流完成)
将java对象按照其结构,转换成字节流,存入文件
对象流的三大块:类的创建、类对象的写入(对象输出流)、类对象的读出(对象输入流)
1、对象流中访问类的创建
使用当前类的实例测试对象流的对象读写操作时
注意:使用对象流的类如果报java.io.NotSerializableException
说明该类要实现Serializable的接口,该接口为序列化操作
ps:
序列化:把一组数据按一个结构转化为一组字节信息的过程
Serializable:是签名接口,我们开发一般不用,这种接口一般是java做的
签名接口:没有方法内容,在编译时,编译器为自动添加方法,即:在java文件编译成class文件后才会出现
签名接口:没有方法内容,在编译时,编译器为自动添加方法,即:在java文件编译成class文件后才会出现
在实现序列化接口后,该类会提示添加一个序列化版本号
**序列化版本号:**java会默认指定加上,这个会影响反序列化,写出与读入的版本号要一致,否则还原不会成功
建议自己生成版本号,不要让编译器自动添加;自己定义,版本号不会随修改类的变化而变化
**transient关键字:**在序列化中,用于忽略该关键字修饰的属性
被修饰的属性将不会被忽略,不会序列化记录,可以达到省略存储空间的目的,减少不必要的开销
但是相应的该属性的信息也没有了,恢复了默认值
代码示例
package io;
import java.io.Serializable;
import java.util.Arrays;
//实现Serializable,记得要导包
public class Person implements Serializable{
private static final long serialVersionUID = 1L;//序列化版本号,java会默认指定加上
private String name;
private int age;
private String gender;
private String[] otherInfo;
public Person() {}
public Person(String name, int age, String gender, String[] otherInfo) {
this.name = name;
this.age = age;
this.gender = gender;
this.otherInfo = otherInfo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String[] getOtherInfo() {
return otherInfo;
}
public void setOtherInfo(String[] otherInfo) {
this.otherInfo = otherInfo;
}
@Override
public String toString() {
return name+","+age+","+gender+","+Arrays.toString(otherInfo);
}
}
2、类对象的写入(对象输出流)
步骤:
- 创建需要写入文件的实例化对象
- 创建文件输出流(低级流),传入文件路径
- 创建对象输出流(高级流),传入低级流
- 将对象通过对象流写入文件,调用对象流的写入对象的方法(oos.writeObject§;)
- 关闭对象输出流
注意事项:
- 在第四步中要注意对象流的写入方法方法可能抛出:NotSerializableException
这说明写出的对象所属的类没有实现Serializable接口,我们需要在该类中实现该接口 - 写入文件后发现该文件的实际数据量比当前对象保存的内容要大
这是因为这组字节除了包含了该对象的数据外,还含有这个对象的结构信息
如果想减少不必要的开销,我们可以用 transient 关键字修饰不必要的属性
代码示例
package io;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/**
* 对象输出流
* @author Tian
*
*/
public class OOSDemo {
public static void main(String[] args) throws IOException {
/*
* 将一个Person实例写入文件Person.obj中
*/
//实例化对象
String name = "吴素";
int age = 22;
String gender = "女";
String[] otherInfo = {"陆地剑仙","佩剑","大凉龙雀","此剑抚平天下不平事","此剑无愧天下有愧人"};
Person p = new Person(name,age,gender,otherInfo);
//创建文件输出流
FileOutputStream fos = new FileOutputStream("./Person.obj");
//创建对象输出流
ObjectOutputStream oos = new ObjectOutputStream(fos);
//写出数据,调用对象输出流的写入对象方法
//该方法可能抛出:NotSerializableException
//当写出的对象所属的类没有实现Serializable接口时就会抛出该异常
oos.writeObject(p);
/*
* 写入文件后发现该文件的实际数据量比当前对象保存的内容要大
* 这是因为这组字节除了包含了该对象的数据外
* 还含有这个对象的结构信息
*/
System.out.println("写出完毕!");
//关闭高级流
oos.close();
}
}
3、类对象的读出(对象输入流)
步骤:
- 创建文件输入流(低级流),传入文件路径
- 创建对象输入流(高级流),传入低级流
- 创建引用,接收读取的对象,通过ois.readObject()方法读取对象
- 关闭对象输入流
注意事项:
- 在第三步中,我们需要先创建一个相应对象,并且读取出来的对象要注意强转为我们新建引用的对象
- 注意如果文件中的数据不是相应的数据类型会报ClassNotFoundException的异常
ps. 在进行文件对象的读取时,我们进行了反序列化操作(把字节转换为对应数据)
代码示例:
package io;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* 对象输入流
* @author Tian
*
*/
public class OISDemo {
public static void main(String[] args) throws ClassNotFoundException, IOException {
/*
* 将Person.obj文件中的对象读取出来
*/
//创建文件输入流
FileInputStream fis = new FileInputStream("./Person.obj");
//创建对象输入流
ObjectInputStream ois = new ObjectInputStream(fis);
//读入数据
//Object p = ois.readObject();
//需要强转为对应的对象类型
//要注意,如果文件中的数据不是相应的数据类型会报ClassNotFoundException的异常
//这里进行了反序列化:把字节转换为对应数据
Person p = (Person) ois.readObject();
System.out.println(p);
ois.close();
}
}
ps.文件流、缓冲流、对象流为字节流