一、概述
- IO流用来处理设备之间的数据传输。(设备:如内存到硬盘、U盘、光盘等)
- Java对数据的操作是通过流的方式。
- Java用于操作流的对象都在IO包中。
- 流按操作数分两种:字节流和字符流。
- 流按流向分为:输入流和输出流。
IO流常用基类:
- 字节流的抽象顶层基类(顶层父类):InputStream,OutputStream
- 字符流的顶层基类:Reader, Writer
二、输入流&输出流
输入与输出是相对内存而言的。
将外设中的数据读取到内存中,就是输入。
将内存的数据写入到外设中,就是输出。
三、字节流&字符流
字节流可以处理所有数据。为什么呢?
Unicode码表是一个国际组织将各种各样码表统一起来的码表,包括中文和英文。
字符流怎么来的?其实就是字节流读取文字字节数据后不直接操作,而是先查指定的编码表 ,获取对应的文字,然后再对文字进行操作。
简单说:字节流+编码表
四、字符流FileWriter
需求:将一些文字存储到硬盘一个文件中。
注意:如果要操作文字数据,建议优先考虑字符流。而且要将数据从内存写到硬盘上,要使用字符流中的输出流。
FileWriter:位于IO->Writer->OutputStreamWriter里面
硬盘的数据基本实现是文件。希望找到一个可以操作文件的Writer。
使用FileWriter的例子(使用构造函数FileWriter(String FilePath)):
import java.io.FileWriter;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
try {
/*调用构造函数FileWriter(String File)写入数据*/
FileWriter fw = new FileWriter("demo.txt");
fw.write("abcde---");//数据被临时存储到流里面了
// fw.flush();//进行刷新,将数据直接写入到目的地中
fw.close();//关闭此流,关闭之前自动刷新缓冲区到目的地
} catch (IOException e) {
e.printStackTrace();
}
try {
/*调用构造函数FileWriter(String fileName, boolean append)写入数据*/
FileWriter fw2 = new FileWriter("demo.txt", true);
fw2.write("abcde"+SEPARATOR+"---");//数据被临时存储到流里面了
fw2.write(SEPARATOR+"JQK");
// fw.flush();//进行刷新,将数据直接写入到目的地中
fw2.close();//关闭此流,关闭之前自动刷新缓冲区到目的地
} catch (IOException e) {
e.printStackTrace();
}
}
}
异常处理:IOException
import java.io.FileWriter;
import java.io.IOException;
public class Main {
private static final String SEPARATOR = System.getProperty("line.separator");
public static void main(String[] args) {
FileWriter fw = null;//初始化为空
try {
fw = new FileWriter("demo.txt");
fw.write("abcde"+SEPARATOR+"---");//数据被临时存储到流里面了
} catch (IOException e) {
e.printStackTrace();
}
finally {//无论成功与否,关闭流
try {//当流不为空,关闭。try..catch的原因和上面一样
if(fw!=null) fw.close();//关闭此流,关闭之前自动刷新缓冲区到目的地
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
五、字符流FileReader
需求:读取一个文本文件。将读取到的字符打印到控制台。
方式一:read()方法,一个一个读取
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class ReadDemo {
public static void main(String[] args) {
FileReader fr=null;
try {
fr = new FileReader("demo.txt");
int p;
while((p=fr.read()) != -1){//当读到的是-1表示读取结束。
System.out.print((char)p);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
方式二:使用read(char[] )读取文本文件数据
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class ReadDemo {
public static void main(String[] args) {
long time1 = System.currentTimeMillis();
FileReader fr=null;
try {
fr = new FileReader("demo.txt");
char[] ch = new char[3];//可以尝试不同数组长度的运行速度,建议值1024整数倍
int len = 0;
while((len=fr.read(ch))!=-1){
System.out.print(new String(ch, 0, len));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fr.close();
System.out.println();
} catch (IOException e) {
e.printStackTrace();
}
}
long time2 = System.currentTimeMillis();
System.out.println("总耗时:"+(time2-time1)+"ms");
}
}
练习:将C盘一个文本文件复制到d盘。
分析:
复制原理:读取C盘文件中的数据,将这些数据写入到D盘中。(连读带写)
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyTextTest_2 {
private static final int BUFFER_SIZE = 1024;
/**
* @param args
*/
public static void main(String[] args) {
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("IO流_2.txt");
fw = new FileWriter("copytest_2.txt");
//创建一个临时容器,用于缓存读取到的字符。
char[] buf = new char[BUFFER_SIZE];//这就是缓冲区。
//定义一个变量记录读取到的字符数,(其实就是往数组里装的字符个数 )
int len = 0;
while((len=fr.read(buf))!=-1){
fw.write(buf, 0, len);
}
} catch (Exception e) {
// System.out.println("读写失败");
throw new RuntimeException("读写失败");
}finally{
if(fw!=null)
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
if(fr!=null)
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
六、字符流缓冲区
缓冲区的出现提供了对数据的读写效率。
对应的类有BufferedWriter和BufferedReader。同时注意,缓冲区要结合流才可以使用在流的基础上对流的功能惊醒了增强。
优化性能比较有效的方法之一就是建立缓冲区。
1.BufferedWriter
构造函数
BufferedWriter(Writer w);
BufferedWriter(Writer w, int sz);
import java.io.*;
public class Main {
private static final String SEPARATOR = System.getProperty("line.separator");
public static void main(String[] args) {
FileWriter fw = null;
try {
/*调用Writer写入数据*/
fw = new FileWriter("demo.txt");
//建立缓冲区
BufferedWriter buffw = new BufferedWriter(fw);
//使用缓冲区的写入方法
buffw.write("abcde"+SEPARATOR+"---接口");
//刷新并关闭缓冲区,注意:此时本质是关闭了被缓冲的流对象
buffw.close();
} catch (IOException e) {
e.printStackTrace();
}
finally {
try {
if(fw!=null) fw.close();//关闭此流,关闭之前自动刷新缓冲区到目的地
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.BufferedReader
注意其中的特有方法readline()
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class ReadDemo {
public static void main(String[] args) {
long time1 = System.currentTimeMillis();
FileReader fr=null;
try {
fr = new FileReader("demo.txt");
BufferedReader bufr = new BufferedReader(fr);
String line=null;
while((line=bufr.readLine())!=null){//注意这里的readline方法的使用,读取一整行
System.out.println(line);
}
bufr.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fr.close();
System.out.println();
} catch (IOException e) {
e.printStackTrace();
}
}
long time2 = System.currentTimeMillis();
System.out.println("总耗时:"+(time2-time1)+"ms");
}
}
重写读写练习:将C盘一个文本文件复制到d盘。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyTextByBufTest {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("buf.txt");
BufferedReader bufr = new BufferedReader(fr);
FileWriter fw = new FileWriter("buf_copy.txt");
BufferedWriter bufw = new BufferedWriter(fw);
String line = null;
while((line=bufr.readLine())!=null){
bufw.write(line);
bufw.newLine();
bufw.flush();
}
bufw.close();
bufr.close();
}
}
3.自定义MyBufferedReader的read、readLine方法
可以试着自己来做一遍,与BufferedReader功能相同的类,以便对缓冲区有深刻的理解。
要求:
- 从硬盘(源)读取文件所有信息(当文件小于Buffer大小时)到内存的缓冲区中;
- read方法实现对内存中数组进行访问(一个一个的访问)的方法;
- 此次取完后,再从源中继续取一批数据,取完用-1作为标记;
- readLine方法实现对回车换行的识别;
import java.io.FileReader;
import java.io.IOException;
public class MyBufferedReader {
private FileReader r;
private char[] buf = new char[1024];
//定义操作这个数组中的元素
private int pointer = 0;
//记录缓冲区中数据个数
private int counter = 0;
MyBufferedReader(FileReader r){
this.r=r;
}
/**
*
* @return
* @throws IOException
*/
public int myRead() throws IOException {
//从源中获取一批数据到缓冲区
if (counter==0) {//等于0才需要从源中获取
counter = r.read(buf);
pointer = 0;//因为从pointer已经到头了,需要从头来做
}
if(counter<0) return -1;
//取出要返回的字符(单个)
char ch = buf[pointer++];
counter--;
return ch;
}
public String myReadLine() throws IOException {
StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch = myRead()) != -1){
//读到\n停止并返回
if(ch != '\n'){
if(ch=='\r') continue;//读到\r既不存也不停止
//将读到的字符存储到缓存行数据的缓冲区中
sb.append((char) ch);
}
else break;
}
if (sb.length()!=0) {
return sb.toString();
}
return null;//注意别少了return null否则停不下来
}
public void myClose() throws IOException {
if(r!=null) {
r.close();
}
}
}
七、装饰设计模式
我们注意到,在上面讲到的BufferedReader中的read()方法对原本的FileReader中的read()方法进行了功能上的增强(将数据一次性大量加载到缓冲区,提升了运行效率)。这样的基本功能不变,运行效率增强的做法是一种新的设计模式——装饰设计模式。
1.定义:
对一组对象的功能进行增强时,就可以使用该模式进行问题的解决。
2.装饰和继承
装饰和继承都能实现一样的功能:进行功能的拓展增强。
那么他们之间有什么区别呢?
继承--->如果这个体系进行功能拓展,有了好多流对象。那么这个流要提高效率,是不是也要产生子类呢?是。这时就会发现职位提高功能,而进行的继承,导致继承体系越来越臃肿。不够灵活。(不能做到不想用某个功能就不用了)
那我们重新思考这个问题。装饰的思考方式是怎样的呢?
既然加入的都是同一种技术——缓冲。前一种是让缓冲和具体的对象相结合。可不可以将缓冲进行单独的封装,哪个对象需要缓冲,就将哪个对象和缓冲关联。
3.特点
装饰类和被装饰类都必须所属同一个接口或父类。
八、补充
LineNumberReader继承自 java.io.BufferedReader ,与其父类功能相似。
特点:一个缓冲字符输入流跟踪行号。这个类定义方法 setLineNumber(int)
和 getLineNumber()
分别设置和获取当前行号。
方法:
int getLineNumber() 获取当前行号。
void setLineNumber(int lineNumber) 设置当前行号。
我最近也是在不断的学习中,水平有限,如果有什么意见或建议请下方评论哦,我将十分感谢。
如果觉得有收获可以关注一波~~~