第一讲.IO流概述
- Java中是通过IO流的方式处理设备间的数据传输,存放在java.io包中。
- 按照操作的数据单元分为字节流和字符流,按照流向分为输入流与输出流。
- 字节流的抽象基类:InputStream,OutputStream ,操作字节,其实字符流的底层也是字节流。
- 字符流的抽象基类:Reader,Writer ,操作字符,专门用来操作文本,可以处理各种文本编码(GBK,UTF-8)。
- IO流中相应的继承类后面都跟着它抽象基类的名字。像FileReader继承Reader,属于字节读取流等等。规律性强,还是很好记的。。。
- IO流的体系很庞杂,但只要学会举一反三,理解透了还是很容易的。。
第二讲.节点流——直接从磁盘文件或内存读写,在流体系中比较基础的流
- FileInputStream、FileOuputStream,字节流的基本输入、输出,下面为拷贝图片的例子:(这里异常处理尽量标准,第一个例程嘛~)
import java.io.*; //图片拷贝,只能用字节流。用字符流可能会出问题。 //因为字符流会尝试查编码表,如果查到对应则不变原码,否则就会到未知字符区查找并改变原码。 class CopyPicture { public static void main(String[] args){ FileInputStream in = null; FileOutputStream out = null; //in,out的声明应该写在外面,让finally中也能看到。初始化一个null是好习惯。 int ch; //这里好好地写了较标准的IO异常处理,即try/catch/finally这一套,以后直接抛了~ try { in = new FileInputStream("D:\\xiaxia.jpg");//注意转义\ out = new FileOutputStream("D:\\copy.jpg"); // 读一个byte,输出由byte提升为int,读不到返回-1 while((ch = in.read())!=-1){ // 写一个byte,就只截取int的低8位咯 out.write(ch); } //read、write还可以将数据读到数组buff中,这就放到字符流与这块对应的演示了。 } catch (IOException ex) { throw new RuntimeException("Copy IO failed!"); } finally//finally语句块中一般写资源关闭语句,但是close还要try! { try {out.close();} catch (IOException ein) {throw new RuntimeException("In close failed!");} finally{ try {in.close();} catch (IOException eout) {throw new RuntimeException("Out close failed!");} } } //close是关闭底层系统资源,这个GC显然是管不到的。out、in这些变量归它管。 //严格说必须在finally中执行关闭流操作,防止程序在close前就因为异常退出了。 } }
- FileReader、FileWriter,字符流的基本输入、输出,用法基本同上。下面有一个拷贝文本的例子:
//将D盘中一个文本文件存到C盘中 import java.io.*; class CopyText { public static void main(String[] args) throws IOException { FileReader in = new FileReader("D:\\dati.txt"); FileWriter out = new FileWriter("C:\\Copy.txt");//文件不存在就创建,存在就覆盖。想追加就用FilewWriter("",true); char[] buff = new char[1024];//缓冲数组,一般为1024的整数倍,String也行。字节流用byte[] //利用缓冲数组,减少磁盘的机械操作,加快速度~ int len;//读入的长度 while((len = in.read(buff))!=-1){ out.write(buff,0,len);//将buff中len长度的数据写入目的 } out.close(); in.close(); } }
第三讲.处理流(filters)
- 顾名思义,通过包装(装饰)其他流以增强其功能。典型的就是BufferedXXX这一系列:BufferedReader, BufferedWriter ,BufferedInputStream ,BufferedOutputStream。它们加入了缓冲功能,优化了读写性能,实例化只能传入流,表明它是专门为流进行优化的存在。
- BufferedInputStream,BufferedOutputStream,属于字节流,用法同其父类FileInputStream及FIleOutputStream没有太大的区别(注意如果写String类型的先用getBytes()方法转成byte数组,这对所用字节流输出流都适用~)。当然对应的read(),write()方法已经用缓冲的方式重写,具体见下面代码:自己写一个MyBufferedInputStream来模拟BufferedInputStream。把这个MyBufferedInputStream拿去替换前面第一个例程中的FileInputStream,效果一样,只不过我们这个是缓冲增强过的。
import java.io.*; class MyBufferedInputStream { private InputStream in; private int pos=0,len=0; private byte[] buff = new byte[1024]; MyBufferedInputStream(InputStream in){this.in = in;} public int read()throws IOException { if(len==0){ len = in.read(buff); if(len==-1)return -1; pos=0; } len--; return buff[pos++]&0xff;//这里需要确保-1(0xffffffff)的唯一性 //否则如果byte b= 0xff;(int) b 就是-1了 } public void close()throws IOException {in.close();} }
- BufferedReader,BufferedWriter,字符流,同其父类个增加了一个方法。其中BufferedReader的readLine()方法可以读一行,BufferedWriter的newLine()方法可以跨平台地写入换行符,比较有用。
//将D盘中一个文本文件存到C盘中 import java.io.*; //还是一样的功能,对比一下可发现,确实简单了不少。 class CopyText { public static void main(String[] args) throws IOException { //包装一下,这个语句比之前长多了。。。 BufferedReader in = new BufferedReader(new FileReader("D:\\dati.txt")); BufferedWriter out = new BufferedWriter(new FileWriter("C:\\Copy.txt")); String line = null; while((line = in.readLine())!=null){ out.write(line); out.newLine(); } out.close(); in.close(); } }
- 通过去实现readLine()方法(read()上面写过了),写一个MyBufferedReader去更好地理解BufferedReader:把之前代码中的BufferedReader换为MyBufferedReader效果一样~
import java.io.*; class MyBufferedReader { private Reader r; MyBufferedReader(Reader r){this.r = r;} public String readLine()throws IOException { int ch; StringBuilder sb = new StringBuilder(); while((ch=r.read())!=-1) { if(ch == '\r') continue; if(ch == '\n') //读完回车后,返回数据 return sb.toString(); sb.append(ch); } //防止最后一行没有回车 if(sb.length()!=0) return sb.toString(); return null; } public void close() throws IOException {r.close();} }
- 这里涉及到了装饰设计模式,BufferedReader这些处理流是很典型的装饰设计模式,它们的作用就是:为所有其他的流对象提供缓冲这种功能增强。装饰与继承是完全不同的两个概念。继承通过继承父类完成功能拓展,设想现在如果用继承来完成缓冲功能,只能通过为每一个具体子类新建一个Buffered子类来实现。而装饰是通过装饰父类去增强父类功能,只需有一个BufferReader,就可以为所有Reader添加缓冲功能了,简单而且还有拓展性。
第三讲.转换流与标准输入输出流
- 字节流处理文本有些时候不好用。但是有些场合不得不接触,像System.in定义的标准输入设备,这个in就是InputStream类型的;而System.out中的标准输出流out类型为PrintStream也是OutputStream类型的。那么如果能不能把InputStream转化为Reader,再把Writer输出的东西转为OutputStream呢?这个答案就是转换流了——InputStreamReader、OutStreamWriter。
- 转换流很简单,看一个例子:通过转换流,用BufferedReader的readLine()去读键盘输入。当然这也是以键盘录入的主要方法。
BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String line = in.readLine();
- 转换流还有一个特点就是可以指定字符集(FileReader、BufferedWriter这些是用系统默认字符集的,无法更改)。
BufferedWriter out =new BufferedWriter(new OutputStreamWriter(System.out),"UTF-8"); out.write("Hello");out.newLine();
- System对象方法全静态,封装了一些系统属性及方法。其中标准输入流(键盘)即System的in字段,为InputStream类型。标准输出流(屏幕)即System.out,为PrintStream打印流,这个之后会讲到,是OutputStream类型。可以通过System.setIn(),System.setOut()更改系统的标准输入输出设备。
- 我最早接触的键盘录入方法是Scanner,用法就像这样:Scannner in = new Scanner(System.in); int i = in.nextInt(); 可以看到Scanner可以方便地录入基本类型数据,也有nextLine()读一行的方法。还可以指定录入格式,算得上是功能最强的输入方法了。它在java.util包中。
第四讲.打印流
- 刚刚提到System.out为PrintStream打印流,顾名思义,就是拥有许多便捷打印方法的流。当然这种流只有输出流啦——PrintStream、PrintWriter
- println()方法是最常用到的,可以输出一行数据,带着换行。利用println("",true)可以autoflush。
- printf()方法可以按数据类型并按照制定的格式输出,这一点是很像C的。
- new对象时可以接受File对象(下面提到),流对象,String fileName,总之基本想到的都可以,体现其强泛用性。
- PrintWriter更常用(应该是最强功能的输出流了),在web开发是都是用PrintWriter一行一行地输出数据。
第五讲.File文件对象
- File——Java描述文件或文件夹的对象,是文件或目录的抽象表示形式。可以方便我们配合IO批量操作文件。还能够设置文件的属性(可读可写之类的)。
- File的一些方法简介如下:
//介绍File的一些方法 /*********创建***********/ File file = new File("D:\\ha\\abc\\newText.txt");//这只是抽象描述,并不会去真的创建 file.createNewFile();//如果没有就创建,返回true;有的话不创建返回false,注意跟输出流的对比。 new file("D:\\hahaha").mkdirs();//创建目录,包括不存在的父目录 //mkdir()如果父目录不存在是不会创建的,返回false /*********删除***********/ file.delete();//deleteOnExit()程序退出时删除,临时文件回去做这件事情 /*********判断***********/ file.isFile();//是否为文件 file.isDirectory();//是否为目录 file.isAbsolute();//是否为全路径 file.exists();//是否存在 file.canExcute();//是否可以执行 file.canRead();file.canWrite();file.isHidden(); /*********获取***********/ file.getPath();file.getAbsolutePath();//得到文件路径 file.getParent()//得到父目录 还有long length();long lastModified();等等
- 获取文件列表,并利用FilenameFilter筛选文件
//获取文件夹下所有的.java文件,不去递归文件夹 class FileDemo2 { public static void main(String[] args) throws IOException { File dir = new File("D:\\EditPlus\\MyWork\\Day12"); String[] names = dir.list(new FilenameFilter(){ public boolean accept(File dir,String name){ return name.endsWith(".java"); } }); for(String name:names) System.out.println(name); } }
- 利用递归,获取文件夹下所有.java文件
import java.io.*; //获取指定文件夹下的所有.java文件,递归 class FileDemo { private static StringBuilder sb; public static void main(String[] args) throws IOException { sb = new StringBuilder(); File dir = new File("D:\\EditPlus\\MyWork"); getJava(dir); System.out.println(sb); } private static void getJava(File dir){ if(!dir.isDirectory()) return; File[] files = dir.listFiles(); for(File item:files){ if(item.isDirectory()) getJava(item); if(item.getName().endsWith(".java")) sb.append(item.getAbsolutePath()+item.getName()+"\r\n"); } } }
- Properties——就是专门用来存取配置信息的Map对象,与IO联合使用,load(InputStream或Reader)还有list(PrintStream或PrintWriter)很实用。
第六讲. 合并流: SequenceInputStream
- 合并流可以进行文件合并的操作,它可以把多个流汇聚为一个大流。合并流只有输入流,因为分割操作不需要特定的流对象了。
- new一个SequenceInputStream时,只接受多个流封装而成的枚举对象Enumeration<InpupStream>。这个是较其他流不同之处。
- 可以利用Vector的elements()方法产生Enumeration<InputStream>,示例代码如下:
v = new Vector<FileInputStream>(); v.add(new FileInputStream("1.txt")); v.add(new FileInputStream("2.txt")); v.add(new FileInputStream("3.txt")); Enumeration<FileInputStream> en = v.elements(); SequenceInputStream sis = new SequenceInputStream (en); //只能传入枚举 ,接下来进行合并文件
附录:
Java的IO类型繁杂,怎样根据问题合理选择呢,这里毕老师视频里有详尽总结,即四个明确:
明确源和目的 | 源:输入流 目的:输出流 |
操作的数据是否为纯文本 | 纯文本用字符流 |
通过设备确定选用那个对象 | 源设备:内存,硬盘,键盘 目的设备:内存,硬盘,控制台 |
是否需要提高效率, | 提高就用Buffered流 |