JavaImprove--Lesson10--IO流-字符流,缓冲流,转换流,打印流,数据流
一.IO流-字符流
上期在字节流的学习中,了解到字节流写字符操作表现很不好,随时面临乱码的情况,一下写完全部数据的,内存可能不足,所以对于文本操作还需要专业的操作
而字符流就非常适合操作文本字符
FileWirte文件字符输入流
作用:以内存为基准,可以把文件的数据以字符的形式输入到内存中去
构造器:
public FileReader(File file);以文件的形式,创建字符流与其的链接
public static void main(String[] args) throws Exception { File file = new File("static/text/test.txt"); FileReader rd = new FileReader(file); rd.close(); }
public FileReader(String pathName);以路径的形式,创建字符与其的链接
public static void main(String[] args) throws Exception { Reader rd = new FileReader("static/text/test.txt"); rd.close(); }
文件字符输入流所有的方法:
public int read()每读取一个字符返回,如果没发现数据可读则返回-1
public static void main(String[] args) throws Exception { Reader rd = new FileReader("static/text/test.txt"); int len; //每次读取一个字符 while ((len = rd.read())!=-1){ System.out.print((char) len); } rd.close(); }
public int read(char[ ] buffer);每次读取一个字符数组的长度,返回读取字符的多少,没有字符后返回-1
public static void main(String[] args) throws Exception { Reader rd = new FileReader("static/text/test.txt"); int len; //申请每次读取的字符数组大小 char[] buffer = new char[8]; //每次读取一个字符 while ((len = rd.read(buffer))!=-1){ System.out.print(new String(buffer,0,len)); } rd.close(); }
字符输入流是以字符为单位,所以不会乱码
FileWriter文件字符输出流
作用:以内存为基准,把内存中的数据以字符的形式写出到文件
构造器:
public FileWriter(File flie)以文件的形式,与字符输出流创建管道
public static void main(String[] args) throws Exception { //以文件的方式创建链接 File file = new File("static/text/test.txt"); FileWriter fw = new FileWriter(file); fw.close(); }
public FileWriter(String pathName);以路径的形式,与字符输出流创建管道
public FileWriter(String pathName,boolean append);
public static void main(String[] args) throws Exception { //以路径的方式创建链接 FileWriter fw = new FileWriter("static/text/test.txt",true);//追加的方式写出 fw.close(); }
字符输出流的方法:
void writer(int c);写出一个字符
public static void main(String[] args) throws Exception { //以路径的方式创建链接 FileWriter fw = new FileWriter("static/text/test.txt",true);//追加的方式写出 fw.write('c'); fw.close(); }
void writer(String str);写一个字符串
public static void main(String[] args) throws Exception { //以路径的方式创建链接 FileWriter fw = new FileWriter("static/text/test.txt",true);//追加的方式写出 fw.write("abcdef"); fw.close(); }
void writer(String str,int pos,int len);写出写出一个字符串的一部分
public static void main(String[] args) throws Exception { //以路径的方式创建链接 FileWriter fw = new FileWriter("static/text/test.txt",true);//追加的方式写出 fw.write("我爱中国,asd",0,4); fw.close(); }
void writer(char[ ] buffer);写出一个字符串数组
public static void main(String[] args) throws Exception { //以路径的方式创建链接 FileWriter fw = new FileWriter("static/text/test.txt",true);//追加的方式写出 char[] str = {'a','b','c'}; fw.write(str); fw.close(); }
void writer(char[ ] buffer,int pos,int len);写出一个字符串数组一部分
public static void main(String[] args) throws Exception { //以路径的方式创建链接 FileWriter fw = new FileWriter("static/text/test.txt",true);//追加的方式写出 fw.write("\r\n");//写入换行符 char[] str = {'a','b','c'}; fw.write(str,0,2); fw.close(); }
注意点:
在使用文件输出流时,必须刷洗流或者关闭流,写出去的数据才能生效
原因:在Java写出字符时,会频繁的操作系统资源,为了减小系统资源的消耗,Java引入缓冲机制,也就是在写字符操作时,不管你操作多少次,写一个字符还是多个字符,都没有直接写入硬盘,而是写入到了内存缓冲区中了
当我们关闭流或者刷新流时,才会刷入硬盘,这样只操作一次就完成了输出
还有当缓冲区被装满时,会自动刷入一次硬盘,关闭的流就不能用了,必须重新建立链接
总结:字节流适合写入非文本文件,字符流适合写入文本文件
二.缓冲流Buffered
对原始流的包装形式,以提高原始流的读写数据效率
它们并不是直接连接到数据源或数据目标,而是连接到另一个流(称为节点流或底层流),并为其提供一个缓冲区。
当从缓冲流读取数据时,它会尝试从内部缓冲区提供数据,而不是立即从底层流中读取,从而减少了从底层数据源读取数据的次数
低级流的意思是更接近于底层的流
缓冲流的工作原理:
- 缓冲流Buffer并不直接连接底层管道,而是将初始流做一个包装
- Buffer中自带一个8kb大小的缓冲区,当初始流连接上管道后,并不会直接读取到程序中,而是装满这8kb的buffer,然后程序在这个buffer中慢慢拿
- 所以,不管程序一次I/O需要拿多少数据,都是在buffer中拿的,而buffer就是第一I/O一次装入的,和程序的拿取无关
- 目的就是提高I/O效率,不让程序频繁调用系统接口,尤其避免程序每次写一个字节,然后写1024次的情况
BufferedInputStream字节缓冲输入流
public static void main(String[] args) { try ( //拿取文件管道流 InputStream is =new FileInputStream("static/text/test.txt"); //封装成缓冲流 InputStream bis = new BufferedInputStream(is) ){ byte[] buffer = new byte[1024]; //循环拿取数据 int len; while ((len = bis.read(buffer)) != -1){ System.out.println(new String(buffer,0,len)); } }catch (Exception e){ System.out.println(e); } }
可看源码,是否有8kb的缓冲区:
如上:默认的buffer大小就是8kb,但是其实它是可以更改的,可以人为扩大的
//封装成缓冲流 InputStream bis = new BufferedInputStream(is,8192 * 4)
这段代码又将缓冲区改为了8kb * 4 就是32kb
BufferedOutputStream字节缓冲输出流
public static void main(String[] args) { try ( //拿取文件管道流 OutputStream os =new FileOutputStream("static/text/test.txt"); //封装成缓冲流 OutputStream bos = new BufferedOutputStream(os) ){ //构造一个字节数组 byte[] buffer = {'a','b','c','d','e'}; bos.write(buffer); }catch (Exception e){ System.out.println(e); } }
BufferedReader字符缓冲输入流
BufferedReader和字节流的定义都是一样的
但是和原始流相比新增了一个方法
public String readLine();读取一行字符回来,将读取字符按行为单位来读取,当没有字符时返回null
public static void main(String[] args) { try ( //创建字符流和文件的管道连接 Reader fd = new FileReader("static/text/test.txt"); //封装成缓冲流 BufferedReader bfd = new BufferedReader(fd) ){ String rs=null; while ((rs=bfd.readLine())!=null){ System.out.println(rs); } }catch (Exception e){ e.printStackTrace(); } }
BufferedWriter字符缓冲输出流
字符缓冲输出流也有一个新的方法,是一个换行方法
public void newLine()换行
public static void main(String[] args) { try ( //创建字符流和文件的管道连接 Writer fw = new FileWriter("static/text/test.txt"); //封装成缓冲流 BufferedWriter bfd = new BufferedWriter(fw) ){ bfd.write("虽然,我爱这世界"); bfd.newLine(); bfd.write("但是,与之相比,我更爱我的祖国"); bfd.newLine(); }catch (Exception e){ e.printStackTrace(); } }
总结
由于缓冲流和不同的流使用没有什么新的特点,所以就没有过多赘述,我们只需要知道它是提供更高性能的流就好了,尽量都使用缓冲流吧
案例
将一段文本按行分类,标上序号(乱序),然后根据需要排序后,按行输出文本正确的序号
题目:
3.abcde 5.hladjdla 4.dhaldajld 9.addadad 1.dadadjald 6.adlajldjald 2.dadadkahdkad 7.poadpiadpa
要求输出:
1.dadadjald 2.dadadkahdkad 3.abcde 4.dhaldajld 5.hladjdla 6.adlajldjald 7.poadpiadpa 9.addadadd
代码展示:
public static void main(String[] args) { try ( //获得输入输、出流与文件建立连接 Reader rd = new FileReader("static/text/MarchingList.txt"); Writer wr = new FileWriter("static/text/test.txt"); //创建buffer缓冲流封装文件流 BufferedReader brd = new BufferedReader(rd); BufferedWriter bwr = new BufferedWriter(wr); ){ //申请一个集合存储文件行,为什么不直接写出去,而是存在集合中?因为没有排序 ArrayList<String> list = new ArrayList<>(); String rs =null; while ((rs = brd.readLine())!= null){ list.add(rs); } //排序,使用Collections自带的排序算法 Collections.sort(list); //读取集合中排好序的行,并写出到文件 for (String s : list) { bwr.write(s); //换行 bwr.newLine(); } bwr.flush();//刷入内存 }catch (Exception e){ e.printStackTrace(); } }
注意:
像上面这种例子,一次需要申请两个流(输入/输出流)时,它们两个流的路径不能指向同一个文件,否则输入流读不到数据
原因:
1.当输入流在读文件时,会上锁此文件,只要还没关闭此读写流,则不会释放锁,而此时去写文件会没有权限,所以写不进去
2.当申请完输出流和文件建立连接时,说明此时文件存在写操作,所以会上锁,当输入流在读取文件时,会因为锁没有权限,读取文件失败
所以尽量文件读取写入不要重名文件,出了问题很难找
缓冲流的性能
缓冲流的性能如何?需要测试
分别使用原始字节流,以及字节缓冲流复制一个很大的视频
测试步骤:
1.使用低级流一个一个字节的复制
2.使用低级流以数组形式的复制
3.使用缓冲高级流一个一个字节的复制
4.使用缓冲高级流以数组形式的复制
代码如下:
public static void main(String[] args) { //测试数据为:300MB //test1();//一个一个字节复制太慢了,懒得等了直接关了 test2(); //3.258s 字节数组:低级流 test3(); //6.815s 一个一个字节:缓冲流 test4(); //0.538s 字节数组:缓冲流 } //一个一个字节复制,并且使用低级流 public static void test1(){ long t1 = System.currentTimeMillis(); try ( //得到流 InputStream is = new FileInputStream("C:\\Users\\86173\\Videos\\Captures\\happyNewYear.mp4"); //输出流 OutputStream os = new FileOutputStream("C:\\Users\\86173\\Videos\\Captures\\happyNewYear1.mp4"); ){ int len; while ((len = is.read())!=-1){ os.write(len); } long t2 = System.currentTimeMillis(); System.out.println("字节输出流一个一个输出时间为:"+(t2-t1)/1000.0); }catch (Exception e){ e.printStackTrace(); } } //使用1kb数组复制,低级流 public static void test2(){ long t1 = System.currentTimeMillis(); try ( //得到流 InputStream is = new FileInputStream("C:\\Users\\86173\\Videos\\Captures\\happyNewYear.mp4"); //输出流 OutputStream os = new FileOutputStream("C:\\Users\\86173\\Videos\\Captures\\happyNewYear2.mp4"); ){ int len; byte[] buffer = new byte[1024]; while ((len = is.read(buffer))!=-1){ os.write(buffer,0,len); } long t2 = System.currentTimeMillis(); System.out.println("字节输出流一个一个输出时间为:"+(t2-t1)/1000.0); }catch (Exception e){ e.printStackTrace(); } } //一个一个字节复制,缓冲流 public static void test3(){ long t1 = System.currentTimeMillis(); try ( //得到流 InputStream is = new FileInputStream("C:\\Users\\86173\\Videos\\Captures\\happyNewYear.mp4"); //输出流 OutputStream os = new FileOutputStream("C:\\Users\\86173\\Videos\\Captures\\happyNewYear3.mp4"); //缓冲流 BufferedInputStream bis = new BufferedInputStream(is); BufferedOutputStream bos = new BufferedOutputStream(os); ){ int len; while ((len = bis.read())!=-1){ bos.write(len); } long t2 = System.currentTimeMillis(); System.out.println("字节输出流一个一个输出时间为:"+(t2-t1)/1000.0); }catch (Exception e){ e.printStackTrace(); } } //字节数组复制,缓冲流 public static void test4(){ long t1 = System.currentTimeMillis(); try ( //得到流 InputStream is = new FileInputStream("C:\\Users\\86173\\Videos\\Captures\\happyNewYear.mp4"); //输出流 OutputStream os = new FileOutputStream("C:\\Users\\86173\\Videos\\Captures\\happyNewYear4.mp4"); //缓冲流 BufferedInputStream bis = new BufferedInputStream(is); BufferedOutputStream bos = new BufferedOutputStream(os); ){ int len; byte[] buffer = new byte[1024]; while ((len = bis.read(buffer))!=-1){ bos.write(buffer,0,len); } long t2 = System.currentTimeMillis(); System.out.println("字节输出流一个一个输出时间为:"+(t2-t1)/1000.0); }catch (Exception e){ e.printStackTrace(); } }
性能测试图测试,数据为300MB的视频复制:
总结:
- 当一个一个字节写入时,不管是不是缓冲流,效率都很低,但是缓冲流确实比原始流快
- 当为字节数组时,速度有质的飞跃,在1024字节中,缓冲流比原始流快,原因是缓冲流buffer为8kb,而原始流的buffer很小(没错原始流也有buffer)
- 当扩大字节数组大小时缓冲流提升不大,而原始流提升很大
- 当达到某个阈值时,两个流速度等大,且增加字节数组大小增速也慢下来了,原因是,此时问题并不在内存,而是内存和硬盘之间速度不匹配的原因,也就是内存写的速度可能只要零点几毫秒(还是很快),而内存写入硬盘需要零点几秒(这里很慢),所以此时再增大字节数组效果不明显了,这时候就是硬件的原因
故:字节数组读取写入数据的确是越大越好,尤其是在8~32kb之间,两个流是一样的,并不需要特别使用那个流,而越往后就不需要增加大小了,因为硬件速度跟不上了,继续增大只会消耗更大的内存空间,没必要
在自己不确定使用字节数组大小时,推荐使用buffer缓冲流,Java官方是很关注开发者群体的,它提供的方案一般考虑了大多数情况
当然,你想自定义原始流的字节数组来写入也是可以的
三.转换流
引入:不同编码的读取会出现乱码
如果读入的编码和文件的编码是一样的,使用字符流读取则不会出现乱码情况
如果读取的编码和文件的编码是不一样的,使用字符流去读取则会出现乱码的情况
示例:如下有一段GBK编码的文本t2.txt
我爱中国,我爱世界 I love China,And I love the world!
使用UTF-8去读文件:
public static void main(String[] args) { //Idea默认编码UTF-8 try ( //申请一个文件读取流 Reader fr = new FileReader("static/text/t2.txt"); ){ //循环读取文件 int len; while ((len = fr.read())!=-1){ System.out.print((char) len); } }catch (Exception e){ e.printStackTrace(); } }
输出如下:
�Ұ��й����Ұ����� I love China,And I love the world!
由此可知,文件编码不一致,会导致读取乱码
所以,Java提供了转换流,用来按照选定编码读取文件,就不会乱码了
InputStreamReader字符输入转换流
字符输入转换流是字符流Reader下的实现流,专门为解决读取文件时,平台编码和文件编码不一致,最终导致乱码的情况
解决思路:
先获取文件的原始字节流,再将其按真实的字符集转成字符流,这样流就不会乱码了
换个说法,在读取文件时,编码不在使用默认的平台编码,而是你传入的编码字符集
构造器:
public InputStreamReader(InputStream is,String charset);
把原始字节流,按照指定的字符集编码成字符流
public static void main(String[] args) { try( //普通字节输入流 InputStream is = new FileInputStream("static/text/t2.txt"); //装入字符转换输入流:传入字节输入流,编码字符集 InputStreamReader isr = new InputStreamReader(is,"GBK"); ) { //通过字符转换输入流读取文件 int len; while ((len = isr.read())!=-1){ System.out.print((char) len); } }catch (Exception e){ e.printStackTrace(); } }
使用字符转换输入流读取效果:
我爱中国,我爱世界 I love China,And I love the world!
OutputStreamWriter字符转换输出流
字符抓换输出流的思想和输入流一样,就是按照自定义的字符集进行编码,然后输出到文件
解决思路:
获取字节输出流,然后指定字符集编码成字符输出流,写出去的字符就会使用此字符集
构造器:
public OutputStreamWriter(OutputStream os,String charset);
把原始的字节输出流,按照指定字符集转换为字符输出流
public static void main(String[] args) { try ( //获得原始字节输出流 OutputStream os = new FileOutputStream("static/text/t2.txt"); //装入字符转换输出流 OutputStreamWriter osw = new OutputStreamWriter(os,"GBK"); ){ //写出一个文本 String rs = "我爱中国"; osw.write(rs); }catch (Exception e){ e.printStackTrace(); } }
四.打印流Print
打印流,顾名思义就是打印出去,所以打印流只有输出流,没有输入流
而打印流有两个实现类,PrintStream,PrintWriter
PrintStream是OutputStream字节输出流的实现类,而PrintWriter则是Writer字符输出流的实现类
作用:
打印流可以更加方便的将数据打印出去,更高效,能实现参数是什么,打印的就是什么
写出方法writer写出97可能写出的是a,打印流print就是97,所以更高效
PrintStream
构造器:
public PrintStream(OutputStream/File/Path );打印流可以通向输出流,文件,路径,也是一个高级流
public PrintStream(path,boolean autoFlush,String encode);第一个参数是路径,第二个参数是自动刷新写入文件,第三个参数是指定字符集
方法:
public void println(数据类型)打印任何数据出去
public static void main(String[] args) { try( //创建一个打印输出流 PrintStream ps =new PrintStream("static/text/t2.txt"); ){ ps.println("我爱中国"); ps.println(97); ps.println('a'); }catch (Exception e){ e.printStackTrace(); } }
PrintWriter
构造器:
public PrintWriter(OutputStream/File/Path );打印流可以通向输出流,文件,路径,也是一个高级流
public PrintWriter(path,boolean autoFlush,String encode);第一个参数是路径,第二个参数是自动刷新写入文件,第三个参数是指定字符集
方法:
public void println(数据类型)打印任何数据出去
public static void main(String[] args) { try( //创建打印流 PrintWriter pw = new PrintWriter("static/text/t2.txt"); ){ pw.println("我爱中国"); pw.println(97); pw.println('a'); }catch (Exception r){ r.printStackTrace(); } }
总结:
打印数据的功能上是一摸一样的:都是方便,性能高效(核心优势)
PrintStream继承自字节流,可以写出字节的方法
PrintWriter继承自字符流,可以写出字符的方法
输出语句重定向
System.out.println()这是一句输出内容到控制台中
输出语句的重定向可以将此语句输出到文件中
因为在out中,也接入了一个流,就是连接控制台的管道流;所以只需要改流到文件中,就可以输出到文件中
public static void main(String[] args) { try ( //控制台输出到文件的位置 OutputStream is = new FileOutputStream("static/text/t2.txt",true); //装载如输出流 PrintStream ps = new PrintStream(is); ){ //装入打印流,更改输出源为文件t2.txt System.setOut(ps); System.out.println("我爱中国"); }catch (Exception e){ e.printStackTrace(); } }
如上:
System.out.println("我爱中国");这句话就输出到文件中了
五.数据流
数据流是一种特殊的流,在前面学习的流,不管是字节流,字符流,还是其它流,读取和写入的时候都是以String类型或byte类型的
而数据流就如其名字一样,可以写出数据和数据类型的,会带有其数据类型,如int,boolean,double类型等
但是写出的文件,我们可能看不懂,毕竟有类型在,要想看必须使用读入流读取后才能看到
DataOutputStream数据字节输出流
构造器:
public DataOutputStream(OutputStream os);创建数据输出流,包装基础输出流
方法:
public final void writeByte(int v);输出byte的数据类型
public final void writeInt(int v);输出int型数据
public final void writeDouble(double v)输出double类型的数据到文件
public final void writeUTF(String str)输出一个字符串到文件
public static void main(String[] args) { try ( //低级文件字节输出流 OutputStream os = new FileOutputStream("static/text/t3.txt"); //装载到数据输出流中 DataOutputStream dos = new DataOutputStream(os); ){ dos.writeByte(97); //字节 dos.writeInt(1); //整型 dos.writeBoolean(true);//布尔型 dos.writeDouble(98.1);//双精度浮点型 dos.writeUTF("我爱中国");//字符串 }catch (Exception e){ e.printStackTrace(); } }
写入后文件内容如下:
虽然看不懂,也不需要去解码,因为带了类型,所以解码不出来的,本来也不是给我们看的,而是数据读入流去读取的
使用读取流读取后就可以输出看到,但是读取的时候,注意类型对应,先存入的是字节型,读取的时候也应该是字节型先读取
DataInputStream数据字节输入流
构造器:
public DataInputStream(InputStream is);创建数据输出流,包装基础输出流
方法:
public final void readByte();读取byte的数据类型
public final void readInt(int v);读取int型数据
public final void readDouble(double v)读取double类型的数据到文件
public final void readUTF(String str)读取一个字符串到文件
public static void main(String[] args) { try ( //低级文件字节输出流 InputStream is = new FileInputStream("static/text/t3.txt"); //装载到数据输出流中 DataInputStream dis = new DataInputStream(is); ){ System.out.println(dis.readByte()); //字节 System.out.println(dis.readInt()); //整型 System.out.println(dis.readBoolean()); //布尔型 System.out.println(dis.readDouble()); //双精度浮点型 System.out.println(dis.readUTF()); //字符串 }catch (Exception e){ e.printStackTrace(); } }
输出如下: