一
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包装起来了
*/
}
}
}
七
按需求分析
-
明确源和目的体系
- 源:输入流 InputStream Reader
- 目的:输出流 OutputStream Writer
-
明确操作数据是否为纯文本
- 是:字符流
- 不是: 字节流
-
明确设备
- 源设备: 内存,硬盘,键盘
- 目的设备: 内存,硬盘,控制台
-
是否要提高效率
练习:将一个文本数据打印在控制台上
*源:
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)