Java 的 IO 四大基类详解(上)

1、概述
     Java 的IO通过java.io 包下的类和接口来支持,java.io包下主要包括输入、输出两种流。每种输入输出流又可分为字节流和字符流两大类。
     字节流以字节为单位处理输入、输出操作;
     字符流以字符来处理输入、输出操作。

2、File类
      File类代表与平台无关的文件和目录,他可以操作文件或目录,比如 File能新建、删除、重命名文件和目录,File类不能访问文件本身。如果要访问文件本身,则使用输入/输出流。
     下面测试一下File类的功能。

/**
 * Created by 杨Sir on 2017/10/30.
 * 该类测试了 File类的常用方法
 */
public class FileTest {
    public static void main(String[] args) throws IOException {

        //以当前路径来创建一个File对象
        File file2 = new File(".");
        //直接获取文件名
        System.out.println(file2.getName());
        //获取文件相对路径的父路径,可能出错,下面返回 null
        System.out.println(file2.getParent());
        //获取绝对路径
        System.out.println(file2.getAbsoluteFile());
        //获取上一级路径
        System.out.println(file2.getAbsoluteFile().getParent());
        System.out.println("-------------------------------------------");

        //以指定路径来创建一个File对象,例如项目中的 file/file2 文件夹为指定路径
        File file0 = new File("file/file2");
        /**
         * 在指定的路径下创建一个临时文件,,文件存放位置 file0对象对应的路径下
         * 使用前缀,系统随机生成的随机数和给定的后缀 作为文件名。  这是一个静态方法
         */
        File tmpFile = File.createTempFile("aaa",".txt",file0);
        //指定JVM退出时删除该文件,钩子方法
        tmpFile.deleteOnExit();
        System.out.println("tmpFile 被删除...");
        System.out.println("-------------------------------------------");

        //以系统当前时间为新文件名来创建新文件
        File newFile = new File(System.currentTimeMillis() + "");
        System.out.println("newFile对象是否存在: "+ newFile.exists());  //返回false
        //以指定的newFile对象来创建一个文件 ,文件位置在项目名下
        newFile.createNewFile();
        //以newFile对象创建一个目录,因为newFile已存在,因此下面方法返回false,无法创建
        System.out.println(newFile.mkdir());

        /**
         * 使用list方法列出当前路径下所有文件和路径
         * 项目名下的所有文件及路径(不包括路径的子路径)
         */
        String[] fileList = file2.list();
        for(String fileName : fileList){
            System.out.println(fileName);
        }
        System.out.println("-----------------------");
         //listRoots()静态方法列出所有的磁盘根路径:  C:\   D:\  E:\  F:\
        File[] roots = File.listRoots();
        for(File file : roots){
            System.out.println(file);
        }
    }
}

     需要注意的是:windows的路径分割符为反斜线(\), 而Java中反斜线是转义字符,因此windows下应该写两条反斜线(\),例如 F:\abc\test.txt。或者使用斜线(/),java支持将斜线当做平台无关的路径分割符。

     File类的list方法可以接收一个FilenameFilter参数,用于文件过滤。这里的FilenameFilter接口是一个函数式接口,里面包含一个accept(File dir, String name)方法。举例如下:

/**
 * Created by 杨Sir on 2017/10/30.
 * 文件过滤器的使用
 */
public class FilenameFilterTest {
    public static void main(String[] args){
        File file = new File(".");
        /**
         * 使用Lambda表达式实现文件过滤器
         * accept方法将对指定File的所有 子目录或文件进行迭代,如果方法返回true,则list()会列出该子目录或文件
         * 如果文件以 .java结尾或文件对应一个路径,则返回true
         */
        String[] nameList = file.list(((dir, name) -> name.endsWith(".java") || new File(name).isDirectory()));
        //当前路径(创建nameList对象的路径)下所有的.java文件和文件夹将被输出
        for (String name : nameList){
            System.out.println(name);
        }
    }
}

3、理解 Java 的 IO 流
3.1  Java的 io流是实现输入输出的基础,Java把不同的输入输出源抽象为流。正因为流的抽象,才可以通过一致的IO代码去读写不同的IO流节点。

流的分类:
1.从内存的角度来看
     – 输入流:只能从中读取数据,而不能向其写入数据
     – 输出流:只能向其写入数据,而不能从中读取数据
  java的输入流主要由 InputStream 和 Reader作为基类,而输出流主要以 OutputStream 和 Writer作为基类,这些基类无法创建实例。
2. 字节流和字符流
     字节流和字符流的区别在于所操作的数据单元不同,字节流操作的数据单元是8位的字节,字符流操作的数据单元是16位的字符。
     字节流主要由 InputStream 和 OutputStream作为基类,字符流主要有 Reader 和 Writer作为基类。如下图:
这里写图片描述
3. 节点流和处理流(按流的角色分):
     从/向 一个特定的IO设备(磁盘、网络)读/写数据的流,称为节点流,当使用节点流时,程序直接连到实际的数据源,和实际的输入/输出节点连接。
     处理流用于对一个已存在的流进行连接或封装,通过封装后的流实现读/写功能。
     使用处理流的好处是:只要使用相同的处理流,程序就可以采用完全相同的输入/输出代码访问不同的数据源,随着处理流包装节点流的变化,程序实际访问的数据源也在变化。Java用处理流包装节点流是一个典型的装饰器模式。

3.2 字节流和字符流
我们先来看看InputStream 和 Reader
     InputStream 和 Reader是所有输入流的抽象基类,他们是所有输入流的模板,提供通用方法。
     ①. InputStream包含三个方法:
       int read(): 从输入流读取单个字节,返回读取的字节数据,字节数据可直接转换为 int类型。
       int read(byte[] b): 从输入流中最多读取 b.length 个字节的数据,并将其存入数组b中,返回实际读取的字节数。
       int read(byte[] b, int off, int len): 从输入流中最多读取 len 个字节的数据,并将其存入数组b中,从off位置开始读,返回实际读取的字节数。
     ②. Reader包含的三个方法:
       int read(): 从输入流读取单个字符,返回读取的字符数据,字符数据可直接转换为 int类型。
       int read(char[] b): 从输入流中最多读取 b.length 个字符的数据,并将其存入数组b中,返回实际读取的字符数。
       int read(char[] b, int off, int len): 从输入流中最多读取 len 个字符的数据,并将其存入数组b中,从off位置开始读,返回实际读取的字符数。

    要注意:当read(char[] c) 或 read(byte[] b)方法返回 -1,表明到了输入流的结束点。
     前面说过,InputStream 和 Reader都不能创建实例,那怎么办呢?没关系,他们各自提供了一个用于读取文件的输入流:FileInputStream 和 FileReader,他们都是节点流,会直接和指定文件关联。 例如使用FileInputStream 来读取文件本身数据。

/**
 * Created by 杨Sir on 2017/10/30.
 * 通过FileInputStream 来读取文件数据
 * 采用字节读取,注释可能会乱码,因为文本保存时采用GBK编码,一个中文字符占两个字节,如果read只读到半个字节,就会乱码。
 */
public class FileInputStreamTest {

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

        //创建字节输入流,相当于从管道中取数据,,绝对路径
        FileInputStream fis = new FileInputStream("D:\\绝对路径\\FileInputStreamTest.java");
        //相对路径
//      FileInputStream fis = new FileInputStream(".\\src\\相对路径\\FileInputStreamTest.java");

        //创建一个长为1024个字节的管道
        byte[] buf = new byte[1024];
        //用于保存实际读取的字节数
        int hasRead = 0;
        //读取1024字节之后会进行输出,然后再次读取时,读取剩余的字节,在输出,直到没有一个能被读取的字节,会返回-1。
        while ((hasRead = fis.read(buf)) > 0){
            //取出字节,将字节数组转换成字符串数输入
            System.out.println(new String(buf,0,hasRead));
        }
        //关闭文件输入流,放在 finally中更安全
        fis.close();
    }
}

     使用FileReader 来读取文件本身数据:

/**
 * Created by 杨Sir on 2017/10/30.
 */
public class FileReaderTest {
    public static void main(String[] args){

        try(
            //创建字符输入流, JDK 1.7以后,这样写,系统自动关闭流
            FileReader fr = new FileReader("D:\\绝对路径\\FileReaderTest.java");
        ){
            char[] cbuf = new char[32];  //这时需要多次调用read()读取
            //保存实际读取的字符数
            int hasRead = 0;
            while((hasRead = fr.read(cbuf)) > 0){
                System.out.println(new String(cbuf, 0, hasRead));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

     除此之外,InputStream 和 Reader还支持如下几个方法移动指针:
       void mark(int readAheadLimit): 在记录指针当前位置记录一个标记。
       boolean markSupported() : 判断此流是否支持 mark()操作,即是否支持记录标记。
       void reset(): 将此流的记录指针重新定位到上一次记录标记(mark)的位置。
       long skip(long n): 记录指针向前移动 n个字符/字节。

我们在来看 OutputStream 和 Writer
     他们各自提供了一个用于写入文件的输入流:FileOutputStream 和 FileWriter,他们都是节点流,会直接和指定文件关联。
      其实OutputStream 和 Writer 也非常相似, 他们共同提供了如下三个方法:
        void write(int c): 将指定的字节或字符输出到输出流中。
       void write(byte[]/char[] buf): 将字节数组或字符数组中的数据输出到指定输出流中。
       void write(byte[]/char[] buf, int off, int len):将字节数组/字符数组中从off位置开始,长度为len的字节或字符输出到输出流中。
   因为字符流可以直接额以字符做操作单位,所以Writer可以用字符串代替字符数组。因此Writer 里还有两个方法:
       void write(String str): 将字符串中的字符输出到输出流中。
       void wirte(String str , int off ,int len): 将字符串中off开始,长度为len的字符输出到输出流中。
    下面举例 利用FileInputStream输入,并使用 FileOutputStream执行输出,实现复制FileOutputStreamTest的功能。

/**
 * Created by 杨Sir on 2017/10/30.
 * 利用FileInputStream输入,并使用 FileOutputStream执行输出,实现复制FileOutputStreamTest的功能
 */
public class FileOutputStreamTest {
    public static void main(String[] args){
        try(
            //创建字节输入流
            FileInputStream fis =
                    new FileInputStream("D:\\绝对路径\\FileOutputStreamTest.java");
            //创建字节输出流
            FileOutputStream fos = new FileOutputStream("FileOutputStreamTest.txt")
        ) {
            byte[] buf = new byte[1024];
            int hashead = 0;
            //循环取数据
            while ((hashead = fis.read(buf)) > 0) {
                //每读取一次,写入一次,读多少,写多少,,可以看到当前路径下多了一个 FileOutputStreamTest.txt文件
                fos.write(buf, 0, hashead);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

   如果希望直接输出字符串内容,使用Writer会更好:

/**
 * Created by 杨Sir on 2017/10/30.
 * 当前路径下会输出一个 FileWriterTest.txt 文件。
 */
public class FileWriterTest {
    public static void main(String[] args){
        try (
                //创建字符输出流
                FileWriter fw = new FileWriter("FileWriterTest.txt"))
        {
            // "\r\n"是windows的换行符,UNIX/Linux/BSD等平台,使用"\n"换行
            fw.write("锦瑟 - 李商隐\r\n");
            fw.write("锦瑟无端五十弦,一悬疑蛛丝年华。\r\n");
            fw.write("庄生晓梦迷蝴蝶,望帝春心托杜鹃。\r\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

以上介绍了4个访问文件的节点流的用法,4个基类使用优点繁琐。如果希望简化编程,就需要借助处理流了。处理流后面在更新。。。

猜你喜欢

转载自blog.csdn.net/annotation_yang/article/details/78397239