一、 字符流缓冲区
缓冲区的出现提高了对数据的读写效率,缓冲区要结合流才可以使用,所以在创建缓冲区之前,必须要先有流对象;
对应类:
BufferedWriter
BufferedReader
BufferedWriter
1.步骤:
1) 创建流对象:FileWriter fw=new FileWriter(“存储路径+文件名”);
2) 创建缓冲区对象,将流对象传入缓冲区构造函数(写入流和读取里要对应相应的缓冲区)
BufferedWriter bufw=new BufferedWriter(fw);
3) 调用缓冲区方法,对数据进行操作bufw.write(String str);//缓冲区为字符流的子类,具有其方法,可直接调用
bufw.blush();//注意:只要用到缓冲区,就要记得刷新; bufw.close();//关闭缓冲区,就是关闭缓冲区中的流对象;
2.缓冲区的特有方法:
newLine();该方法是一个跨平台的换行符;
示例:提高写入效率,使用缓冲区。
import java.io.*; class BufferWriterDemo { public static void main(String[] args) { //创建一个字符写入流对象。 FileWriter fw = new FileWriter("buf.txt"); //为了提高字符写入流效率,加入了缓冲技术。 //只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。 BufferedWriter bufw = new BufferedWriter(fw); for(int x= 1;x<5; x++) { //使用缓冲区的写入方法将数据先写入到缓冲区中 bufw.write("abcde"+x); bufw.newLine();//写入内容换行方法 bufw.flush(); } //记住,只要用到缓冲区,就要记得刷新。 bufw.flush(); //其实关闭缓冲区就是在关闭缓冲区中的流对象。 bufw.close(); } }
BufferedReader
1.步骤:该类是一个读取流缓冲区,使用方式与BufferedWriter一样
1) 创建一个读取流对象和文件相关联:
FileReader fr=new FileReader(“文件路径+文件名”);//该文件必须存在;
2) 创建一个缓冲区将fr作为参数传入构造函数:
BufferedReader bufr=new BufferedReader(fr);
3) 该类作为Reader的子类,拥有Reader的所有方法,同时该类还有一个一次读取一行的方法:
readLine();//读取一行
4) 该方法返回的是一个字符串,如果已达到六末尾返回null,故该值可作为判断流是否结束的标记;
String line=bufr.readLine();
5) 关闭流:
bufr.close();//关闭缓冲区即关闭流;
2.注意:
bufr.read():这个read方法是从缓冲区中读取字符数据,所以覆盖了父类中的read方法。
bufr.readLine():另外开辟了一个缓冲区,存储的是原缓冲区一行的数据,不包含换行符。
示例:提高读取效率,使用缓冲区。
import java.io.*; class BufferReaderDemo { public static void main(String[] args) { //创建一个读取流对象和文件相关联。 FileReader fr = new FileReader("buf.txt"); //为了提高效率,加入缓冲技术,将字符读取流对象作为参数传递给缓冲对象的构造函数。 BufferedReader bufr = new BufferedReader(fr); String line = null; while((line = bufr.readLine())!=null) { System.out.println(line); } bufr.close(); } }
LineNumberReader
1.跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行 号。
2.该类常用方法:
setLineNumber(int);//设置行号,让行号从参数int开始;
getLineNumber();//获取当前行号,该行号会加在行数据的前面,他们分别用于设置和获取当前行号;
import java.io.FileReader; 02. import java.io.IOException; 03. import java.io.LineNumberReader; 04. 05. public class LineNumberReaderDemo{ 06. public static void main(String[] args) throws IOException { 07. FileReader fr = new FileReader("LineNumberReaderDemo.java" ); 08. LineNumberReader lnr = new LineNumberReader(fr); 09. 10. String line = null; 11. 12. lnr.setLineNumber(100); 13. 14. while((line = lnr.readLine()) != null){ 15. System.out.println(lnr.getLineNumber() + ":" + line); 16. } 17. 18. lnr.close(); 19. } 20. }
二、装饰设计模式
1.当想要对已有的对象进行功能增强时,可以定义类,将已有对象传人,基于已有的功能,并提供加强功能。那么自定义的该 类称为装饰类。
2.装饰类通常会通过构造方法接收被修饰的对象。
并基于被修饰的对象的功能,提供更强的功能。
示例:
class Person { public void chifan() { System.out.println("吃饭"); } } class SuperPerson { private Person p; SuperPerson(Person p) { this.p = p; } public void superChifan() { System.out.println("开胃"); p.chifan(); System.out.println("甜点"); } } class PersonDemo { public static void main(String[] args) { Person p = new Person(); SuperPerson p1 = new SuperPerson(p); p1.superChifan(); } }
3.与继承的区别:装饰模式比继承要灵活,装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能, 所以装饰类和被装饰类通常都属于一个体系中的;
4.装饰类一般修饰的是一个类的多个子类,所以参数类型可以定义为这些子类的父类型,以多态的形式接收子类,这样就可以接 收多种类型的子类了,例:
BufferedReader的构造函数的参数类型为Reader;
BufferedReader(Reader in);//它可以接收Reader的子类;
分析:
缓冲区中无非就是封装了一个数组,并对外提供了更多的方法对数组进行访问,其实这些方法最终操作的都是数组的角标。
缓冲的原理:
其实就是从源中获取一批数据到缓冲区中,再从缓冲区中不断地取出一个一个数据。
在此次取完后,再从源中继续取一批数据进缓冲区,当源中的数据取完时,用-1作为结束标记。
import java.io.FileReader; import java.io.IOException; import java.io.Reader; class MyBufferedReader{ private Reader r; // 定义一个数组作为缓冲区 private char[] buf = new char[1024]; // 定义一个指针用于操作这个数组中的元素,当操作到最后一个元素后,指针应该归零 private int pos = 0; // 定义一个计数器用于记录缓冲区中的数据个数,当该数据减到 0 ,就从源中继续获取数据到缓冲区中 private int count = 0; MyBufferedReader(Reader r){ this .r = r; } // 该方法从缓冲区中一次取一个字符 public int myRead() throws IOException { // 从源中获取一批数据到缓冲区中,需要先做判断,只有计数器为 0 时,才需要从源中获取数据 if (count == 0){ count = r.read(buf); // 每次获取数据到缓冲区后,角标归零 pos = 0; } if (count < 0) return -1; 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); } if (sb.length() != 0){ return sb.toString(); } return null ; } public void myClose() throws IOException { r.close(); } } public class MyBufferedReaderDemo{ public static void main(String[] args) throws IOException { FileReader fr = new FileReader("buf.txt" ); MyBufferedReader bufr = new MyBufferedReader(fr); String line = null ; while ((line = bufr.myReadLine()) != null){ System.out.println(line); } bufr.myClose(); } }
三、字节流
1.字节流可操作任意类型的文件
2.分类:
读取流:InputStream
写入流:OutputStream
InputStream
此抽象类是表示字节输入流的所有类的超类;
该类的常用方法:
available() 该方法可获取读取文件的长度,返回int;
用该返回值定义数组长度可避免循环读取,但对于较大文件不适用;
close() 关闭流
read() 读取一个字节
read(byte[] b) 将读取的字节缓存到字节数组b中,长度可自定义(注:字符流定义的是字符 数组char[],字节流定义的是字节数组byte[])
read(byte[] b,int off,int len) 将输入流中最多len个数据字节流读入byte中
FileInputStream
1.该类是InputStream的子类用于操作文件的字节流子类,该类的用法与字符流相同:对象建立时需传入要操作 的文件;
2.该类的常用方法与父类相同;
示例:
import java.io.FileInputStream; import java.io.IOException; public class ByteStreamDemo{ public static void main(String[] args) throws IOException { demo_read1(); System.out.println( "---------------"); demo_read2(); System.out.println( "---------------"); demo_read3(); } // 读取方式一 public static void demo_read1() throws IOException { //1 、创建一个读取流对象,和指定文件关联 FileInputStream fis = new FileInputStream("bytedemo.txt" ); // 打印字符字节大小,不过要少用,文件太大,可能内存溢出 byte[] buf = new byte[fis.available()]; fis.read(buf); System.out.println( new String(buf)); fis.close(); } // 读取方式二 public static void demo_read2() throws IOException { FileInputStream fis = new FileInputStream("bytedemo.txt" ); // 建议使用这种读取数据的方式 byte[] buf = new byte[1024]; int len = 0; while((len = fis.read(buf)) != -1){ System.out.println( new String(buf,0,len)); } fis.close(); } // 读取方式三 public static void demo_read3() throws IOException { FileInputStream fis = new FileInputStream("bytedemo.txt" ); // 一次读取一个字节 int ch = 0; while((ch = fis.read()) != -1){ System.out.print(( char)ch); } fis.close(); } }
BufferedInputStream
1.该类时输入流缓冲区,用法与字符流缓冲区相同,将一个输入流对象作为参数传入构造函数;
2.该类常用方法:
available();
close();
read();
read(byte[] b,int off,int len);
3.注意:在BufferedInputStream原理中,可能出现还未读便结束的情况,因为,如果数据以8个1排列read方法将会 直接返回-1,则程序会判断没有数据便结束;
4.所以得出以下结论:字节流读取一个字节的read方法为什么返回类型不是byte,而是int,因为有可能读到8个1 而返回-1,那么数据没读完便结束,所以为了避免这种情况将读到的字节进行int类型提升,并在保留原字节 数据的情况前面补了24个0,变成int类型的数值,而在写入数据,只写入int数据的最低8位;
自定义缓冲区功能示例:
import java.io.*; class MyBufferedInputStream{ private InputStream in; private byte[] buf = new byte[1024*4]; private int pos = 0,count = 0; MyBufferedInputStream(InputStream in){ this.in = in; } //一次读一个字节,从缓冲区(字节数组)获取。 public int myRead()throws IOException{ //通过in对象读取硬盘上数据,并存储buf中。 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&0xff; } return -1; } public void myClose()throws IOException{ in.close(); } }
OutputStream
1.此抽象类时表示输出字节流的所有类的超类;
2.该类的常用方法:
close() 关闭流
flush() 刷新,写出所有缓冲的输出字节
write(byte[] b) 将 b.length 个字节从指定的 byte 数组写入此输出流
write(byte[] b,int off,int len) 将b中从off到len个字节写入此输入流;
write(int b) 将指定的字节写入此输入流
FileOutputStream
1.该类是OutputStream的一个子类,该类的使用方式与字符流输出流相同,在创建对象是就要指定文件;
2.如果文件已存在且不覆盖原文件,可使用:
FileOutputStream(String name,boolean append);
append值为true,则可直接在原文件后添加文件;
3.该类的常用方法与父类相同;
4.注意:操作字节流写入的时候不需要刷新;
示例:
import java.io.FileOutputStream; import java.io.IOException; public class ByteStreamDemo{ public static void main(String[] args) throws IOException { demo_write(); } public static void demo_write() throws IOException { //1、创建字节输出流对象,用于操作文件 FileOutputStream fos = new FileOutputStream( "bytedemo.txt"); //2、写数据,直接写入到了目的地中 fos.write( "abcdefg".getBytes()); //关闭资源动作要完成 fos.close(); } }
BufferedOutputStream
1.输出流缓冲区,用法与字符流缓冲区一样,将一个输出流对象作为参数传入构造函数使用;
2.该缓冲区常用方法:
flush(); 刷新
write(byte[] b,int off,int len); 将指定byte数组中从偏移量off开始的len个字节写入此缓冲区的输出流;
write(int b); 将指定的字节写入此缓冲的输出流;
练习:通过几种方式对MP3的进行拷贝,比较它们的效率。
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class CopyMp3Test{ public static void main(String[] args) throws IOException { copy_1(); copy_2(); } public static void copy_1() throws IOException { FileInputStream fis = new FileInputStream("0.mp3" ); FileOutputStream fos = new FileOutputStream("1.mp3" ); byte[] buf = new byte[1024]; int len = 0; while((len = fis.read(buf)) != -1){ fos.write(buf,0,len); } fis.close(); fos.close(); } public static void copy_2() throws IOException { FileInputStream fis = new FileInputStream("0.mp3" ); BufferedInputStream bufis = new BufferedInputStream(fis); FileOutputStream fos = new FileOutputStream("2.mp3" ); BufferedOutputStream bufos = new BufferedOutputStream(fos); int ch = 0; while((ch = bufis.read()) != -1){ bufos.write(ch); } bufis.close(); bufos.close(); } }
标准输入输出
1.System.in;对应标准输入设备:键盘;
System.in是一个字节流对象,是InputStream类型,可调用该类下的方法;例:
InputStream in=System.in;
in.read();//该方法为阻塞是线程,等待键盘录入;
import java.io.*; class ReadIn{ public static void main(String[] args) throws IOException{ InputStream in = System.in; StringBuilder sb = new StringBuilder(); while(true){ int ch = in.read(); if(ch=='\r') continue; if(ch=='\n'){ String s = sb.toString(); if("over".equals(s)) break; System.out.println(s.toUpperCase()); sb.delete(0,sb.length()); }else sb.append((char)ch); } } }
2.System.out;标准输入流,通常对应显示器或其它,此类是OutputStream的子类对象;
转换流的由来:
字符流与字节流之间的桥梁
方便了字符流与字节流之间的操作
转换流的应用:
字节流中的数据都是字符时,转成字符流操作更高效。
转换流:
InputStreamReader:字节到字符的桥梁,解码。
OutputStreamWriter:字符到字节的桥梁,编码。
InputStreamReader是字节流通向字符流的桥梁。
什么时候使用转换流呢?
1.源或者目的对应的设备是字节流,但是操作的却是文本数据,可以使用转换作为桥梁,提高对文本操作的便捷。
2.一旦操作文本涉及到具体的指定编码表时,必须使用转换流。
InputStreamReader
1.该类是Reader的子类,InputStreamReader是字节流通向字符流的桥梁,该类可以将字节流转换为字符流,并使用字符流的方法
2.使用方法:将一个字节流对象作为参数传入该类的构造函数:
例:InputStreamReader(InputStream in)
3.为了提高效率,可在BufferedReader内包装InputStreamReader,故常用做法如:
BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));
OutputStreamWriter
1.该类是Writer的子类,OutputStreamWriter是字符流通向字节流的桥梁,可以调用Writer类中的方法;
2.为提高效率可将OutputStreamWriter装饰到BufferedWriter中,常见用法:
BufferedWriter bufw=new BufferedWriter(new OutputStreamWriter(System.out));
3.注意:输出流写入的数据在缓冲区中,故每写一次就要flush()一次;
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class TransStreamDemo{ public static void main(String[] args) throws IOException { //字节流 InputStream in = System.in; //将字节转成字符的桥梁,转换流 InputStreamReader isr = new InputStreamReader(in); //对字符流进行高效装饰,缓冲区 BufferedReader bufr = new BufferedReader(isr); String line = null; //读取到了字符串数据 while((line = bufr.readLine()) != null){ if("over" .equals(line)) break; System.out.println(line.toUpperCase()); } } }
五、流的操作规律
之所以要弄清楚这个规律,是因为流对象太多,开发时不知道用哪个对象合适。想要知道对象的开发时用到哪些对象,只要通过四个明确即可。
1、明确源和目的
源:InputStream Reader
目的:OutputStream Writer
2、明确数据是否是纯文本数据
源:是纯文本:Reader
否:InputStream
目的:是纯文本:Writer
否:OutputStream
到这里,就可以明确需求中具体要使用哪个体系。
3、明确具体的设备
源设备:
硬盘:File
键盘:System.in
内存:数组
网络:Socket流
目的设备:
硬盘:File
控制台:System.out
内存:数组
网络:Socket流
4、是否需要其他额外功能
是否需要高效(缓冲区):
是,就加上buffer
需求:复制一个文本文件
1.明确源和目的。
源:InputStream Reader
目的:OutputStream Writer
2.是否是纯文本?
是!
源:Reader
目的:Writer
3.明确具体设备。
源:
硬盘:File
目的:
硬盘:File
FileReader fr = new FileReader("a.txt");
FileWriter fw = new FileWriter("b.txt");
4.需要额外功能吗?
需要,高效
BufferedReader bufr = new BufferedReader(new FileReader("a.txt"));
BufferedWriter bufw = new BufferedWriter(new FileWriter("b.txt"));
练习:将一个中文字符串数据按照指定的编码表写入到一个文本文件中。
1.目的:OutputStream,Writer
2.是纯文本:Writer
3.设备:硬盘File
FileWriter fw = new FileWriter("a.txt");
fw.write("你好");
import java.io.FileWriter; import java.io.IOException; public class TransStreamDemo { public static void main(String[] args) throws IOException { writeText(); } public static void writeText() throws IOException { FileWriter fw = new FileWriter("c.txt" ); fw.write( "你好"); fw.close(); } }
需求:打印c.txt文件中的内容至控制台显示。
import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; public class TransStreamDemo { public static void main(String[] args) throws IOException { writeText(); } public static void writeText() throws IOException { InputStreamReader isr = new InputStreamReader(new FileInputStream("c.txt" ),"UTF-8" ); char[] buf = new char[10]; int len = isr.read(buf); String str = new String(buf,0,len); System.out.println(str); isr.close(); } }
原因分析:由于c.txt文件中是GBK编码的“您好”,4个字节。
使用InputStreamReader类是用UTF-8编码进行读取,由于GBK编码的字节使用UTF-8没有对应的字符,因此使用“?”进行 代替。