一、基本概念
1、流:在Java中,将不同输入输出设备之间的数据传输抽象表述为流;
2、IO流:Java中的“流”都位于java.io包中,称作IO(输入输出)流;
3、IO流分类:
a.根据数据传输方向,可分为输入流和输出流,程序从输入流中读取数据,向输出流中写入数据;
b.根据操作数据的不同,可分为字节流(InputStream、OutputStream)和字符流(Reader、Writer)。
4、InputStream:所有字节输入流的父类;
5、OutputStream:所有字节输出流的父类;
6、Reader:所有字符输入流的父类;
7、Writer:所有字符输出流的父类;
注意:上述四个类均为抽象类,不可被实例化。
二、不同输入输出方式性能测试
对同一文件(中文)用下列不同输入输出方式进行输出,对比复制文件所消耗的时间,测试效率:
1、单独使用字节流;
2、字节流+缓冲字节数组;
3、使用字节缓冲流;
4、字节缓冲流+缓冲字节数组;
5、字节数组输出流(ByteArrayOutputstream):该类会在创建对象时就创建一个byte型数组的缓冲区,写入数据时,该对象会将数据先写入缓冲区,最后一次性写入文件;
6、字符缓冲流 ;
package com.Liao.FileTest;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileCopy {
private static File file = new File("fileTest2\\西游记.txt");// 测试文件对象
private int count;
public static void main(String[] args) {
FileCopy fc = new FileCopy();
System.out.println("测试文件:" + file.getName() + "\t大小:" + file.length() / 1000 + "KB");
fc.test1();
fc.test2();
fc.test3();
fc.test4();
fc.test5();
fc.test6();
}
// 字节流
public void test1() {
try {
System.out.println("********方式一:字节流**********");
// 新建文件命名
String name = "fileTest2\\" + ++count + ".txt";
File file1 = new File(name);
long s1 = System.currentTimeMillis();// 测试开始,计时
// 创建输入输出流对象
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(file1);
// 读写数据
int len;
while ((len = fis.read()) != -1) {
fos.write(len);
fos.flush();
}
fis.close();
fos.close();
long s2 = System.currentTimeMillis();// 测试结束,计时
System.out.println("复制文件耗时:" + (s2 - s1) + "ms");
System.out.println("文件大小:" + file1.length() / 1000 + "KB");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 字节流+缓冲字节数组
public void test2() {
try {
System.out.println("********方式二:字节流+缓冲字节数组**********");
// 新建文件命名
String name = "fileTest2\\" + ++count + ".txt";
File file1 = new File(name);
long s1 = System.currentTimeMillis();// 测试开始,计时
// 创建输入输出流对象
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(file1);
// 读写数据
int len;
byte[] bs = new byte[1024];
while ((len = fis.read(bs)) != -1) {
fos.write(bs, 0, len);
// fos.flush();
}
fis.close();
fos.close();
long s2 = System.currentTimeMillis();// 测试结束,计时
System.out.println("复制文件耗时:" + (s2 - s1) + "ms");
System.out.println("文件大小:" + file1.length() / 1000 + "KB");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 字节缓冲流
public void test3() {
try {
System.out.println("********方式三:字节缓冲流**********");
// 新建文件命名
String name = "fileTest2\\" + ++count + ".txt";
File file1 = new File(name);
long s1 = System.currentTimeMillis();// 测试开始,计时
// 创建输入输出流对象
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(file1);
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
// 读写数据
int len;
while ((len = bis.read()) != -1) {
bos.write(len);
// bos.flush();//这里加强制输出会影响写入速度
}
bis.close();
bos.close();
long s2 = System.currentTimeMillis();// 测试结束,计时
System.out.println("复制文件耗时:" + (s2 - s1) + "ms");
System.out.println("文件大小:" + file1.length() / 1000 + "KB");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 字节缓冲流+缓冲字节数组
public void test4() {
try {
System.out.println("********方式四:字节缓冲流+缓冲字节数组**********");
// 新建文件命名
String name = "fileTest2\\" + ++count + ".txt";
File file1 = new File(name);
long s1 = System.currentTimeMillis();// 测试开始,计时
// 创建输入输出流对象
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(file1);
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
// 读写数据
int len;
byte[] bs = new byte[1024];
while ((len = bis.read(bs)) != -1) {
bos.write(bs, 0, len);
// bos.flush();//这里加强制输出会影响写入速度
}
bis.close();
bos.close();
long s2 = System.currentTimeMillis();// 测试结束,计时
System.out.println("复制文件耗时:" + (s2 - s1) + "ms");
System.out.println("文件大小:" + file1.length() / 1000 + "KB");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// ByteArrayOutputStream
public void test5() {
try {
System.out.println("******方式五:字节数组缓冲流+缓冲字节数组*******");
// 新建文件命名
String name = "fileTest2\\" + ++count + ".txt";
File file1 = new File(name);
long s1 = System.currentTimeMillis();// 测试开始,计时
// 创建输入输出流对象
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(file1);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 读写数据
int len;
byte[] bs = new byte[1024];
while ((len = fis.read(bs)) != -1) {
baos.write(bs, 0, len);
}
fos.write(baos.toByteArray());
fis.close();
baos.close();
fos.close();
long s2 = System.currentTimeMillis();// 测试结束,计时
System.out.println("复制文件耗时:" + (s2 - s1) + "ms");
System.out.println("文件大小:" + file1.length() / 1000 + "KB");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 字符缓冲流
public void test6() {
try {
System.out.println("********方式六:字符缓冲流**********");
// 新建文件命名
String name = "fileTest2\\" + ++count + ".txt";
File file1 = new File(name);
long s1 = System.currentTimeMillis();// 测试开始,计时
// 创建输入输出流对象
BufferedReader br = new BufferedReader(new FileReader(file));
BufferedWriter bw = new BufferedWriter(new FileWriter(file1));
// 读写数据
String str;
while ((str = br.readLine()) != null) {
bw.write(str);
bw.newLine();
}
br.close();
bw.close();
long s2 = System.currentTimeMillis();// 测试结束,计时
System.out.println("复制文件耗时:" + (s2 - s1) + "ms");
System.out.println("文件大小:" + file1.length() / 1000 + "KB");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果如下:
上述文件均读写成功,未出现乱码情况,对比耗时,可以发现:
1、单独使用字节流时,读写效率非常低,这是因为每次只读取一个字节,需要频繁操作文件;
2、使用缓冲数组来读取文件时,效率明显提升,这是由于一次可读取多个字节的数据,并将数据存入缓冲数组中,当数据读取完毕或字节数组满时,将字节数组中的数据写入文件;另外,当文件大小已知时,可以创建一个文件大小的数组(file.length( )),一次性读取数据,不过这样比较消耗内存。
3、使用字节缓冲流时(BufferedInputStream( )和BufferedOutputStream( ))时,效率较字节流方式有较大提升,这是由于缓冲字节流内部也定义了一个大小为8192的字节数组,读写方式与第二种类似;
4、使用字节缓冲流+缓冲字节数组,由于创建的读取缓冲字节数组只有1024长度,所以这种方式效率最高,其实本质上来说,这种方式也与第二种方式类似;
5、使用字节数组输出流+缓冲字节数组,ByteArrayOutpuStream对象是将写入的数据先存入缓冲区,最后一次性写入文件;
6、使用字符缓冲流读写数据,运用BufferedReader的readLine( )方法,按行读取和写入数据,readLine( )方法会逐个读取字符,当读到回车或换行符时,会将读到的内容作为一行的内容返回。
7、上述方法效率之所以提高,均是采用了缓冲区,或在读取时缓冲数据,或在写入时缓冲数据,以减少对文件的操作;
由于除第一组外,其它各组耗时相差较小,因此用其他几种方法读写一个较大的文件(圣经复制了n遍),比较耗时,另对缓冲字节数组做了一点调整,将数组长度设为文件大小(file.length( )),测试结果如下:
此时字符缓冲流读写效率最高,而第二种方式第二快,正好验证了前面的猜测。
三、字节流和字符流的区别
字节流和字符流的区别主要体现在以下几个方面:
1、最直观的体现:操作代码不同;
2、作用对象不同:字节流直接作用于文件,而字符流作用于缓冲区(内存),通过缓冲区操作文件。这个在下面通过一个简单的案例来说明。
3、主要区别:适用范围及处理方式。
a、适用范围:
字节流处理二进制数据,可处理任何类型文件,包括文本、图像、视频等,而字符流只能用来处理字符串或字符文件;
b、处理方式
字节流处理单元为1个字节,操作字节和字节数组;字符流处理的单元为2个字节的Unicode字符,操作字符、字符数组或字符串。
字节流是最基础的数据传输流,字节流可通过转换流(InputStreamReader和OutputStreamWriter)转换为指定字符集的字符流,其实字节流转换为字符流其根本是一个字节数组转换为一个指定编码集的字符,核心操作如下:
String str=new String("汉字".getBytes("GBK"),"UTF-8");
补充案例:
分别用字节流和字符流写入相同的数据,操作完成后不关闭输出流,查看文件写入情况:
package com.Liao.FileTest;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
public class FileTest {
public static void main(String[] args) {
FileTest ft=new FileTest();
ft.test1();
ft.test2();
}
@SuppressWarnings("resource")
public void test1() {
File file=new File("fileTest2\\1.txt");
try {
file.createNewFile();//创建文件
//创建字节输出流对象
FileOutputStream out=new FileOutputStream(file);
//写入数据
out.write("hi,你好!".getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//输出文件长度
System.out.println("字节流写入文件:"+file.length());
}
@SuppressWarnings("resource")
public void test2() {
File file=new File("fileTest2\\2.txt");
try {
file.createNewFile();//创建文件
//创建字符输出流对象
FileWriter out=new FileWriter(file) ;
//写入数据
out.write("hi,你好!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//输出文件长度
System.out.println("字符流写入文件:"+file.length());
}
}
输出结果如下:
根据输出结果可以看出,字节流无需关闭输出流即可成功写入文件,而字符流则写入失败。这是由于字节流直接操作文件,而字符流作用于缓冲区,数据均写在缓冲区,需要关闭输出流将数据强制写入文件,否则数据一直存放在缓冲区无法输出。当然这里也可以调用flush( )方法,刷新,将缓冲区数据强制写入文件,这里就不演示了。还有一种情况即缓冲区数据满时,也会自动将缓冲区数据写入文件,不过存在数据丢失情况。
四、小结
1、InputStream和OutputStream是所有字节流的父类,Reader和Writer是所有字符流的父类,这四个类都是抽象类,不能被实例化;
2、单独使用字节流或者字符流读取效率,添加缓冲区可大幅提高读写效率;
3、字节流直接操作文件,而字符流作用于缓冲区,通过缓冲区来操作文件;
4、IO操作属于资源操作,不管使用字节流还是字符流,操作完后都要及时关闭;
5、字节流通过转换流可转换为指定字符集的字符流;
6、字节流处理二进制数据,可用于处理任何类型文件,推荐使用;字符流处理字符数据,对于处理字符文本有一定优势。
参考博客:https://blog.youkuaiyun.com/cynhafa/article/details/6882061