IO流基础与练习

IO流处理设备之间的数据传输 如 内存和硬盘

通过流的方式对数据操作

用于操作流的对象都存在IO包中


两种操作:
  • 按数据分:字节流与字符流 (字符流由来:文本数据很多,各种码表不一样,GBK,UTF-8等等)
  • 按流向分:输入流,输出流
常用基类:
  • 字节流的抽象基类:InputStream,OutputStream
  • 字符流的抽象基类:Reader,Writer
  • 子类是以父类名作为后缀名,如:InputStream的子类FileInputStream

以FileWriter为例
构造方法必须有参,可创建一个文件

FileWriter fw = new FileWriter("demo.txt"); 

new 完就会在内存中创建一个文件,然后存入硬盘。并且会直接覆盖重名文件。
此时路径为workspace中当前project目录下(相对路径)

fw.writer("aaaaa");

writer 写入一些字符串,但是只会保存在缓冲中,需要调用flush来刷新缓冲中的数据。刷入指定位置demo.txt中

close 方法,刷新一次,然后关闭流资源。 即调用了一次flush方法。


IO异常处理
public static void main(String[] args) {
	FileWriter fw = new FileWriter("demo.txt");
	fw.write("hahaha");
	fw.close();
}

其中每句话都需要try catch,(不然就throws)

可是放在一起简单try并不可以,像这样:

public static void main(String[] args) {
	try {
		FileWriter fw = new FileWriter("demo.txt");
		fw.write("hahaha");
	} catch (IOException e){
		System.out.println(e.toString());
	} finally {
		fw.close();
	}
}

close关流动作一定要执行,所以要放在finally中
看着还行,其实不可以,会报错,因为定义在try语句块中的fw对象,在finally语句块中并不能被访问到,所以这个时候,
需要讲fw定义在类里作为成员变量:

public static void main(String[] args) {
	FileWriter fw = new FileWriter("demo.txt");
	try {
		fw.write("hahaha");
	} catch (IOException e){
		System.out.println(e.toString());
	} finally {
		fw.close();
	}
}

但是,此时new FileWriter(“demo.txt”)也需要抛出异常,那么需要在外部建立引用,然后再try语句块中再进行初始化

public static void main(String[] args) {
	FileWriter fw = null;
	try {
		new FileWriter("demo.txt");
		fw.write("hahaha");
	} catch (IOException e){
		System.out.println(e.toString());
	} finally {
		fw.close();
	}
}

此时,还会报错,因为fw.close可以被访问,但是这句话也需要抛出异常,那么需要在finally中再进行一次try catch处理

public static void main(String[] args) {
	FileWriter fw = null;
	try {
		new FileWriter("demo.txt");
		fw.write("hahaha");
	} catch (IOException e){
		System.out.println(e.toString());
	} finally {
		try {
			fw.close();
		} catch (IOException e) {
			System.out.println(e.toString());
		}
	}
}

现在基本ok了。假设因为疏忽(比如建立在"g:\demo.txt"盘中,可是并没有此路径)fw初始化失败,有异常抛出,不过此时finally语句还是会执行,会抛出第二个异常java.lang.NullPointerException.(因为根本就没有fw)。

那么为了代码的健壮性,我们在关闭流的时候,要进行一次判断,如果存在fw,那么我们再执行关闭它的语句

public static void main(String[] args) {
	FileWriter fw = null;
	try {
		new FileWriter("demo.txt");
		fw.write("hahaha");
	} catch (IOException e){
		System.out.println(e.toString());
	} finally {
		if (fw != null) {
			try {
				fw.close();
			} catch (IOException e) {
				System.out.println(e.toString());
			}
		}
	}
}

有几个流要关闭,就要单独进行几次不等于空的判断分别去关闭。

续写字符

在构造方法中的第二个参数设为true,还是同样的write

FileWrite fw = new FileWriter("demo.txt", true);fw.write("bbbb");
文本文件读取方式一

查文档中的构造方法:读取单个字符

public static void main(String[] args) throws IOException{	//创建读取对象,与文件建立联系	//文件不存在则异常
	FileNotFoundException	FileReader fr = new FileReader("demo.txt");	//read() 一次读一个字符,自动往下读	
	int ch1 = fr.read(); //返回的是读到的字符,以当前系统默认的编码数字形式记载	
	System.out.println("ch1 = " + (char)ch1); //所以此时强制转换成char类型,当读完之后,java会返回-1	
	int ch = 0;	
	while(ch = fr.read() != -1) {
		System.out.println((char)ch);	
	}	
	fr.close();
}
读取方式二:读取字符数组
public static void main(String[] args) throws IOException{			   
	FileReader fr = new FileReader("demo.txt");		//定义一个字符数组用来存读到的字符	
	char [] buf = new char[1024]; //一般字符数组长度设置为1024整数倍	// int ch = fr.read(buf); // 这个read(char[])返回的是读到的字符个数,字符已经被存入字符数组buf中了	
	int num = 0;	
	while(num = fr.read(buf) != -1) {		
		System.out.println(new String(buf,0,num)); //将字符数组转换成String输出,从0位开始,转换num个	
	}	
	fr.close();
}

练习:复制一个文本文件
/*
复制了TicketDemo.java里面的文字到RuntimeDemo_copy这个新创建的文件里面
*/

import java.io.*;
class CopyTest {
    public static void main(String[] args) {
        FileWriter fw = null;
        FileReader fr = null;
        try {
            fw = new FileWriter("RuntimeDemo_copy.txt"); //创建了一个新文件			
            fr = new FileReader("TicketDemo.java"); //进行关联			
            char[] buf = new char[1024];
            int len = 0;
            while ((len = fr.read(buf)) != -1) {
                fw.write(buf, 0, len);
            }
        } catch (IOException e) {
            System.out.println(e.toString());
        } finally {
            if (fw != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    System.out.println(e.toString());
                }
            }
            if (fr != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    System.out.println(e.toString());
                }
            }
        }
    }
}

字符流的缓冲区,提高效率

对应类:

  • BufferedWriter
  • BufferedReader
    与流结合才能使用
BufferedWriter:
//但是首先要创建一个流对象
FileWriter fw = new FileWriter("buf.txt");

//将流对象作为参数传递给缓冲区的构造函数
BufferedWriter bufw = new BufferedWriter(fw);
bufw.write("abc");

//依然要刷新
bufw.flush();

//关闭
bufw.close();      // 不需要再关闭fw,关闭bufw缓冲即可

一个新方法newLine();可写入新的一行,用于换行


BufferedReader:
//创建读取流对象与文件关联
ileReader fr = new FileReader("buf.txt");

//同样,将流对象作为参数传递给缓冲区的构造函数
BufferedReader bufr = new BufferedReader(fr);

bufr.readLine(); // 新方法,读取一行,若读完,返回nullString line = null;
while((line=bufr.readLine()) != null) {	
	System.out.println(line);
}
bufr.close();

练习:通过缓冲区复制一个.java文件

import java.io.*;
class CopyTestByBuf {
    public static void main(String[] args) {
        BufferedReader bufr = null;
        BufferedWriter bufw = null;
        try {
            bufr = new BufferedReader(new FileReader("CopyTest.java"));
            bufw = new BufferedWriter(new FileWriter("copyTest.txt"));
            // readLine() 返回一行换行符之前的内容,但是不包括换行符			
            String str = null;
            while ((str = bufr.readLine()) != null) {
                bufw.write(str);
                bufw.newLine(); // 需要写入新的一行用于换行,不然数据会全部挤在一行				
                bufw.flush();
            }
        } catch (IOException e) {
            System.out.println(e.toString());
        } finally {
            try {
                if (bufr != null) {
                    bufr.close();
                }
            } catch (IOException e) {
                System.out.println(e.toString());
            }
            try {
                if (bufw != null) {
                    bufw.close();
                }
            } catch (IOException e) {
                System.out.println(e.toString());
            }
        }
    }
}

装饰设计模式

对已有对象进行功能增强时,可以定义类,将已有对象传入,
基于已有功能,改进加强。
自定义的该类被称为装饰类

通常装饰类通过构造方法接收被装饰的对象,然后增进功能。

装饰比继承灵活,避免继承臃肿,功能和已有的类是相同的,只是更强。
所以装饰类和被装饰类是属于同一个体系,同样继承自更加高级的父类。
(利用多态性质,可以传入很多同级别的类)


BufferedReader 里面的readLine()方法其实还是基于FileReader的read()方法
一次读取一个字符,但是把装在缓冲区里面,等待一次写入。

下面重写BufferedReader的readLine()方法

	import java.io.*;
	class MyBufferedReader {
	    private FileReader fr; // 构造方法,把被装饰类FileReader当作参数传进来。并设置成成员变量,能够作用于整个类	
	    MyBufferedReader(FileReader fr) {
	        this.fr = fr;
	    }

	    // 基于FileReader的read()方法来自定义readLine方法,增强它的功能。	
	    // windows换行符是'\r''\n'组成。先判断,再转换成char类型存入
	    // 如果读取到 '\r',继续往下读取, 如果读到'\n',返回读到的数组。	
	    public String myReadLine() throws IOException {
	        StringBuffer sb = new StringBuffer();
	        int ch = 0;
	        while ((ch = fr.read()) != -1) {
	            if (ch == '\r') {
	                continue;
	            }
	            if (ch == '\n') {
	                return sb.toString();
	            }
	            sb.append((char) ch);
	        }

	        // 可能最后一行并没有换行符,那么必须加一句判断用来输出,否则可能输出不了最后一行		
	        if (sb.length() != 0) {
	            return sb.toString();
	        }
	        return null;

	    }

	    // 自定义一个close()	
	    public void myClose() throws IOException {
	        fr.close();
	    }
	}


	class CopyDemo {
	    /*	
	    	需要进行try catch处理	  
	    	第一 在外部建立引用	  
	    	第二 初始化,建立文件,对象,缓冲区对象之间的联系	  
	    	第三 调用自定义的myReadLine()方法,读取后再存入	  
	    	第四 finally中别忘了分别判断然后关闭流。		
	    */
	    public static void main(String[] args) {
	        FileReader fr = null;
	        FileWriter fw = null;
	        MyBufferedReader mybr = null;
	        BufferedWriter buf = null;
	        try {
	            fr = new FileReader("CopyTest.java");
	            fw = new FileWriter("Copy111.txt");
	            buf = new BufferedWriter(fw);
	            mybr = new MyBufferedReader(fr);
	            String str = null;
	            while ((str = mybr.myReadLine()) != null) {
	                buf.write(str);
	                buf.newLine();
	                buf.flush();
	            }
	        } catch (IOException e) {
	            System.out.println(e.toString());
	        } finally {
	            if (mybr != null) {
	                try {
	                    mybr.myClose();
	                } catch (IOException e) {
	                    System.out.println(e.toString());
	                }
	            }
	            if (buf != null) {
	                try {
	                    buf.close();
	                } catch (IOException e) {
	                    System.out.println(e.toString());
	                }
	            }
	        }
	    }
	}

自定义字节流缓冲区来实现文件的复制

将e盘下的一个png图片复制成jpg图片

  • read()和write()方法都是读取byte类型,返回的和接收的参数却都是int类型,为了避免-1的情况
  • read()对数据进行提升 byte --> int, 前面补充了3个字节,都是00000000
  • write()对数据进行强转 int --> byte, 只写入int类型的最后一个的有效字节

import java.io.*;
public class InputDemo {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		
		MyBufferedInputStream mbis = null;
		MyBufferedOutputStream mbos = null;
		try {
			mbis = new MyBufferedInputStream (new FileInputStream ("E:\\workspace\\1.png"));
			mbos = new MyBufferedOutputStream (new FileOutputStream ("E:\\workspace\\2.jpg"));
			
			int ch = 0;
			while ((ch=mbis.myRead()) != (-1)) {
				mbos.myWrite(ch);
			}
		} catch (IOException e ) {
			System.out.println("复制文件失败");
		} finally {
			if (mbis != null) {
				try {
					mbis.myClose();
				} catch (IOException e){
					System.out.println("输入流创建失败");
				}
			}
			if (mbos != null) {
				try {
					mbos.myClose();
				} catch (IOException e){
					System.out.println("输出流创建失败");
				}
			}	
		}
	}

}

class MyBufferedInputStream {
	private InputStream in;
	private byte[] buf = new byte [1024];
	private int pos =0; // 每次取一个的指针  
	private int count =0; //缓冲区中字符数组里的存在的字符个数
	
	MyBufferedInputStream (InputStream in) {
		this.in = in;
	}
	
	/* 调用InputStream中的read(char[])方法,每次从硬盘读1024个字节到缓冲区
	 * 缓冲区的myRead()方法每次读一个,利用指针,每次从缓冲区的字符数组中取一个字节
	 * 如果count为0,说明缓冲区的字符全被取完,那么再次调用父类read(char[])方法抓1024个字节,此时需要重置指针pos=0;
	 * 取完所有的,则count会变成返回的-1,
	 * 
	 * b&255,因为myRead()方法返回的是int类型,byte在转换的时候,可能会出现-1,那么需要在转换提升成int的时候在前面补充0
	 * 
	 * 为了避免-1的出现,所以read()方法返回的是int类型,对结果做提升
	 */
	public int myRead() throws IOException {
		if (count == 0){
			count = in.read(buf);
			if (count <0) {
				return -1;
			}
			pos = 0;
			byte b = buf[pos];
			count--;
			pos++;
			return b&255;
		} else if (count > 0) {
			byte b = buf[pos];
			count--;
			pos++;
			return b&255;
		}
		return -1;
	}
	
	public void myClose() throws IOException {
		in.close();
	}
}
/*
 * write()方法其实也是把int类型前面的3个字节都去掉了,只写入了最后的一个字节
 * write()做强转,保证原数据的一致性
 */
class MyBufferedOutputStream {
	private OutputStream out;
	
	MyBufferedOutputStream (OutputStream out) {
		this.out = out;
	}
	
	public void myWrite (int ch) throws IOException {
		out.write(ch);
	}
	public void myClose() throws IOException {
		out.close();
	}
}

练习:

读取键盘录入。

需求:
通过键盘录入数据,
当录入一行数据后,对其进行打印,
如果录入over,那么停止录入。

转换流的作用是以特定的编码表存储字符

  • InputStreamReader(InputStream in, String charsetName)
  • OutputStreamWriter(OutputStream out, String charsetName)
import java.io.*;
public class ReadIn {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		
		//read_1();
		read_2();
		}
	
	// 自定义的读取行数据操作
	public static void read_1() throws IOException {
		InputStream in = System.in;
		
		StringBuilder sb = new StringBuilder ();
		
		while (true) {
			int ch = in.read();
			if ((char)ch == '\r') {
				continue;
			}
			if ((char)ch == '\n') {
				if (sb.toString().equals("over")) {
					break;
				} else {
					System.out.println(sb.toString());
					sb.delete(0, sb.length());
				}
			} else {
				sb.append((char)ch);
			}
			
		}
	}
	
	// 转换流 利用封装好的readLine()方法
	// 装饰增强的效果
	// 问题:  中文好像不行,比如搜狗输入法的时候,字符会变多
	public static void read_2() throws IOException {
		
		/*
		InputStream in = System.in;  // 获取键盘录入对象
		InputStreamReader isr = new InputStreamReader (in);  // 转换流对象类型,字节流转换为字符流
		BufferedReader bufr = new BufferedReader(isr);  // 建立缓存区,提高效率
		*/
		
		// 最常见键盘录入
		BufferedReader bufr = 
				new BufferedReader(new InputStreamReader(System.in));
		// 输出
		BufferedWriter bufw = 
				new BufferedWriter(new OutputStreamWriter(System.out));
		
		String line = null;
		while ((line=bufr.readLine()) != null) {
			if (line.equals("over")) {
				break;
			}
			bufw.write(line);
			bufw.newLine(); // BufferedWriter里的增强方法
			bufw.flush(); // 记得write时候数据是在缓冲区,需要进行flush()	
			/*
			System.out.println(line); // 将System.out包装起来了
			*/
		}
	}
}

按需求分析
  1. 明确源和目的体系

    • 源:输入流 InputStream Reader
    • 目的:输出流 OutputStream Writer
  2. 明确操作数据是否为纯文本

    • 是:字符流
    • 不是: 字节流
  3. 明确设备

    • 源设备: 内存,硬盘,键盘
    • 目的设备: 内存,硬盘,控制台
  4. 是否要提高效率


练习:将一个文本数据打印在控制台上

*源:
1. 输入,纯文本 ----> Reader体系
2. 硬盘文件 ----> FileReader
3. 提高效率 ----> BufferedReader

BufferedReader bufr = new BufferedReader(new FileReader("Demo.txt"));

*目的:
1. 输出,纯文本 ----> Writer体系
2. 控制台 ----> System.out // System.out是一个字节流对象,此时,需要通过转换流将System.out转换成Writer体系
3. 提高效率 ----> BufferedWriter

BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

示例代码:

import java.io.*;
public class WriteInFile {
	public static void main(String[] args) throws IOException {
		BufferedReader bufr = new BufferedReader(new FileReader("E:\\workspace\\GUI.txt"));
		BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
		
		String line = null;
		
		while ((line= bufr.readLine()) != null) {
			bufw.write(line);
			bufw.newLine();
			bufw.flush();
		}
	}	
}
拓展:

转换流的作用是以特定的编码表存储字符

  • InputStreamReader(InputStream in, String charsetName)
  • OutputStreamWriter(OutputStream out, String charsetName)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值