12.IO(一)【IO流概述】【字符流】【字符流缓冲区】【装饰设计模式】【字节流】


一、IO流概述

1.        IO,是InputOutput的缩写。

2.        流,是传递数据信息的载体,是程序中的数据所经历的路径。

3.        流分为输入流和输出流。输入流是将数据从数据源传递给程序输出流是将数据从程序传递到目标的地方,如硬盘,内存,网络等。

4.        Java语言操作数据就是通过流的方式。

5.        Java中的IO流库提供了大量的流库,均包含在IO包中,要使用这些流类必须先用import语句引入。

6.        根据输入输出的数据类型,流可以分为字节流(Byte)和字符流(Chracter,他们处理信息的基本单位分别是字节字符

 

二、字符流

1.        字符流主要用于操作文本数据,使用字符流读取文字字节数据时,不直接操作,而是先查编码表,获取对应的文字,再进行操作。

简单说,字符流==字节流+编码表。

2.        如果要操作文本数据,优先考虑字符流。

3.        个人理解Java中的字符流融合了编码表,默认的编码表是当前系统的编码表。中文是Unicode编码表,是双字节的。InputStream处理数据时以字节为基本单位,在处理文本数据时,不是很方便,处理比较慢,所以Java为处理字符提供了一套专门的类,简化编程。

4.        字符流的两个顶层抽象父类:ReaderWriter。每次读取或写入16位字符。Reader是读取输入流,Writer是写出输出流。

5.        Writer的常用子类为FileWriter,用于操作文件。Reader常用的操作文件的子类为FileReader

注意:基类的子类都是以父类作为后缀名,前缀为要实现的功能。如FileWriter,前缀为File,说明该子类专门用于操作文件的写出。

6.        使用字符流读取数据的步骤:

l 创建FileReader对象,与数据相关联。在创建读取流对象时,必须要明确被读取的文件,而且该文件一定是存在的。否则会抛出FileNotFoundException异常。

l 调用流的read()方法读取数据intread()方法,读取单个字符。在字符可用、发生 I/O错误或者已到达流的末尾前,此方法一直阻塞。如果已达到结尾,返回-1.

read(char[] buf):将字符读入数组。buf,目标缓冲区。

l 关闭流:close();关闭流后,将不能再读取数据。

<span style="font-size:18px">import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

//读取一个文本文件,将读取到的字符打印到控制台。

public class FileReaderDemo {

	public static void main(String[] args) throws IOException {

		//1.创建读取字符数据的流对象
		/*
		 * 在创建读取流对象时,必须要明确被读取的文件,一定要确定该文件是存在的。
		 * 
		 * 用一个读取流关联一个已存在文件。
		 */
		FileReader fr = new FileReader("demo.txt");
	
		int ch = 0;
		while((ch = fr.read())!=-1){
			System.out.print((char)ch);
		}
		fr.close();
	}

}
</span>

 

例2:读取一个文本文件,将读取到的字符打印到控制台。

<span style="font-size:18px">import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class FileReaderDemo2 {
	public static void main(String[] args) throws IOException {
		FileReader fr = new FileReader("demo.txt");//文件里的字符为abcdw
		/*
		 * 使用read(char[]) 读取文本文件数据。
		 * 
		 * 先创建字符数组。
		 */
		char[] buf = new char[1024];
		int len = 0;//因为数量一直在变,所以可以定义变量。
		while((len=fr.read(buf))!=-1){
			System.out.println(new String(buf));
		}
//		method_1(fr, buf);
		fr.close();
	}

	/**
	 * @param fr
	 * @param buf
	 * @throws IOException
	 */
	public static void method_1(FileReader fr, char[] buf) throws IOException {
		int num = fr.read(buf);//将读取到的字符存储到数组中。
		System.out.println(num+":"+new String(buf));
		/*
		 * 数组容量为3,所以num=3,
		 */
		
		int num1 = fr.read(buf);
		System.out.println(num1+":"+new String(buf));
		/*
		 * 文件里剩下两个字符:dw。再次读取时,读取到的dw重新存到数组,覆盖了ab,num1 = 2,同时c没被覆盖。
		 * 所以这次输出dwc
		 */
		int num2 = fr.read(buf);
		System.out.println(num2+":"+new String(buf));
		/*
		 * 文件里没有字符,返回-1.所以num = -1, 
		 * 数组里的字符没有被覆盖,所以输出dwc,和第二次一样。
		 */
	}
}
</span>


 

7.        使用字符流写出数据的步骤:

l 创建FileWriter对象,与数据相关联。创建该对象时,必须要明确存储数据的目的地。如果文件不存在,则会自动创建。如果文件存在,则会覆盖原来的文件。

l 调用write(String str):写入字符串到目的地,如果要续写,可以写为write(String str ,true),这样就不会覆盖原文件,而且会在文件末尾处添加数据Write(char[]buf,int off, int len),写入数组的某一部分。

l 调用flush()方法,将缓冲区的数据写入到目的地中。刷新之后还可以继续写数据到目的地。

l close()方法,用于关闭流。关闭资源之前,会刷新缓冲区。关闭之后,不能再写入数据到目的地中,否则会抛出异常。

例子:将一些文字存储到硬盘中的文件。

<span style="font-size:18px">import java.io.FileWriter;
import java.io.IOException;

public class FileWriterDemo {
	private static final String LINE_SEPARATOR = System.getProperty("line.separator");

	public static void main(String[] args) throws IOException {

		//创建一个可以往文件中写入字符数据的输出流对象。
		
		FileWriter fw = new FileWriter("demo.txt");//加入true,续写。
		
		fw.write("advasdf"+LINE_SEPARATOR+"lala");//换行
		fw.write(LINE_SEPARATOR+"haha");
		/*
		 * 进行刷新,将数据直接写到目的地中。刷新后还可以继续操作流。
		 */
//		fw.flush();
		
		/*
		 * 关闭流,关闭资源,关闭之前先刷新,其实就是close方法调用了flush方法。关闭之后就不能再操作当前流、
		 */
		fw.close();
		
//		fw.write("sfeg");//java.io.IOException: Stream closed
		
	}
}
</span>


 

练习:从E:\eclipse\day20e中复制文件demo.txt的内容到E:\目录的democopy。txt文件中。

思路:
  既然是复制文件内容,那么先读取文件。优先考虑字符输入流。FileReader
  读取文件后再写入到新文件中。FileWriter
  
  步骤:
  1.创建要进行复制的文件对象和保存复制后的文件对象。
  new FileReader()
  new FileWriter()
  
  2.读取文件内容,一个一个复制过去,或者建立字符数组,一次性读取。
  3.频繁读写操作。
  4.关闭流。

<span style="font-size:18px">import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class FileCopyTest_2 {
	public static void main(String[] args)  {
		FileReader copyBy = null;
		FileWriter copy = null;
		try {
			String readerPath = "IO流_2.txt";//要复制的文件路径。
			String writerPath = "testcopy_2.txt";//复制到目标文件。
			
			copyBy = new FileReader(readerPath);
			copy = new FileWriter(writerPath);
			
			char[] file = new char[1024];//定义字符数组。
			
			int len = 0;
			while((len=copyBy.read(file))!=-1){//读取字符到数组
				
				copy.write(file,0,len);//从字符中写入目标文件。
			}
		} catch (Exception e) {
			throw new RuntimeException("读写失败!");
		}finally{
			if(copyBy!=null)//被创建成功,需要关闭
				try {
					copyBy.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			if(copy!=null)
				try {
					copy.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
	}
}</span>

 

三、字符流缓冲区

概述

字符流缓冲区分为输入流缓冲区和输出流缓冲区,即

BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。

                                    可以指定缓冲区大小,或者可以使用默认大小。一般情况下,默认大小就足够了。

                                    提供了readLine()方法,从而实现行的高效读取。

BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。

                                    该类提供了newLine()方法,他使用了平台自己的分隔符,从而实现换行。

1.      缓冲区的原理

假设内存中有一数组buf,缓冲区调用继承来的bufr.read(buf),从硬盘读取一些数据到内存。

从内存中读取数据时,调用:这个read是从缓冲区中的取出的字符数据。所以覆盖了父类中的read方法。这个read本身就是一个高效的read().直接从缓冲区读取,不用到硬盘中读取。

 

用bufr.read()读取字符后,就可以操作字符了,根据文本的行特点,可以按照行读取,所以进行了下一步的动作,就有了新的方法readLine().

2.      readLine()方法的原理

在缓冲区中,bufr.read()从存储字符的容器读取字符后,不直接操作,而是在缓冲区开辟另一个临时容器,将字符装入,然后继续读取,直到读取换行符\r,终止符\n,并且不包含换行符,这时候才从临时容器中一次性读取存储的有效字符。这就是readLine()原理。临时容器可以是StringBuilder,因为最终返回的是字符串。只是在read()基础上加了一个判断换行标记。

简单说,readLine()方法:使用了缓冲区的read方法,将读取到的字符进行缓冲并判断换行标记,将标记前的缓存数据编程字符串输出。

     BufferedWriter示例:

   

<span style="font-size:18px">import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedWriterDemo {
	public static final String LINE_SEPARATOR = System.getProperty("line.separator");
	public static void main(String[] args) throws IOException {
		FileWriter fw = new FileWriter("buf.txt");
		
		//为了提高写入效率,使用字符流的缓冲区。
		//创建一个字符写入流的缓冲区对象,并和指定要被缓冲的流对象相关联。
		BufferedWriter bufw = new BufferedWriter(fw);
		
		//使用缓冲区的写入方法将数据写入缓冲区中。
//		bufw.write("你好"+LINE_SEPARATOR+"你好two");
//		bufw.write("你好!");
//		bufw.newLine();
//		bufw.write("我不好!");
		for (int i = 0; i < 4; i++) {
			bufw.write("早安世界"+i);
			bufw.newLine();
			bufw.flush();
		}
		//使用缓冲区的刷新方法将数据刷入目的地中。
		bufw.flush();
	
		//关闭缓冲区,其实就是关闭被缓冲的流对象。
		bufw.close();
	}
}
</span>

      

      BufferedReader示例:

    

<span style="font-size:18px">import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class BufferedReaderDemo {

	public static void main(String[] args) throws IOException {

//		demo();
		FileReader fr = new FileReader("buf.txt");
		BufferedReader bufr = new BufferedReader(fr);
		/*
		 * 行的读取。
		 * readLine():一行一行的读取。
		 */
		
		String line = null;
		while((line=bufr.readLine())!=null){
			System.out.println(line);
		}
		
		/*
		String line1 = bufr.readLine();
		System.out.println(line1);
		
		String line2 = bufr.readLine();
		System.out.println(line2);
		
		String line3 = bufr.readLine();
		System.out.println(line3);
		
		String line4 = bufr.readLine();
		System.out.println(line4);
		String line5 = bufr.readLine();
		System.out.println(line5);
		*/
		bufr.close();
	}

	/**缓冲字符数组
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static void demo() throws FileNotFoundException, IOException {
		FileReader fr = new FileReader("buf.txt");
		char[] buf = new char[1024];
		int len = 0;
		while((len=fr.read(buf))!=-1){
			System.out.println(new String(buf,0,len));
		}
		fr.close();
	}
}
</span>

       

自定义读取缓冲区:

自定义的读取缓冲区,其实就是模拟一个BufferedReader。
  

分析:
  缓冲区中无非就是封装了一个数组,
  并对外提供了更多的方法对数组进行访问。
  其实这些方法最终操作的都是数组的角标。
  
缓冲的原理:
  从源中获取一批数据装进缓冲区中。
  再从缓冲区不断的取出一个一个数据。
  
  在此次取完后,再从源中继续取一批数据进缓冲区。
  当源中的数据取完时,用-1作为结束标记。

<span style="font-size:18px">import java.io.FileReader;
import java.io.IOException;

public class MyBufferReader {

	private FileReader r;
	
	//定义一个数组作为缓冲区
	private char[] buf = new char[1024];
	
	//定义一个指针,用于操作数组元素,操作到最后一个元素时,指针归0.
	private int pos = 0;
	
	//定义一个计数器,用于记录缓冲区的数据个数,当该数据减到零时,就从源中继续获取数据到缓冲区中。
	private int count = 0;
	
	MyBufferReader(FileReader r){
		this.r = r;
	}

	public int myRead() throws IOException{
		if(count==0){
			count = r.read(buf);
			pos = 0;
		}
		if(count<0)
			return -1;
		char ch = buf[pos];
		pos++;
		count--;
		return ch;

		/*
		//1.从源中获取数据到缓冲区中。需要先做判断,只有计数器为0时,才需要从源中获取数据。
		if(count==0){
			count = r.read(buf);
			
		//每次获取数据到缓冲区后,角标归零。
		pos = 0;
		char ch = buf[pos];
		
		pos++;
		count--;
		
		return ch;
			
		}
		
		if(count<0)
			return -1;
		
		else{
			
			char ch = buf[pos];
			
			pos++;
			count--;
			
			return ch;
		}
		*/
		
	}
	
	public String myReadLine() throws IOException{
		
		StringBuilder sb = new StringBuilder();
		
		int ch = 0;
		
		while((ch=myRead())!=-1){
			
			if(ch=='\r')
				continue;
			if(ch=='\n')
				return sb.toString();
			
			sb.append((char)ch);
			
		}
		
		//健壮性判断
		/*
		 * 因为假如“早安世界3”这一行字符最后没有换行字符“\n”,虽然数据存储到缓冲区中,
		 * 但是没有写入文件。所以要判断。
		 */
		if(sb.length()!=0)
			return sb.toString();
		return null;
	}

	public void close() throws IOException {
		r.close();
	}
</span>

 

四、装饰设计模式(wrapper)

1. 概述

    当想对一组对象功能进行增强时,可以自定义一个增强功能的类,将已有对象传递给增强功能类的构造函数。

    基于已有对象的功能,添加想增强的功能。那么自定义的类称为装饰类。这种设计模式称为装饰设计模式

2. 装饰的特点

    装饰类和被装饰类都必须所属同一个接口或者父类。

3. 继承和装饰都能实现功能的扩展,两者有什么区别呢?

首先有一个继承体系:

writer

|--TextWriter:用于操作文本。

|--MediaWriter:用于操作媒体。

想要对操作的动作进行效率的提高。按照面向对象,可以通过继承对具体的进行功能的扩展。

效率提高需要加入缓冲技术。

可以通过继承实现功能的扩展。

Writer

|--TextWriter:用于操作文本。

           |--BufferedTextWriter:加入了缓冲技术的操作文本的对象。

|--MediaWriter:用于操作媒体。

           |--BufferedMediaWriter

 

但是通过继承不理想:如果这个体系进行功能的扩展,又增加流对象。

那么这个流要提高效率,也要产生子类。就会发现只为提高功能而进行继承,导致继承体系越来越臃肿。不够灵活。

 

既然加入的都是同一种技术,继承是让缓冲和具体的对象相结合,可不可以将缓冲进行单独封装,哪个对象需要缓冲就将哪个对象和缓冲相关联。

class Buffer{

Buffer(TextWriter w)

{

}

Buffer(MediaWriter w)

{

}

}        

class BufferWriter extends Writer{

BufferWriter(Writer w){

}

}

 

这样,原来的体系就变成了:

writer

           |--TextWriter:用于操作文本。

           |--MediaWriter:用于操作媒体。

           |--BufferWriter:用于提高效率。

总结:

  •  装饰比继承更灵活。避免了体系因继承而臃肿,并且降低了类和类之间的耦合。
  •  从继承结构转为装饰的组合结构。
  •  装饰类要增强已有对象的功能,具备和原来对象的功能,而且又有自己增强的功能。

注意:要增强功能时,多用装饰类,少用继承。装饰类更灵活。

   示例:人有吃饭的功能,现在想人多了两项功能:吃饭前喝开胃酒,饭后吃甜点。

  

<span style="font-size:18px">public class PersonDemo {
	public static void main(String[] args) {
		Person p = new Person();
		p.chifan();
		
		//装饰
		NewPerson p1 = new NewPerson(p);
		p1.chifan();
		
		//继承
		NewPerson_2 p2 = new NewPerson_2();
		p2.chifan();
	}
}

class Person{
	void chifan(){
		System.out.println("吃饭");
	}
}

//这个类的出现是为了增强Person而出现的。装饰
class NewPerson{
	private Person p;
	NewPerson(Person p){
		this.p = p;
	}
	
	public void chifan(){
		System.out.println("开胃酒");
		p.chifan();
		System.out.println("甜点");
	}
}

//通过继承实现功能增强
class NewPerson_2 extends Person{
	public void chifan(){
		System.out.println("开胃酒");
		super.chifan();
		System.out.println("甜点");
	}
}
</span>


五、字节流

1.      概述

用于操作字节数据,每次读取或者写入8位。字节流能操作文本数据,也可以操作其他媒体文件。

2.      字节流的两个基类:

InputStream输入流,用于读取数据。

OutputStream输出流,用于写入数据。

3.      由于媒体文件数据都是以字节存储,所以字节流对象可以直接操写入数据到文件,不用再进行刷新动作。

为什么不用进行刷新流的动作呢?

因为字节流操作的是字节,是数据的最小单位,不用像字符流一样需要转换为字节,可直接将字节数据写入到文件。

4.      InputStream特有方法:

int available();返回文件的字节大小。

注意:可以调用此方法来获取字节大小,从而创建一个和文件大小一致的数组,从而省去循环判断。

          但是如果文件过大,超过了虚拟机分配的默认内存64M,此数组长度所占内存空间就会溢出。

          所以,此方法适用于文件较小的时候。

   例子:复制图片

  

<span style="font-size:18px">import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

//复制图片
public class copyPic {
	public static void main(String[] args) {
		copyPicMethod1();
		copyPicMethod2();
	}
	
	//avaliable方法
	public static void copyPicMethod2() {
		FileInputStream fis = null;
		FileOutputStream fos = null;
		try {
			fis = new FileInputStream("F:\\我的女孩.jpg");
			fos = new FileOutputStream("F:\\我的女孩1.jpg");
			//调用avaliable方法,获取字节数大小,创建字节数组
			byte[] buf = new byte[fis.available()];
			
			fis.read(buf);//复制数据
			fos.write(buf);//黏贴文件到指定路径。
		} catch (IOException e) {
			throw new RuntimeException("复制图片失败!");
		}finally{
			if(fis!=null){
				try {
					fis.close();
				} catch (IOException e) {
					throw new RuntimeException("读取图片失败!");
				}
			}
			
			if (fos!=null) {
				try {
					fos.close();
				} catch (IOException e) {
					throw new RuntimeException("写入图片失败!");
				}
			}
		}
	}

	// 创建字符数组作为缓冲区
	public static void copyPicMethod1() {
		FileInputStream fis = null;
		FileOutputStream fos = null;
		try {
			fis = new FileInputStream("F:\\我的女孩.jpg");
			fos = new FileOutputStream("F:\\我的女孩1.jpg");

			byte[] buf = new byte[1024];
			int len = 0;

			while ((len = fis.read(buf)) != -1) {//复制数据
				fos.write(buf, 0, len);//粘贴到指定路径
			}
		} catch (IOException e) {
			throw new RuntimeException("复制图片失败!");
		}finally{
			if(fis!=null){
				try {
					fis.close();
				} catch (IOException e) {
					throw new RuntimeException("读取图片失败!");
				}
			}
			
			if (fos!=null) {
				try {
					fos.close();
				} catch (IOException e) {
					throw new RuntimeException("写入图片失败!");
				}
			}
		}
	}

}
</span>



                             

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值