文件IO流

1.IO概述

简介

  1. IO:

  I:input输入

  0:out输出

站在内存的角度:从某个设备进去到内存,用的input去读取到内存中,从内存中输出到其他设备,out,用输出去写出到其他设备

  1. 包:IO

IO分类

  1. 分类的方式有两种:按照功能可以分类,按照流向也可以分类
  2. 按照功能分类:

    读:Reader

      写:Write

  1. 按照流向分类:

    输入流:inputStream

      输出流:OutPutStream

  1. IO流的体系结构,根据分类,有四种流对象的类型

  字节流:

          输入流:InputStream

          输出流:OutPutStream

   字符流:  

         输入流:Reader

         输出流:Writer

IO程序书写流程

  1. 在操作之前,要导包,io包
  2. 在操作流对象的时候,要处理解决异常
  3. 在操作完流对象之后,需要关闭资源

2.字节流

概述

  1. 可以直接操作字节信息的流对象
  2. 根据流向,可以分成字节输入流和字节输出流
  3. 顶层抽象父类分别是:InputStream 和 OutputStream
  4. 根据交互设备的不同,有不同的具体子类

InputStream

  1. 字节输入流的顶层抽象父类
  2. 常用的方法

          read():从当前的字节输入流中,获取一个字节

          read(byte[] arr):将arr.length个字节,读取到arr中

     3. InputStream是一个抽象类,不能直接创建对象,只能由子类创建对象

FileInputStream

  1. InputStream的一个具体子类,用于和磁盘上的文件进行交互
  2. FileInputStream不仅可以一次读取一个字节,也可以一次读取很多个字节;不仅可以读取纯文本文件,也可以读取图片、视频、音频等非纯文本文件。一切数据在计算机中都是以字节的形式在存储和计算
  3. 构造方法:

  FileInputStream(File f):将File对象封装成字节输入流,将来可以读取这个文件中的信息

  FileInputStream(String path):将字符串封装成字节输入流,将来可以读取信息

  注意事项:无论是哪个构造方法,都只能封装文件的路径,封装文件夹的路径没有任何意义,因为文件夹本身没有任何数据,所以也不能使用流对象读取数据。

  1. 两个常用方法:

  read():一次读取一个字节,返回值类型是int类型,原因要把返回的字节前面加上24个0,无论读取到的是负数还是整数,都会变成正数,只要是从文件中读取到的数据,都是整数;如果返回了-1,就说明不是从文件中读到的数据,而是整个方法专门准备的文件末尾的标记。说明:虽然每次调用该方法,但是返回的内容却不会相同,因为文件指针在不断向后移动。

  read(byte[] arr):一次读取多个字节,存储到字节数组中

代码示例

InputStream in=new FileInputStream("a.txt");
	
	File f=new File("a.txt");
	InputStream ins=new FileInputStream(f);
	//next()方法每读取一个字节,指针往后移动一次
		/*
		 * System.out.println(ins.read()); System.out.println(ins.read());
		 * System.out.println(ins.read()); System.out.println(ins.read());
		 * System.out.println(ins.read()); System.out.println(ins.read());
		 * System.out.println(ins.read()); System.out.println(ins.read());
		 */
	int i;
	while((i=ins.read())!=-1) {
		
		System.out.print((char)i);
	}
	

OutputStream

  1. 字节输出流的顶层抽象父类
  2. 常用方法:

  write(int b):将一个字节信息写出到指定的设备中

  write(byte[] arr):将一个字节数组中的所有信息,写出到指定设备中

  write(byte[] arr, int offset, int len):将一个字节数组arr中的从offset索引开始,总共len个字节写出到指定的设备中。

  1. 这个类是抽象类,不能直接创建对象

FileOutputStream

  1. 说明:可以将字节数据写出到指定的文件中
  2. 构造方法:

  FileOutputStream(File f):将f描述的路径文件封装成字节输出流对象

  FileOutputStream(String path):将path描述的文件路径封装成字节输出流对象

  使用字节输出流写出数据,就进入到关联的文件中。

  1. 注意事项:

  1、成员方法都来自于抽象父类

  2、写到磁盘上,存储的就是数字,既没有编码,也没有解码;如果使用文本编辑器读取这个文件,就会发生两步:先读取数据,通过编码表进行解码,让我们看到字符

代码示例

public static void main(String[] args) throws IOException {
	File f=new File("b.txt");
	if (!f.exists()) {
		f.createNewFile();
	}
	//FileOutputStream(File file, boolean append)
	//默认是false覆盖,true是追加
	OutputStream os=new FileOutputStream(f,true);
	os.write(98);
	String str="hello,world,你好世界";
	os.write(str.getBytes());
	String s2="xyz";
	//写出数组指定的位置
	os.write(s2.getBytes(), 0, 1);
	os.close();
}

文件拷贝

  1. 含义:

  将一个文件中的数据,拷贝到另一个文件中

  1. 本质:

  从一个文件中,使用输入流,读取一个字节

  将这个字节,使用输出流,写出到另外一个文件中

  1. 图示:

 代码示例

public static void main(String[] args) throws IOException {
		File file=new File("E://day17笔记.doc");
		//System.out.println(file.getName());
		InputStream ins=new FileInputStream(file);
		OutputStream os=new FileOutputStream("E://"+file.getName(),true);
		int i;
		while((i=ins.read())!=-1) {
			os.write(i);
		}
		os.close();
		ins.close();
	
}

文件拷贝效率提升

  1. 使用一个字节一个字节拷贝的方式,效率非常低:IO的次数过多,有多少个字节,就要IO两倍的次数
  2. 提升的思路:一次多读一些数据,一次多写出一些数据,使用FileInputStream中的read(byte[] arr)和FileOutputStream中的write(byte[] arr)。数组的大小可以准备成和文件一样样大。配合InputStream中的available方法可以获取源文件的字节个数,用于创建数组的大小。
  3. 数组属于内存的,内存的大小是有限的,如果文件过大,就无法创建大小相同的数组。
  4. 只能考虑使用小一些的数组,每次拷贝源文件的一部分,多拷贝几次。涉及的方法:

  InputStream中的read(byte[] arr):将数据读取到数组中,返回本次读到的有效字节的个数,如果返回值为-1,表示本次读到的有效字节个数为0,表示到达了文件末尾

  OutputStream中的write(byte[] arr):将数组中的所有数据,都写出到了目标文件中

  OutputStream中的write(byte[] arr, int offset, int len):将数组中的指定部分的数据,写出到目标文件中(在读取的时候,读到了多少有效字节,就将这么多有效字节写出到目标文件中),一般offset都选择0

  1. 注意事项:最终拷贝方案就是小数组

  1、数组的大小可以任意选择,数组越大,拷贝次数越少,拷贝的效率越高

  2、一般情况,数组的大小使用1024的整数倍,在jdk中喜欢使用1024*8大小作为缓冲区的大小。

代码示例


public static void main(String[] args) throws Exception {
	long start=System.currentTimeMillis();
	test4_一次性拷贝();
	long end=System.currentTimeMillis();
	System.out.println("用时:"+(end-start));
}
public static void test1_单字节拷贝() throws Exception {
	File file =new File("E:\\soft\\FeiQ.exe");
	InputStream is=new FileInputStream(file);
	OutputStream os=new FileOutputStream("E://"+file.getName());
	int i ;
	while((i=is.read())!=-1) {
		os.write(i);
	}
	is.close();
	os.close();
}
public static void test2_小数组拷贝() throws Exception {
	File file =new File("E:\\soft\\FeiQ.exe");
	InputStream is=new FileInputStream(file);
	OutputStream os=new FileOutputStream("E://"+file.getName());
	byte b[]=new byte[2];
	while(is.read(b)!=-1) {
		os.write(b);
	}
	is.close();
	os.close();
}
public static void test3_大数组拷贝() throws Exception {
	File file =new File("E:\\soft\\FeiQ.exe");
	InputStream is=new FileInputStream(file);
	OutputStream os=new FileOutputStream("E://"+file.getName());
	byte b[]=new byte[1024];
	int len;
	while((len=is.read(b))!=-1) {
		os.write(b,0,len);//读取多长就写多长
	}
	is.close();
	os.close();
}
public static void test4_一次性拷贝() throws Exception {
	File file =new File("E:\\soft\\FeiQ.exe");
	InputStream is=new FileInputStream(file);
	OutputStream os=new FileOutputStream("E://"+file.getName());
	byte b[]=new byte[is.available()];
	int len;
	while((len=is.read(b))!=-1) {
		os.write(b,0,len);
	}
	is.close();
	os.close();
}

InputStream类型中read()方法和read(byte[] arr)方法的比较

  1. 读取字节个数的区别:

  read()方法一次只能读取一个字节

  read(byte[] arr)方法一次可以读取多个字节,取决于数组的大小

  1. 读取到的字节信息存储的位置不同:

  read()读取到的文件字节信息是作为返回值进行返回的

  read(byte[] arr)读取到的字节信息,存储到参数数组arr中

  1. 返回值的含义不同:两个方法的返回值类型都是int类型

  read()方法返回的是读取的有效字节本身

  read(byte[] arr)方法的返回是读取到的有效字节的个数(返回值的取值范围:-1,1~arr.length)

高效缓冲流

  1. BufferedInputStream和BufferedOutputStream
  2. 是包装类型:本身不具备读写的功能,只是在某个具体的流对象的基础上,对其进行加强,例如FileInputStream和FileOutputStream,原本效率较低,加强之后,就效率较高
  3. 构造方法:

  BufferedInputStream(InputStream is):将指定的具体的字节输入流传入构造方法的参数,形成一个高效版本的字节输入流

  BufferedOutputStream(OutputStream os):将指定的具体的字节输出流传入构造方法的参数,形成一个高效版本的字节输出流

  1. 使用:

  这两个高效流还是字节流,还是InputStream和OutputStream,所以抽象父类中的那些方法仍然可以继续使用

  1. 原理:

  1、BufferedInputStream高效的原理:在该类型中准备了一个数组,存储字节信息,当外界调用read()方法想获取一个字节的时候,该对象从文件中一次性读取了8192个字节到数组中,只返回了第一个字节给调用者。将来调用者再次调用read方法时,当前对象就不需要再次访问磁盘,只需要从数组中取出一个字节返回给调用者即可,由于读取的是数组,所以速度非常快。当8192个字节全都读取完成之后,再需要读取一个字节,就得让该对象到文件中读取下一个8192个字节了。

  2、BufferedOutputStream高效的原理:在该类型中准备了一个数组,存储字节信息,当外界调用write方法想写出一个字节的时候,该对象直接将这个字节存储到了自己的数组中,而不刷新到文件中。一直到该数组所有8192个位置全都占满,该对象才把这个数组中的所有数据一次性写出到目标文件中。如果最后一次循环过程中,没有将数组写满,最终在关闭流对象的时候,也会将该数组中的数据刷新到文件中。

代码示例

public static void main(String[] args) throws Exception {
	File f=new File("E:\\soft\\java1.7api中文.rar");
	InputStream is=new FileInputStream(f);
	//用带有缓冲区的类包装一下
	BufferedInputStream bis=new BufferedInputStream(is);
	
	OutputStream os=new FileOutputStream("E://"+f.getName());
	BufferedOutputStream bos=new BufferedOutputStream(os);
	
	int i;
	while((i=bis.read())!=-1) {
		bos.write(i);
	}
	bis.close();
	//一定要关闭输出流,否则可能会导致部分字节输出不到文件中
	bos.close();
	
}

案例--复制文件夹

ublic static void main(String[] args) throws Exception {
	copyFile("E:\\upload", "E:\\copy");
}
public static void copyFile(String p1,String p2) throws Exception {
	File f1=new File(p1);
	if (f1.isFile()) {//如果直接传过来文件,直接复制
		copy(p1, p2);
		return;
	}
	File[]list = f1.listFiles();//传过来文件夹
	File target=new File(p2);
	if (!target.exists()) {//如果目标文件夹没有,就创建新的
		target.mkdirs();
	}
	for(File f:list) {
		if(f.isFile()) {//如果遍历的当前文件是文件
			copy(f.getPath(), p2+"\\"+f.getName());
		}else {
			copyFile(f.getPath(), p2+"\\"+f.getName());
		}
	}
}
/**复制文件
 * @param src
 * @param target
 * @throws Exception
 */
public static void copy(String src,String target) throws Exception {
	BufferedInputStream bis=new BufferedInputStream(new FileInputStream(src));
	BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(target));
	byte b[]=new byte[1024*8];
	int len;
	while((len=bis.read(b))!=-1) {
		bos.write(b, 0, len);
	}
	bis.close();
	bos.close();
}

输出流中的close()和flush()的区别

  1. close方法会先调用flush方法
  2. close方法用于流对象的关闭,一旦调用了close方法,那么这个流对象就不能继续使用了
  3. flush只是将缓冲区中的数据,刷新到相应文件中,而不会将流对象关闭,可以继续使用这个流对象。但是如果flush方法使用过于频繁,那么丧失了缓冲区的作用。

3..字符流

使用字节流处理字符的问题

  1. 使用字节流写字符

  可以使用,但是需要先把字符串转成字节数组,再存储到文件中,比较麻烦

  1. 使用字节流读取字符

  如果是纯英文,可以一次读取一个字节

  如果是纯中文,可以一次读取两个字节(GBK)

    如果是纯中文,而且UTF-8的编码一次性读取三个字节

  如果是中英文混杂,每次不知道读取多少个字节,因此无论字节数组准备多大,都会出现乱码

代码示例

public static void main(String[] args) throws Exception {
//utf-8汉字占3个字节,gbk汉字占2个字节
//	System.out.println("你".getBytes().length);
	InputStream is=new FileInputStream("a.txt");
	byte b[]=new byte[1024];//utf-8编码读取3个
	//gbk一次性读取两个字节
	//纯英文一次性读取一个字节也可以,随意
	//中英文混合,解决不了
	int i;
	while((i=is.read(b))!=-1) {
		System.out.print(new String(b));
	}
	
}

解决方案

  1. 出现乱码的原因:

  有可能汉字只读取了部分字节,转成字符串的时候,就会出现乱码

  1. 解决:

  动态判断每次应该读取多少个字节

  在GBK编码表中,如果是一个英文,那么一定读取到的字节是正数,如果读取到的字节是正数,那么就可以断定是英文字符,就读取一个字节转成字符即可

  在GBK编码表中,如果是一个中文,那么就一定读取到的第一个字节是负数,如果读取到一个负数,就说明读到的是一个中文,就需要再次读取一个字节,两个字节一起转成字符。

  1. 说明:

  如果在工程中频繁做这么底层的操作,太复杂,直接使用jdk中已经提供好的解决方案

  字符流:不仅能动态判断GBK的每个字符是中文还是英文,还可以判断其他编码表的所有语种

字符流的使用

  1. 抽象顶层父类:Reader、Writer
  2. 常用方法:

  输入流Reader:

        read();

        Read(char[]c)

输出流Writer

       Write(int i)

       Write(char[])

       Write(char[],int start,int len)

  1. 抽象父类不能直接创建对象,需要使用具体的子类类型来创建
  2. 使用FileReader和FileWriter可以作为创建对象的类型

代码示例

public static void main(String[] args) throws Exception {
		/*
		 * Reader reader=new FileReader("a.txt"); 
		 * int i; 
		 * while((i=reader.read())!=-1) {
		 * System.out.print((char)i); }
		 */
	readAsArr();
}
public static void readAsArr() throws Exception {
	FileReader reader=new FileReader("a.txt");
	char c[]=new char[1];
	while(reader.read(c)!=-1) {
		System.out.print(new String(c));
	}
}

总结

输入流

InputStream  抽象类,

FileInputStream

read() 返回的是读取的一个字节

read(byte[]b)返回的是读取长度,将读取的内容写到byte数组中

输出流:

OutputStream 抽象类

FileOutputStream

wirte(int i)写的是字节

write(byte [] b)写一个字节数组

write(byte[] ,int start,int len)写一个字节数组,从哪个位置开始写,写的长度

带有缓冲区的输入输出流:

作用:减少与磁盘的交互次数,提高效率

BufferedInputStream(InputStream)

BufferedOutputStream(OutputStream)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值