首先我们先来看一下javaIO流的构成体系,第一次看到IO的整个体系可能会被吓住,但是面向对象的知识告诉我们,只要搞清楚父类或者抽象类的一些常用方法就可以上手使用了,对于不同的子类可以根据需要再去了解其具备的特殊功能。
IO流有四大抽象类,分别为InputStream、OutputStream、Reader、Writer。其中InputStream和OutputStream为处理字节的节点流,Reader和Writer为处理字符的字符流。关于何时使用字符流何时使用字节流我们下面再进行介绍。
首先列出抽象类中的常用方法
读取数据的基本方法
abstract int read() //读取一个字节数据,并返回读到的数据,如果返回-1则表示读到了文件末尾
int read(byte[] b)//读取数据到字节数组中,同时返回实际读到的字节数,如果返回-1,表示读到了输入流的末尾
int read(byte[] b, int off, int len) //将数据读入一个字节数组,同时返回实际读取的字节数。如果返回 -1,表示读到了输入流的末尾。off 指定在数组 b 中存放数据的起始偏移位置;len 指定读取的最大字节数
关闭字节流方法
void close() //通知操作系统关闭输入流
写数据常用方法
abstract void write(int b) //往输出流中写入一个字节。
void write(byte[] b) //往输出流中写入数组b中的所有字节。
void write(byte[] b, int off, int len)//往输出流中写入数组 b 中从偏移量 off 开始的 len 个字节的数据。
其他常用方法
void flush() //刷新输出流,强制缓冲区中的输出字节被写出。
void close() //关闭输出流,释放和这个流相关的系统资源。
输入流在向外部写出数据前会将数据暂存在内部缓冲区,缓冲区满后再向外输出,如果不调用flush方法可能会导致数据残留于缓冲区。所以最好在写出数据后及时刷新缓冲区。
读数据方法
public int read() throws IOException// 读取一个字符,返回值为读取的字符。
public int read(char cbuf[]) throws IOException; //读取一系列字符到数组 cbuf[]中,返回值为实际读取的字符的数量。
public abstract int read(char cbuf[],int off,int len) throws IOException; //读取 len 个字符,从数组 cbuf[] 的下标 off 处开始存放,返回值为实际读取的字符数量,该方法必须由子类实现。
关闭流方法同字节输入流。
public void write(int c) throws IOException; //写单个字符
public void write(char cbuf[]) throws IOException; //将字符数组 cbuf[] 写到输出流 。
public abstract void write(char cbuf[],int off,int len) throws IOException; //将字符数组cbuf[]中的从索引为off的位置处开始的len个字符写入输出流 。
public void write(String str) throws IOException; //将字符串str中的字符写入输出流 。
public void write(String str,int off,int len) throws IOException; //将字符串 str 中从索引 off 开始处的 len 个字符写入输出流 。
通过对比字符流和字节流常用方法我们可以看到,字节流操作的基本元素类型为byte类型,而字符流操作的基本元素类型为char类型。所以对于纯文本文件这种牵扯到字符集问题的IO操作,使用Reader和Writer更加合适,其内部会自动帮我们处理字符集问题。
输入流操作流程
(1)创建数据源
(2)选择合适的输入流
(3)读取数据
(4)关闭流
输出流操作流程
(1)创建数据源
(2)选择合适的输出流
(3)输出数据
(4)关闭流
文件读取的基本操作
public static void main(String[] args) {
File file = new File("D:\\File\\NoteBook\\IOLearn\\src\\abc.txt");//1、创建源
InputStream inputStream = null;
try {
inputStream = new FileInputStream(file); //2、选择流
byte [] arr = new byte[10];
int len = 0;
while(-1!=(len = inputStream.read(arr))) {
//3、读取
String str = new String(arr,0,len);
System.out.println(str);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(inputStream!=null) {
try {
inputStream.close();//4、关闭
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
文件写入的基本操作
public static void main(String[] args) {
File destinationFile = new File(filePath);
OutputStream os = null;
byte[] byteArr = "io is so easy!".getBytes();
try {
os = new BufferedOutputStream(new FileOutputStream(destinationFile));
os.write(byteArr);
os
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如果我们将文件读入和文件写入相结合便可以实现文件拷贝的功能,下面给出拷贝函数
public static void copyFile(String srcPath,String dstPath) {
File scrFile = new File(srcPath);
File destFile = new File(dstPath);
InputStream is = null;
OutputStream os = null;
try {
is = new FileInputStream(scrFile);
os = new FileOutputStream(destFile);
byte[] flush = new byte[1024*10];
int len = 0;
while(-1!=(len=(is.read(flush)))) {
os.write(flush, 0, len);
os.flush();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
try {
os.close();
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
相比于文件字节流,字符流操控的是单个字符,避免了中英文不同编码而导致的乱码现象。
下面给出示例代码,通过对既含有中文又含有英文的文件进行读取,来对比两种输入流的不同。
public static void compareFileReaderWithFileInputStream(String filePath){
File file = new File(filePath);
Reader reader = null;
InputStream is = null;
try {
reader = new FileReader(file);
is = new FileInputStream(file);
char charBuff[] = new char[1024];
byte byteBuff[] = new byte[1024];
int len = -1;
while((len=reader.read(charBuff))!=-1){
for(int i = 0; i<len; i++)
System.out.print(charBuff[i]);
System.out.println();
}
len = -1;
while((len=is.read(byteBuff))!=-1){
for(int i = 0; i<len; i++)
System.out.print((char) byteBuff[i]);
System.out.println();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(reader != null){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
带读取的文件内容为:
io is so easy
io 非常简单
字符流输出结果为:
可以看到使用字符流可以避免中英文编码方式不同而带来的乱码问题。
字符输入流的使用方式可以类比字节输入流,这里不再累述。
字节数组流和文件流的不同在于字节数组流操作的数据源为内存空间,它的作用就是将某一段内存搬运到指定位置。所以说对于字节数组流,可以不对其进行关闭。(其关闭方法为空方法,保留该方法只是为了保证风格一致)
字节数组输入流
public static void main(String[] args) throws IOException {
//字节数组输入
String s = "string for byteArray"; //创建源
byte [] src = s.getBytes();
InputStream is = null;
is = new BufferedInputStream(new ByteArrayInputStream(src));//选择流
int len = -1;
byte buffer[] = new byte[1024];
while((len = is.read(buffer))!=-1){
//读取
for(int i = 0; i<len; i++)
System.out.print((char)buffer[i]);
}
}
字节数组输出流
public static void main(String[] args){
//字节数组输出
ByteArrayOutputStream bao = new ByteArrayOutputStream();
OutputStream os = new BufferedOutputStream(bao);
os.write("woshiyigedayingxiong".getBytes());
os.flush();
byte dst[] = bao.toByteArray(); //数据会写入内部缓冲区,使用toByteArray()方法可获取数据
for(byte c:dst){
System.out.print((char)c);
}
}
IO操作是程序整体性能的一大瓶颈,为了尽量加快IO速度,javaIO库中实现了缓冲流,借用缓冲读取或写入的方式提高IO性能。缓冲流属于处理流,其操作对象为节点流,其底层实现实际上是通过使用装饰器模式增强了节点流的功能。
其使用方式也非常简单,只需要在对应节点流上套一层缓冲流即可。
以文件输入流为例,仅仅在第二步选择流处套上一层缓冲流即可。
public static void main(String[] args) {
File file = new File("D:\\File\\NoteBook\\IOLearn\\src\\abc.txt");//1、创建源
InputStream inputStream = null;
try {
inputStream = new BufferedInputStream(new FileInputStream(file)); //2、选择流,此处在文件输入流外套了一层缓冲流,增强了文件输入流的功能
byte [] arr = new byte[10];
int len = 0;
while(-1!=(len = inputStream.read(arr))) {
//3、读取
String str = new String(arr,0,len);
System.out.println(str);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(inputStream!=null) {
try {
inputStream.close();//4、关闭
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
字符缓冲流的使用方法类似。只是操作对象更换为字符流而已。
虽然名字为对象流,但该流也可以操作基本数据类型。在操作对象时需注意,被操作的对象必须实现Serializable接口。ObjectOutputStream&ObjectInputStream构造函数的输入参数分别为OutputStream类型和InputStream类型
当使用文件输入输出流作为参数输入时,可以实现对象的本地持久化,当使用字节数组输入输出流的时候一般用于网络传输。
下面的代码实现对象输出到字节数组中,然后还原为对象的功能。
class Student implements Serializable{
public String name;
public int id;
public Student(String name, int id) {
this.name = name;
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
}
public class IO03 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream oot = new ObjectOutputStream(outputStream);
oot.writeObject(new Student("li",45));
byte [] arr = outputStream.toByteArray();
System.out.println(arr.length);
ObjectInputStream oit = new ObjectInputStream(new ByteArrayInputStream(arr));
while(true){
try{
Student temp = (Student)oit.readObject();
System.out.println(temp);
}catch (EOFException e) {
break;
}
}
}
}