io思维导向图

图片来源于互联网

一、IO流概述

 IO流用于处理设备之间的数据传输问题。Java对数据的操作,通过流的形式。操作存储设备中的数据,流技术实现,程序和设备之间建立连接通道,数据流。I -> Input 输入 O -> Output输出

  • 字符流: 开始JDK1.1  字符流专门用于操作文本文件,记事本可以打开的,人可以看懂的文件。 每次操作1个字符 16个二进制,去查询本机默认的编码表。方便Java程序操作纯文本。( .txt  .java  .html .xml  .css )

  • 字节流:开始JDK1.0  字节流操作文件是任意文件。每次操作1个字节8个二进制。不查询编码表 。

二、IO流的分类

  1. 字符输出流: 写文本文件的,抽象基类java.io.Writer

    写的方法 write,很多重载形式,写字符数组,单个字符,字符串,字符数组一部分,字符串的一部分。 flush 刷新流的缓冲  close关闭流对象

  2. 字符输入流:读取文本文件的,抽象基类java.io.Reader

    读的方法 read,很多重载形式,读取单个字符,字符数组,字符数组一部分。close关闭流对象

  3. 字节输出流:写任意的文件,抽象基类java.io.OutputStream

    写的方法 write,很多重载形式,写单个字节,字节数组,字节数组一部分。 close关闭流对象

  4. 字节输入流:读取任意文件,抽象基类java.io.InputStream

    读的方法 read,很多重载形式,读取单个字节,字节数组,字节数组一部分。close关闭流对象

 IO流中的所有类的命名法则:后缀都是父类名,前面都是可以操作的文件名,流向名

  FileInputStream  FileReader ObjectInputStream  InputStreamReader

三、书写注意

  • IO使用,导入java.io包
  • IO流类中的方法,大多数都会抛出异常,调用者try ...throws
  • IO流对象,没有实际的功能操作文件,依赖操作系统来实现功能,流对象用完后,一定要关闭资源

四、字符流输出

1. 写文件

数据输出是java.io.Writer类(抽象的),具体操作找子类 FileWriter 来写入字符文件的便捷类。

  1. 创建子类对象,需要使用子类的构造器,传递String文件名
  2. 子类对象调用父类方法write()写文件
  3. 关闭资源(一旦流对象关闭,不能再次使用)

注意:创建对象,调用方法写,write方法是把数据写在了内存中,flush刷新该流的缓冲,把数据从内存刷到目的文件。字符流写文件必须刷新,如果不刷新数据可能会留在内存中,但是只要进行了刷新数据肯定走向目的文件

2. 追加的形式写文件

当我们不能覆盖写入的时候,就需要进行后续的追加。利用FileWriter类的构造方法,传递true可以实现文件的追加写入

 三种写入的方法:

  1. 写单个的字符  write(int i)
  2. 写字符数组  write(char[] c)
  3. 写字符数组一部分 write(char[] c,int start,int length)

demo:

package io;

import java.io.*;

public class FileWriteDemo {
	public static void main(String[] args) throws IOException {
		
		//构造方法抛异常
		FileWriter write = new FileWriter("test.txt");

		//write方法,三种重载形式(string s) (int i ) (char [])
		write.write("this is a test");
		
		write.flush();
		
		write.close();
		
		
		//打开进行追加,文件存在时,不会覆盖原来的内容
		FileWriter fw = new FileWriter("test.txt",true);
		
		fw.write("ok, append something");
		fw.flush();
		fw.close();
	}
}

五、字符流读取文件

读取文件是java.io.Reader类,也是抽象类,具体使用要找子类,字符流读取使用子类FileReader。

  1. 创建子类对象,需要使用子类构造器,传递String文件名
  2. 在调用父类方法read读取文件
  3. 关闭资源

一般两种读取的方式:

  • 读取单个字符  int read() 每次读取1个字符,返回码值,read方法每执行一次,自动的向后读取一个字符,读取到文件末尾,返回-1。
  • 数组读取int read(char[]),一次性读取数组长度个字符,返回读取的长度,到达文件末尾时返回-1

demo:

package io;
/*
 * 字符输入流
 *
 * 1. int reader() 读取单个字符,返回字符对应的int值,每次读取,指针就会向后移动,指向下一个字符,到文件末尾时,返回-1
 * 2. int reader(char []) 读取一个数组的长度,具体长度由数组的长度决定,返回读取的长度,到文件末尾时,返回-1
 *  
 */

import java.io.*;

public class FileReaderDemo {
	public static void main(String[] args) throws IOException {
		
		FileReader fr = new FileReader("test.txt");
		
		System.out.println((char)fr.read());
		System.out.println(fr.read());
		System.out.println(fr.read());
		System.out.println(fr.read());
		
		fr.close();

		method();
	}
	
	//读取数组
	public static void method ()throws IOException {
		FileReader fe = new FileReader("test.txt");
		
		char[] ch = new char[2];
		
		int i = 0;
		while((i = fe.read(ch)) != -1){
			String s = new String(ch,0,i);
			System.out.print(s);
		}
		
		fe.close();
	}
}

六、缓冲区

缓冲区是为了提高流的读写效率而出现,比如BufferedWriter,也是Writer的子类,Writer类的方法都可以用的。但是使用缓冲区对象,必须有一个流对象配合使用。

1. 字符输出流

BufferedWriter类的构造方法BufferedWriter(Writer w)参数是一个字符输出流,将输出流FileWriter类的对象传递给BufferedWriter的构造方法  BfferedWriter(new FileWriter),BufferedWriter这个缓冲区,将提高FileWriter的写如效率。

缓冲区特有方法  void newLine()文本中,写换行。可以写\r\n还是用newLine(),如果需要文本的换行,需要使用newLine()实现,这个方法具有跨平台性。

Windows,换行\r\n    Linux换行\n . 如果安装的JDK是Windows版本的,newLine()方法中写的就是\r\n, 安装的JDK是Linux版本,newLine()方法写的就是\n

Demo:

package io;
/*
 * 缓冲区demo
 */
import java.io.*;
public class BufferedWriterDemo {
	public static void main(String[] args) throws IOException{
		
		//创建字符输出流
		FileWriter fw = new FileWriter("E:\\test.txt");
		
		BufferedWriter bw = new BufferedWriter(fw);
		
		bw.write("test!\r132");
		
		bw.newLine();//跨平台性 win下是\r\n  linux下是\n
		
		bw.write("new test!");
		
		bw.flush();
		
		bw.close();

	}
}

2. 字符输入流

BufferedReader类也是Reader的子类,Reader的方法当然都可以用,需要一个输入流对象,配合使用。

BufferedReader构造方法BufferedReader(Reader r)参数是一个字符输入流,方法String readLine()读取文本一行,返回字符串。

demo:

package io;
import java.io.*;
/*
 *  BufferedReader
 *  String readerLine() 一次读取一行
 */
public class BufferedReaderDemo {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new FileReader("E:\\test.txt"));
		String s = null;
		while( (s = br.readLine()) != null){
			System.out.println(s.length() +"..." + s);
		}
		br.close();
	}
}

练习demo(复制一个文件,使用缓冲区实现):

package io;
import java.io.*;
/*
 * 一个练习,buffered读行,写行,换行
 * 处理异常
 * 目标文件:e:\temp.html
 */
public class CopyTextByBuffered {
	public static void main(String[] args) {
		
		BufferedReader bfr = null;
		BufferedWriter bfw = null;
		
		try{
			//初始化需要用的文件和缓冲区的读取和写入的对象
			bfr = new BufferedReader(new FileReader("E:\\temp.html"));
			bfw = new BufferedWriter(new FileWriter("E:\\newTemp.html"));
			
			String s = null;
			while((s = bfr.readLine()) != null){
				bfw.write(s);
				bfw.newLine();
				bfw.flush();
			}
			
		}catch(IOException e){			
			//
			throw new RuntimeException("文件copy失败");		
		}finally{ //关闭缓冲区
			try{
				bfw.close();
			}catch(IOException e){
				throw new RuntimeException("关闭文件写入缓冲区异常");
			}
			
			try{
				bfr.close();
			}catch(IOException e){
				throw new RuntimeException("关闭文件读取缓冲区异常");
			}
			
		}
	}
}

七、装饰设计模式

装饰设计模式出现的意义就是增强原有对象的功能。举个例子,BufferedReader,BufferedWriter 装饰类的出现,就是增强了原有的流对象Reader和Writer的功能,装饰设计在java的IO中使用相当广泛。

说道这里,那继承覆盖和装饰的区别在哪里,举个例子进行说明。

现在有一个父类Reader,下面有四个子类分别是:

  • 文本类 TextReader
  • 视频类 VideoReader
  • 音乐类 SoundReader
  • 游戏类 GameReader

如果此时因为这四个类的功能太少要进行扩展,采用继承的话就需要再写四个子类来进行继承和重写和加强,因此就会有八个类出现。这对开发和新手接触都是相当麻烦的,要额外的增加相当多的成本。那此时如果采用装饰类的话,只需要一个BufferedReader装饰类将继承自Reader的所有类都装饰起来,简单简洁,适合新手接触和开发。

八、实现readerLine()练习

依照BufferedReader类的readLine方法原理,自己进行实现,要求功能上和原方法相同。

Demo:

package io;
/*
 * 自己实现一个类似于BufferReader 里面的 readerline方法
 * 只是练习,直接把异常抛出去,没有做相应的处理
 */
import java.io.*;

public class ReaderLineTest {
	public static void main(String[] args) throws IOException{
		
      //初始化一个FileReader对象
		FileReader fr = new FileReader("E:\\test.txt");
		//讲文件内的内容全部打印出来,使用的文件是BufferedWriter中创建的文件
		String s = null;
		while((s = MeReaderLine(fr)) != null){
			System.out.println(s);
		}

		fr.close();
	}
	
	/*
	 * 传入一个流对象,返回一个String
	 */
	
	public static String MeReaderLine(FileReader fr) throws IOException{
		
		
		
		StringBuffer bf = new StringBuffer();
		
		if(fr == null)
			return  null;
		int temp = 0;
		
		try{
			while((temp = fr.read()) != -1){
				if(temp == 13)
					continue;
				if(temp == 10)
					return bf.toString();

				bf.append((char)temp);
			}
			
			if(bf.length() == 0)
				return null;
			else
				return bf.toString();
		}catch(IOException e){
			throw new RuntimeException("读取失败");
			
		}
	}
}

九、字节流输入输出

1. 字节输入流–InputStream

InputStream是字节输入流,可读取任意文件。read可 读取字节数组,可读单个字节。此类是抽象类,不能直接用,所以找子类 FileInputStream。

  • FileInputStream()构造方法,传递String的文件名
  • read()方法读取文件结尾返回-1
  • read(字节数组)利用数组作为缓冲,提高流的读取效率

根据字符流的原理,字节流中的read()读取到的字节存储到字节数组中,返回读取一次的字节数中有效字节个数,文件末尾返回-1

  • int  available(),返回字节输入流中封装的文件的字节数

2. 字节输出流–OutpuStream

OutpuStream字节输出流,可以写入任意文件。write(byte) \write(byte[])写字节数组或者单个字节。OutpuStream是抽象类,不能直接用,实现找子类 FileOutputStream。 FileOutputStream()构造方法,传递String的文件名。

字节流写数据的时候,不需要刷新,但是要关闭

3. 字节流缓冲区(不是很重要,会用就好了)

字节流缓冲区和字符流缓冲区用法很像,注意字节流是没有行概念的。

  • BufferedOutputStream()构造方法,传递字节输出流,写入的方法写单个字节,字节数组,没有行!!
  • BufferdInputStream() 构造方法,传递字节输入流,读取的方法,单个字节,字节数组

 下面是一个字节流缓冲区复制文件的demo,并没有使用buffered…:

package io;

/*
 * 使用字节流,实现任意文件的copy
 */
import java.io.*;

public class CopyFileByFileStream {
	public static void main(String[] args){
		
		FileInputStream fis = null;
		FileOutputStream fos = null;
		long  s = System.currentTimeMillis();
		try{
          //初始化字节流输入输出对象
			fis = new FileInputStream("E:\\迅雷下载\\VSCodeSetup-stable.exe");
			fos = new FileOutputStream("E:\\vscode.exe");
			//byte[]用来缓存字节流数据,1024刚好1kb,效率和内存占用上都还行
          	byte[] by = new byte[1024];
			int len = 0;
			while((len =fis.read(by) )!= -1){
				fos.write(by,0,len);
			}
			//下面为异常处理,在finally中关闭流对象即可
		}catch(IOException e){
			throw new RuntimeException("文件复制失败");
		}finally{
			
			try{
				if(fis != null)
					fis.close();
			}catch(IOException e){
				throw new RuntimeException("源文件关闭失败");				
			}
			try{
				if(fos != null)
					fos.close();
			}catch(IOException e){
				throw new RuntimeException("新文件关闭失败");
			}
		}
		long  e = System.currentTimeMillis();
		System.out.println(e-s);
		
	}
}

十、键盘输入

个人感觉java的键盘输入其实和C/C++还是有很大区别的,通过一个demo进行练习和实现,具体的实现思路和方法都在下面的这个demo中,核心利用的是System.in返回的BufferedInputStream进行读取输入的字符。

其实字符流的read()方法是阻塞式的,之前一直因为直接使用文件进行读取,所以程序并不会停下来。现在直接调用read方法,因为没有任何的输入源,所以程序就会停下来,等待输入,键盘输入后才会继续执行。

package io;
/*
 * 键盘输入练习
 * 使用StringBuffer实现内容的不定长读取
 * 1.while永真循环,遇到回车结束,判断\n实现
 * 2.while永真循环,用到规定的字符over结束,用endsWith判断实现
 * 3.使用转换流实现 InputStramReader
 */

import java.io.*;

public class TranseDemo {
	public static void main(String[] args) throws IOException{
		
		//getByKeyboard();
		getBygetByKeyboardAndInputStramReader();
	}
	
	/*
	 * 第3种、转换流思路实现
	 * 规定:over结尾,结束read
	 * 以over结尾的同时,在over之前如果有字符也予以read
	 */
	public static void getBygetByKeyboardAndInputStramReader() throws IOException{
		/*
		 * System.in返回一个BufferedInputStream对象,也就是字节流的子类
		 * 把System.in返回的字节流对象传给InputStreamReader的构造方法,得到转换流
		 * 把转换流传入BufferedReader的构造方法中就得到了BufferedReader的实例对象br
		 */
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		String line = null;
		while((line = br.readLine()) != null){
			if(line.endsWith("over")){
				if(line.length() != 4){
					System.out.println(line.substring(0, line.length()-4));
				}
				break;
			}
			System.out.println(line);
		}
	}
	
	/*
	 * 1.2思路实现
	 */
	public static void getByKeyboard() throws IOException{
		InputStream in = System.in;
		
		StringBuffer sb = new StringBuffer();
		
		int len = 0;
		while(true){
			len = in.read();
			if(len == 13)
				continue;
			if(len == 10){
				
				if(sb.toString().endsWith("over"))
					break;
				System.out.println(sb.toString());
				sb.delete(0, sb.length());
			}
			else{
				sb.append((char)len);
			}
		}
		
	}
}

十一、转换流

控制台输出System.out 引用类型的成员变量,返回一个PrintStream,这个类的名字后缀是一个Stream,所以是字节流,继承OutputStream,肯定是一个字节输出流对象。刚才我们的程序,读取的控制台输入,用的是字符流的readLine读取的键盘输入,将读取到的字符,输出到System.out中去,肯定不行,readLine读取到的是字符数据, System.out返回的是字节输出流,所以想将接受到的数据打印回控制台,还是需要转换流。

OutputStreamWriter , 继承Writer,也是一个字符流,字符流向字节的桥梁。

构造方法,传递字节输出流OutputStreamWriter(OutputStream out)构造器中,可以传递任意的字节输出流

下面给出一个demo,实现控制台的输入输出以及其中的转换流操作等等。

转换流示例

package io;
/*
 * 转换流demo:
 * 字节流 -> 字符流 -> 程序处理字符流后 -> 字流节
 * 
 * 字符数据输出到字节流中,使用OutputStreamWriter
 * 
 * 程序直接全部使用匿名类对象,要改变输入源和输出源
 * 只需要更改InputStreamReader构造方法传入的参数就OK
 * 因为是示例程序,所以直接把IOException扔出去了
 * 
 * 注意:此demo外部的输入输出都是字节流,内部使用两次转换流
 */

import java.io.*;

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

		/*
		 * 字节流-》字符流
		 * System.in返回一个BufferedInputStream对象,也就是字节流的子类
		 * 把System.in返回的字节流对象传给InputStreamReader的构造方法,得到转换流
		 * 把转换流传入BufferedReader的构造方法中就得到了BufferedReader的实例对象br
		 */
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		/* 字符流-》字节流
		 * System.out返回PrintStream,是OutputStream的子类,也就是字节输出流
		 * PrintStream传入OutputStreamWriter的构造方法,得到一个转换流,此转换流将输出的字符流转为为字节流
		 * 最后使用BufferedWriter装饰类操作
		 */
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
		
		String line = null;
		while((line = br.readLine()) != null){
			if(line.endsWith("#")){
				if(line.length() != 1){
					bw.write(line.substring(0, line.length()-1));
					bw.newLine();
					bw.flush();
				}
			
				break;
			}
			else{
				bw.write(line);
				bw.newLine();
				bw.flush();
			}
		}
		//关闭流对象
		br.close();
		bw.close();
	}
}

十二、转换流编码

当流对象没有指定查询哪一个码表,默认走操作系统的中GBK。

  • 转换流OutputStreamWriter构造方法中,写一个字节输出流,写编码表的名字(String)不区分大小写,转换流会将文本数据,以指定的编码形式写入文件

  • 转换流InputStreamReader构造方法中,写一个字节输让流,写编码表的名字(String)不区分大小写,转换流会将文本数据,以指定的编码形式读取文件

demo

package io;

/*
 * 转换流的编码效果
 * 
 * 1.实现多种编码输出到文件
 * 2.实现多种编码下的转换流读取
 */
import java.io.*;

public class TranseEncodingDemo {
	public static void main(String[] args) throws IOException {
		
		writeWithGBK();
		writeWithUTF();
		readWithUTF();
		
	}
	
	/*
	 * 在win下GBK实际上是默认的读取方式
	 * 单还是显示实现进行对比
	 * 注意:传入的编码String不区分大小写
	 */
	public static void writeWithGBK() throws IOException{
		//初始化一个转换流对象
		OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream("E:\\textByGBK.txt"),"GBK");
		
		ow.write("这是一个测试!");
		
		ow.flush();
		ow.close();
	}
	
	/*
	 * 实现UTF-8的编码输出到文件
	 * 注意:传入的编码String不区分大小写
	 */
	public static void writeWithUTF() throws IOException{
		//初始化一个转换流对象
		OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream("E:\\textByUTF.txt"),"UTF-8");
		ow.write("这是一个测试!");
		ow.flush();
		ow.close();
	}
	
	/*
	 * 读取gbk就不写了,直接实现读取utf8
	 * 按照默认的gbk编码读取输出:杩欐槸涓?涓祴璇曪紒  (乱码了)
	 * 按照指定的编码utf-8输出:这是一个测试!  (正确)
	 */
	public static void readWithUTF() throws IOException{
		
		InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\textByUTF.txt"),"UTF-8");
		
		char[] ch = new char[20];
		int len = isr.read(ch);
		System.out.println(new String(ch).substring(0, len).toString());
		isr.close();
	}
}

十三、字符的编码和解码

常见的编码表有以下几种:

  • 拉丁文 iso8859-1,用于 java网络服务器Tomcat
  • GBK 中文编码表,2个字节 对应1个汉字 20000个汉字
  • UTF-8 3个字节对应1个汉字

汉字进行编码 :byte[] b = "汉字内容".getBytes("编码类型");

汉字进行解码:String s = new String(byte[] ,"编码类型");

demo

package io;

/*
 * 汉字的编码和解码练习
 * 编码
 * utf-8 : -26 -99 -88 -26 -81 -108 -24 -67 -87 
 * gbk : -47 -18 -79 -56 -48 -7 
 * Unicode: -2 -1 103 104 107 -44 -113 105 
 * 
 * 解码和编码会有异常,懒得处理,直接抛了
 */
public class EncodingDemo {
	public static void main(String[] args) throws Exception {
		
		String s = "杨比轩";
		
		byte[] by = s.getBytes("gbk");
		
		for(byte b : by){
			System.out.print(b + " ");
		}
		
		System.out.println("\n" + decoding(by,"gbk"));
		
	}
	
	/*
	 * 解码方法
	 */
	public static String decoding(byte[] by, String coding) throws Exception{
		return new String(by,coding);
	}
}

附: java–IO(二)