Thinking in Java---Java IO 总结之基本IO

初次接触Java IO的时候,我有一种措手不及的感觉,因为这个类库中的类实在是太多了!并且如果我们想要使用一个类对象进行输入输出,可能还需要创建其它几个类的对象,实在是很麻烦。在这篇文章中我试图对Java IO类库进行一个梳理,总结一下几种常用的IO类组合。文章分为三个部分:1.Java操作目录的File类;2.Java经典的输入输出流;3.java的标准输入输出;4.”特立独行”的RandomAccessFile类。

一.Java操作文件/目录的File类
File(文件)这个类名有一定的误导性,我们有可能会觉得它代表的是文件,但实际上File类代表的是与平台无关的目录和文件。也就是说,如果希望在程序中操作文件和目录都可以通过File类来完成。值注意的是:File类能够新建,删除,重命名文件和目录,也可以判断所给的路径是文件还是目录,但是,File类是不能访问文件内容本身的;也就是说如果我们要向文件中写入内容或则读取文件中的内容,都必须使用输入/输出流。
我们可以使用文件路径字符串来创建File类对象,这个路径可以是绝对路径,也可以是相对路径;所谓的相对路径,指的是相对于当前java虚拟机运行时所在的路径。File类对应的操作不是很多,下面这份演示代码展示了大多数的用法(我是在Ubuntu下进行的测试,所以使用的路径与windows是不一样的):

package lkl;
import java.io.*;

public class FileTest {

    public static void main(String[] args) throws IOException{

        File path = new File("/Test");

        ///取得文件名
        System.out.println(path.getName());

        ///返回该File对象所对应的目录的父目录
        System.out.println(path.getParent());

        //获取绝对路径
        System.out.println(path.getAbsolutePath());

        //在当前路径下创建一个临时文件
        File tmpFile = File.createTempFile("aaa", ".txt",path);
        ///指定退出JVM时,删除该文件
        tmpFile.deleteOnExit();

        ///以当前系统时间作为新文件名来创建新文件
        File newFile = new File(System.currentTimeMillis()+"");
        System.out.println("newFile对象是否存在:"+newFile.exists());

        //以指定的newFile对象创建一个文件
        newFile.createNewFile();

        //以newFile对象来创建一个目录,因为newFile已经存在
        //所以下面的方法返回false
        newFile.mkdir();

        //使用list()列出当前路径下的所有文件和路径
        System.out.println("====当前路径下的所有文件和路径如下====");
        String[] list=path.list();
        for(String fileName : list){
            System.out.println(fileName);
        }

        //listRoots()静态方法可以列出所有的磁盘根路径
        ///返回的是File对象而不是String对象
        System.out.println("===系统的所有根路径如下===");
        File[] roots = File.listRoots();
        for(File fi : roots){
            System.out.println(fi);
        }
    }
}/*
Test
/
/Test
newFile对象是否存在:false
====当前路径下的所有文件和路径如下====
test.txt~
test.txt
a.java
ss.zip.info
a.class
aaa9141834113211816485.txt
lkl
ss.zip
java
obj.txt
===系统的所有根路径如下===
/
*/

我们注意到上面的代码中存在一个list()方法,这个方法用来列出当前路径下的所有文件和目录,但是有时候我可能需要只列出满足某些条件的文件和目录而不是所有,这时候我们就需要用到Java的”文件过滤器”的功能了。具体的来讲就是可以向list()中传入一个FilenameFilter的参数,通过该参数就可以只列出符合要求的文件。FilenameFilter是一个接口,我们要实现它的话,就要实现它的accept(File dir,String name)方法;list()会为此目录对象下的每个文件名调用accept(),来判断该文件是否包含在内,判断结果由accept()返回的布尔值表示。另外,accept()中使用的正则表达式就是我们自己传进去的规则。如下面这份代码:

package lkl;
import java.io.*;
import java.util.*;
import java.util.regex.Pattern;

//这次用内部类的形式实现FilenameFilter接口
public class DirList1 {
     

    ///传给匿名内部类的参数应该是final类型的
    private static FilenameFilter filter(final String regex){
        return new FilenameFilter(){
            private Pattern pattern=Pattern.compile(regex);

            //实现FilenameFilter接口必须实现accept方法
            public boolean accept(File dir,String name){
                return pattern.matcher(name).matches();
            }
        };
    }

    public static void main(String[] args){
        String regex=".*.java$";
        File file = new File("/Test");
        String[] list =file.list(filter(regex));
        System.out.println(Arrays.toString(list));
    }/*Output
       [a.java]
    */
}

二.Java经典的输入输出流
首先要讲一下”流”这个概念;编程语言的I/O库中常使用流这个抽象概念,它代表任何有能力产出数据的数据源对象或有能力接收数据的接收端对象。”流”屏蔽了实际的I/O设备处理数据的细节。然后之所以下面这些IO类叫做经典的输入输出流,是因为后面还会接触到Java的NIO。
首先来讲我们需要从几个方面来对Java的IO进行一个分类,以便有一个直观的认识:
(1).输入流和输出流
输入流是指只能从中读数据,而不能向里面写数据:包括InputStream和Reader两个基本接口
输出流是指只能向里面写数据而不能向其中读数据:包括OutputStream和Writer两个基本接口

(2).字节流和字符流
字节流和字符流的唯一不同就在于他们的操作的数据单元不同而已,字节流操作的数据单元是字节,而字符流操作的数据单元是字符。所有从Writer,Reader派生来的类都是字符流的类。而所有从InputStream和OutputStream派生出来的类都是字节流的类。

(3).节点流和处理流
所谓的节点流指的是:可以向/从一个特定的IO设备(如磁盘,网络,数组,字符串)读/写的流;节点流也被成为低级流。
所谓的处理流指的是:对一个已存在的流进行连接或封装,然后通过封装以后的流来实现数据的读写功能。处理流也叫做高级流。
Java中的IO使用了装饰器的设计模式,就是体现在节点流和处理流的包装关系上。使用处理流的好处在于我们可以忽略掉底层数据源的不同,而使用统一的方式进行读写。
下面给一张体现上面三个分类的图:
Thinking in Java---Java IO 总结之基本IO_第1张图片

因为节点流是所有输入输出的基础,所以有必要拿出来单独细看一下。
(1).InputStream 对应的节点流(按对应的数据源分)
1).字节数组:ByteArrayInputStream。以字节数组作为读取对象。
2).String对象:StringBufferInputSteam。将String转换成InputStream。
3).文件:FileInputStream。用于从文件中读取信息。
4).”管道”:PipedInputStream。
5).其它的数据源:如Internet连接等

(2).OutputSteamd对应的结点流
1).ByteArrayOutputStream。在内存中创建缓冲区,所有送往”流”的数据都要放置在此缓冲区。
2).FileOutputStream 用于将信息写入到文件
3).PipedOutputStream 将信息写入到管道

(3).Reader对应的节点流
1).FileReader:以文件作为读取对象
2).StringReader:以字符串作为读取对象
3).CharArrayReader:以字符数组作为读取对象
4).PipeReader:以管道作为读取对象

(4).Writer对应的节点流
1).FileReader:以文件作为输出对象
2).StringWriter:以字符串作为输出对象
3).CharArrayReader:以字符数组作为输出对象
4).PipedWriter:以管道作为输出对象

另外还要注意的是,InputStream可以通过InputStreamReader转换成Reader的,而OutPutstream可以通过OutputStreamWriter转换成Writer。下面通过几个实例了解一下IO类具体的用法。
(1)

package lkl;
import java.io.*;

//向文件中写入内容
///可以直接使用FileWriter进行写入,但是为了效率我们先用FileWrite包装的
//BufferedWriter,然后为了格式化输出,我们使用BufferedWriter包装的PrintWriter
public class BasicFileOutPut {
     static final String file ="/Test/obj.txt"; ///将要被写入的文件
     public static void main(String[] args) throws IOException{
         int LineNum=1;
         BufferedReader in= new BufferedReader(new FileReader("/Test/test.txt"));
        // PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));

         ///PrintWriter中提供了一个快捷的构造器方式,可以使得我们不用进行上面
         //那样复杂的包装,其形式如下:
         PrintWriter out = new PrintWriter(file);
         String s;
         while((s=in.readLine())!=null){
             out.println(LineNum++ +": "+s);
         }
         in.close();
         out.close();
     }
}

(2)

package lkl;
import java.io.*;
import java.util.LinkedList;

///I/O流的典型使用方式1
///打开一个文件进行操作时,为了提高速度,对文件进行缓冲
//使用BufferedReader,这个I/O流包括一个可以整行读取的ReadLine()方法
//BufferdReader 属于包装流,需要在构造器中传入一个FileInputReader节点流
public class BufferedInputFileTest {

    public static void main(String[] args) throws IOException{
                BufferedReader br = new BufferedReader(new FileReader("/Test/test.txt"));
                LinkedList read = new LinkedList<>();
                String s;
                StringBuilder sb = new StringBuilder();
                while((s=br.readLine())!=null){ ///按行读取
                    s=s.toUpperCase();
                    read.add(s);
                    //sb.append(s+"\n"); ///采用append()方法比“+”要快
                }
                br.close();
                for(int i=read.size()-1;i>=0;i--){
                    System.out.println((String)read.get(i));
                }
                //System.out.println(sb);
    }
}

(3)

package lkl;
import java.io.*;

//如果我们使用DataInputStream写入数据
//那么java就可以保证我们使用DataInputStream准确的读取数据
///无论读和写的平台多么不同
//注意这两个I/O操作都是面向字节的的包装流
public class Data {

      public static void main(String[] args) throws IOException{ 
          //使用DataOutputStream向文件写入数据
         DataOutputStream out =new DataOutputStream(new BufferedOutputStream(new FileOutputStream("/Test/obj.txt")));

       ///写入不同类型数据,有不同的调用格式
          out.writeDouble(3.151592654); 

          ///当我们使用DataOutputStream写字符串并让DataInputStream能够恢复
          //它的唯一可靠做法就是使用UTF-8编码,这里是使用writeUTF()和readUTF()实现的
          out.writeUTF("That was pi");
          out.close();
       ///使用DataInputStream读取文件里的数据
         DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("/Test/obj.txt")));

          System.out.println(in.readDouble());
          System.out.println(in.readUTF());
          in.close(); 
     }
}

(4)

package lkl;
import java.util.*;
import java.io.*;

//IO2
//StringReader 接受一个字符串源,然后我们就可以从中读取数据
public class MemoryInput {

    public static void main(String[] args) throws IOException{
        StringReader in =new StringReader("jlfsg");
        int c;
        while((c=in.read())!=-1){
            System.out.print((char)c); ///read()是以int形式返回下一个字节,一次必须要转型
        }
    }
}

(5)

package lkl;
import java.io.*;

//IO3
///使用DataInputStream逐个读取字节
///其为字符型包装流,所以必须使用InputStream进行包装
public class FormattedMemoryInput {

      public static void main(String[] args) throws IOException{
              DataInputStream din = new DataInputStream(new ByteArrayInputStream("jfsfsflsf".getBytes()));
            ///因为是逐个读取,所以不能通过返回值判断结束
              //但是可以通过available()判断还有多少个字符可以读取
              while(din.available()!=0){ 
                  System.out.print((char)din.readByte());
              }
      }
}

三.Java的标准输入输出
Java的标准输入输出流指的是System.in,System.out
System.err分别是标准输入流,标准输出流,标准错误输出流。标准IO的意义在于:我们可以很容易的将程序串联起来,一个程序的标准输出都可以成为另一个程序的标准输入。System.out,System.err都是已经经过包装的PrintStream对象,而System.in是未经包装过的InputStream;所以在使用System.in之前我们需要进行包装。我们所关注的点在于我们可以对这些标准流进行包装,然后也可以对他们进行重定向。如下面的代码所示:

package lkl;
import java.io.*;
///标准IO
///所谓的标准IO保存System.in System.out System.err 三个
//其中System.out System.err 属于OutputStream 并且已经被包装成了printStream
//而System.in属于InputStream但是没有被包装,所以我们使用的时候需要自己进行包装
public class SystemIO {

    public static void main(String[] args) throws IOException{

        ///将System.in包装成BufferedReader
      //  BufferedInputStream in = new BufferedInputStream(System.in);
        ///要先转成Reader
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String content="";
        String str;
        while((str=br.readLine())!=null&&str.length()!=0){
            content+=str;
        }
        br.close();
        ///将System.out包装成PrintWriter
        ///System.out是一个PrintStream但是PrintWriter有一个构造器可以接受OutputSteam
        ///作为参数,所以可以直接使用那个构造器将System.out转成PrintWriter
        PrintWriter out =new PrintWriter(System.out,true); ///注意设置第二个参数
        out.println(content);
    }
}
package lkl;
import java.io.*;

///标准IO的重定向
//默认情况下,这些IO都是进行屏幕的读写的
///但是有时候我们可以对他们进行重定向,以输入/输出不同的对象
///重定向操作的都是字节流,必须使用OutputSteam和InputStream而不是Reader和Writer

///System类提供的三个重定向函数如下:
///setIn(InputStream)
///setOut(PrintStream)
///setErr(PrintStream)
public class SystemIO2 {

    public static void main(String[] args) throws IOException{
        PrintStream console = System.out;
        BufferedInputStream in = new BufferedInputStream(new FileInputStream("/Test/obj.txt"));
        PrintStream out = new PrintStream(new FileOutputStream("/Test/test.txt"));

        ///重定向输入流,从文件输入
        System.setIn(in);
        ///重定向输出流,输出到文件
        System.setOut(out);

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String s;
        while((s=br.readLine())!=null){
            System.out.println(s);
        }
        out.close();
        //重新定向到控制台
        System.setOut(console);
    }
}

四.”特立独行”的RandomAccessFile类
RandomAccessFile适用于有大小已知的记录组成的文件。之所以将RandomAccessFile称为”特立独行”的类是基于两个方面的原因。一是这个类不是InputStream和OutputStream继承层次中的类,除了实现了DataInput和DataOutput接口(DataInputStream和DataOutPutStream也实现了这两个接口)之外,它和这两个继承体系没有任何关系。二是这个类的功能是很特别的,他允许我们在一个文件里面任意的移动,然后可以同时通过这个类对文件进行读和写操作。
RandomAccessFile相当于将DataInputStream和DataOutputStream组合起来使用,其中有几个比较特别一点的方法,seek()允许我们将记录从一处转移到另一处,而getFilePointer()用于查找当前所处文件的位置,length()用于判断文件的最大尺寸;另外其构造器还需要第二个参数,用来指示我们只是”随机读”(r)还是”即读又写”(rw)。另外要注意RandomAccessFile只适用于文件。下面给出了一份演示代码:

package lkl;

import java.io.IOException;
import java.io.RandomAccessFile;

///RandomAccessFile类的使用
///RandomAccessFile是独立与OutputStream InputStream 外的一个独立的
//IO类,其可以对文件同时进行读写操作。并且可以在文件中进行随机访问。
//但是使用RandomAccessFile时必须知道文件的排版,这样才可以正确的操作它。
///RandomAccessFile拥有读取基本类型和UTF-8字符串的各种具体方法
public class RandomAccessFileTest {

    static String file = "obj.txt";
    static void display() throws IOException{
        //使用RandomAccessFile必须指定文件的访问模式:“r”或“rw”;
        RandomAccessFile rf = new RandomAccessFile(file,"r");
        for(int i=0;i<7;i++){
            System.out.println("value "+i+ ": "+rf.readDouble());
        }
        System.out.println(rf.readUTF());
        rf.close();
    }

    public static void main(String[] args) throws IOException{
        RandomAccessFile rf = new  RandomAccessFile(file,"rw");
        for(int i=0;i<7;i++)
           rf.writeDouble(i*1.414);
        rf.writeUTF("the end of file");
        rf.close();
        display();

        rf = new RandomAccessFile(file,"rw");
        rf.seek(5*8);   ///RandomAccessFile可以通过移动seek()指针来执行随机读写
        rf.writeDouble(47.000001);
        display();
        rf.close();
    }
}

你可能感兴趣的:(Thinking,in,Java,java编程思想,JavaIO,文件操作)