黑马程序员——JAVA基础——IO(一)---流概述,节点流、处理流、转换流与标准输入输出流、打印流、File文件对象、合并流

时间:2021-04-17 10:56:31

-----------android培训java培训、java学习型技术博客、期待与您交流!------------

第一讲.IO流概述

  1. Java中是通过IO流的方式处理设备间的数据传输,存放在java.io包中。
  2. 按照操作的数据单元分为字节流和字符流,按照流向分为输入流与输出流。
  3. 字节流的抽象基类:InputStream,OutputStream ,操作字节,其实字符流的底层也是字节流。
  4. 字符流的抽象基类:Reader,Writer ,操作字符,专门用来操作文本,可以处理各种文本编码(GBK,UTF-8)。
  5. IO流中相应的继承类后面都跟着它抽象基类的名字。像FileReader继承Reader,属于字节读取流等等。规律性强,还是很好记的。。。
  6. IO流的体系很庞杂,但只要学会举一反三,理解透了还是很容易的。。

第二讲.节点流——直接从磁盘文件或内存读写,在流体系中比较基础的流

  1. 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前就因为异常退出了。
    }
    }
  2. 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)

  1. 顾名思义,通过包装(装饰)其他流以增强其功能。典型的就是BufferedXXX这一系列:BufferedReader, BufferedWriter ,BufferedInputStream ,BufferedOutputStream。它们加入了缓冲功能,优化了读写性能,实例化只能传入流,表明它是专门为流进行优化的存在。
  2. 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();}
    }
  3. 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();}}
  4. 通过去实现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();}}
  5. 这里涉及到了装饰设计模式,BufferedReader这些处理流是很典型的装饰设计模式,它们的作用就是:为所有其他的流对象提供缓冲这种功能增强。装饰与继承是完全不同的两个概念。继承通过继承父类完成功能拓展,设想现在如果用继承来完成缓冲功能,只能通过为每一个具体子类新建一个Buffered子类来实现。而装饰是通过装饰父类去增强父类功能,只需有一个BufferReader,就可以为所有Reader添加缓冲功能了,简单而且还有拓展性。

第三讲.转换流与标准输入输出流

  1. 字节流处理文本有些时候不好用。但是有些场合不得不接触,像System.in定义的标准输入设备,这个in就是InputStream类型的;而System.out中的标准输出流out类型为PrintStream也是OutputStream类型的。那么如果能不能把InputStream转化为Reader,再把Writer输出的东西转为OutputStream呢?这个答案就是转换流了——InputStreamReader、OutStreamWriter。
  2. 转换流很简单,看一个例子:通过转换流,用BufferedReader的readLine()去读键盘输入。当然这也是以键盘录入的主要方法。
    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    String line = in.readLine();
  3. 转换流还有一个特点就是可以指定字符集(FileReader、BufferedWriter这些是用系统默认字符集的,无法更改)。
    BufferedWriter out =new BufferedWriter(new OutputStreamWriter(System.out),"UTF-8");out.write("Hello");out.newLine();
  4. System对象方法全静态,封装了一些系统属性及方法。其中标准输入流(键盘)即System的in字段,为InputStream类型。标准输出流(屏幕)即System.out,为PrintStream打印流,这个之后会讲到,是OutputStream类型。可以通过System.setIn(),System.setOut()更改系统的标准输入输出设备。
  5. 我最早接触的键盘录入方法是Scanner,用法就像这样:Scannner in = new Scanner(System.in); int i = in.nextInt(); 可以看到Scanner可以方便地录入基本类型数据,也有nextLine()读一行的方法。还可以指定录入格式,算得上是功能最强的输入方法了。它在java.util包中。

第四讲.打印流

  1. 刚刚提到System.out为PrintStream打印流,顾名思义,就是拥有许多便捷打印方法的流。当然这种流只有输出流啦——PrintStream、PrintWriter
  2. println()方法是最常用到的,可以输出一行数据,带着换行。利用println("",true)可以autoflush。
  3. printf()方法可以按数据类型并按照制定的格式输出,这一点是很像C的。
  4. new对象时可以接受File对象(下面提到),流对象,String fileName,总之基本想到的都可以,体现其强泛用性。
  5. PrintWriter更常用(应该是最强功能的输出流了),在web开发是都是用PrintWriter一行一行地输出数据。

第五讲.File文件对象

  1. File——Java描述文件或文件夹的对象,是文件或目录的抽象表示形式。可以方便我们配合IO批量操作文件。还能够设置文件的属性(可读可写之类的)。
  2. 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();等等
  3. 获取文件列表,并利用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);}}
  4. 利用递归,获取文件夹下所有.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");}}}
  5. Properties——就是专门用来存取配置信息的Map对象,与IO联合使用,load(InputStream或Reader)还有list(PrintStream或PrintWriter)很实用。

第六讲. 合并流: SequenceInputStream 

  1. 合并流可以进行文件合并的操作,它可以把多个流汇聚为一个大流。合并流只有输入流,因为分割操作不需要特定的流对象了。
  2. new一个SequenceInputStream时,只接受多个流封装而成的枚举对象Enumeration<InpupStream>。这个是较其他流不同之处。
  3. 可以利用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流