Java基础系列:IO流

小伙伴们,咱们认识一下。html

俗世游子:专一技术研究的程序猿java

在前面的几篇文章中,咱们分别介绍了api

LinkedHashMap继承自HashMap,咱们在这里不作过多介绍数组

根据以前的结构图,咱们还缺乏Set没有介绍。缓存

说实在的,这里我不打算和详细的来介绍Set集合,介绍Set也只是调用其api方法。咱们先快速过一下。网络

Set

Set是不包含重复元素的集合,底层基于Map实现oracle

这也是为何不介绍Set的缘由app

基础

Set集合中,其源码实现直接依赖Map来实现,Set添加的元素,经过MapKey来存储,Value部分是经过Object对象来填充,实现方式以下:ide

HashSet为例post

private static final Object PRESENT = new Object();

public HashSet() {
    map = new HashMap<>();
}

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

能够看到上面代码的方式

其分类关系以下

Set子集 实现
HashSet HashMap
TreeSet TreeMap
LinkedHashSet LinkedHashMap

根据上面简单的分析,咱们也能够总结出Set的一些特色:

  • Set是基于Map来实现的,将元素存储在Map的Key中,Value部分采用Object对象进行填充
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
  • Set元素不可重复,惟一,且无序

  • TreeSet中能够经过设置排序器来对容器数据进行排序
public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}
  • HashSet在设置初始大小的时候其实也就是在对HashMap设置,因此这里推荐设置2的N次幂的数
public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}

这里咱们引出一个方式叫作:组合方式

组合方式

在java中,基于基础类而后再其类中进行创新操做的方式,有两种方式:

  • 继承基础类(父类):在子类中重写其父类的方法。
  • 组合基础类:也就是说,在子类中,经过调用基础类的方法,来达到复用基础类的方式。在Set中,就是使用的组合基础类的方式

对比以上两种方式,组合基础类的优势:

  • 继承表示父子类是同一个类型,而 Set 和 Map 原本就是想表达两种类型,因此采用继承不合适,并且 Java 语法限制,子类只能继承一个父类,后续难以扩展。
  • 组合更加灵活,能够任意的组合现有的基础类,而且能够在基础类方法的基础上进行扩展、编排等,并且方法命名能够任意命名,无需和基础类的方法名称保持一致。

IO

本文章主要仍是要介绍一下Java中的IO。IO也就是输入/输出;Java中的IO操做本质上是对磁盘文件的操做

这种方式属于磁盘IO,固然,还存在一种叫作:网络IO

能够说咱们开发的程序,系统瓶颈主要就出如今这两块IO上,咱们在开发中也是针对这两块在代码层面来进行优化的

从总体划分上,能够分为三大类

  • BIO
  • NIO
  • AIO

BIO也就是咱们接下来要了解的IO类型,称为阻塞式IO

NIO能够叫作New IO,是一种非阻塞式的IO

NIO的执行效率比BIO要高一点

咱们先聊基础的,在Netty系列章节中,咱们再介绍NIO的相关使用

AIO暂时不作介绍,后面介绍到网络再说

那么咱们就要先来了解下什么是文件

文件

文件能够认为是相关记录或放在一块儿的数据的集合。像咱们电脑中的图片,视频等都是属于文件,须要采用相对应的软件才能打开,若是咱们都是使用纯文本打开的话那么就是一堆乱码,其实咱们也知道这是属于二进制数据

也就是说二进制数据经过必定的编码规则进行排序,而后经过相对应的软件打开,咱们就能够很明白的查看当前文件

那么这些数据是存储存储在哪里呢?对应到硬件上能够存储在U盘,硬盘等硬件系统中。

你们确定都见过的

那么再聊的深刻一点,如何从存储盘中读取文件内容

咱们能够这么来理解:

在电脑硬盘中存在一圈圈的磁道,这些磁道中存储的就是文件对应的数据,磁盘经过磁头来读写磁道,而磁头链接到一个传动臂,而传动臂将磁头定位到磁道的过程称之为寻址

盘片视图

该图来源:《深刻理解计算机系统》第6章存储技术

可是咱们要注意一点:硬件在读取数据的时候是有读取单位的

如今打个比方:

好比如今咱们已经有了1G的数据,咱们要寻找其中1个字节的数据,这个磁壁是怎么读取的?

咱们应该明白一点,确定不是1个字节1个字节的找,操做系统在读取的时候是已为读取单位(4K大小)

因此哪怕咱们要读取1个字节的数据,可是磁盘读取的时候也会读取一页大小(4K),而后会将从磁盘读取到的数据读到内存,可是读到内存的时候又会有一个问题:

从磁盘读取的时候是4K,可是读到内存的时候不必定是4K,这和硬件和操做系统是相关的,有多是4K或者是4K的整数倍,

可是能肯定的是:读数据的时候必定是按照 为单位来读取

咱们经过具体文件来看上述的说明:

文件大小

所以在硬件设备中存在一个概念:磁盘预读

就是说:每次无论须要多少字节的数据,都会将页的整数倍的数据读取进来

在这里同时又隐含一个著名的原理:局部性原理

程序数据的访问都有汇集成群的,也就是说全部数据都是汇集放在一块儿的。所以磁盘预读能够将整块的数据读取进来,下次再要找相似数据的时候,就不须要去磁盘查找(类比缓存)

局部性原理分为两种不一样形式:

  • 空间局部性

在一个具备良好空间局部性的程序中, 若是一个内存位置被引用了一次,那么程序极可能在不远的未来引用附近的一个内存位置

  • 时间局部性

在一个具备良好时间局部性的程序中,被引用过一次的内存位置极可能在不远的未来再被屡次引用

在《深刻理解计算机系统》中第6章6.2

File

那么接下来咱们来看看应该如何操做文件,在Java中为咱们提供了一个专门用来处理文件的类:File

下面咱们来看构造方法:

File构造方法

相对比而言,使用第二种和第三种方式的构造方法的更多,咱们已第二个构造方法为例:

File file = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\FileDemo.java");
// 判断当前文件是否存在:true
System.out.println(file.exists());

一样,该类还有不少其余的api方法,这里给你们演示几个经常使用的,其他的方法你们能够去查看File的api文档:

File file = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\FileDemo.java");
System.out.printf("当前是不是文件:%s \n", file.isFile());
System.out.printf("当前是不是文件夹:%s \n", file.isDirectory());
System.out.printf("当前文件名称:%s \n", file.getName());
System.out.printf("当前文件绝对路径:%s \n", file.getAbsolutePath());
System.out.printf("文件长度:%s \n", file.length());

File newFile = new File("newCreateFile.txt");
newFile.createNewFile();
System.out.printf("newFile是否存在:%s \n", newFile.exists());

newFile.delete();
System.out.printf("newFile是否存在:%s \n", newFile.exists());

你们最好亲自尝试一下

还要一个比较经常使用的方法,咱们作个小例子:

列出指定文件夹下的全部文件,若是是文件夹的话,那么继续向下遍历

这里能够用到一个方法:listFiles(),该方法会获得指定文件夹下全部的文件且对象类型为File[]

与其相对应的还有一个方法:list(),该方法和listFiles()相似,不过获得的对象类型为字符串数组

private static void _demo2() {
    File file = new File("D:\\Working Directory\\project");
    printFile(file, "|-");
}

private static void printFile(File file, String level) {
    File[] files = file.listFiles();
    for (File f : files) {
        if (f.isDirectory()) {
            System.out.println(level + f.getName());
            printFile(f, level + "-");
        } else {
            System.out.println(level + f.getName());
        }
    }
}
// 输出不少,我就不给效果图了,你们本身尝试下

关于方法中的参数:FileFilter,主要是对文件进行过滤操做

这里还须要说明一点:若是你们在测试的时候,出现异常可是程序自己无错,那么咱们须要想想是否是指定的目录是受系统保护的目录

文档

关于File就介绍到这里,更多其api方法查看官方文档:

File API方法

这是关于文件方面的相关内容,下面咱们来聊一聊另一个内容

IO流

上面咱们能获得当前文件了,可是咱们若是想要经过代码来读取文件中的内容,那么咱们应该怎么作呢?

这就是咱们下面要聊的内容:

能够这么理解:

从一个文件将数据返送到另外一个文件。这里包含一个流向问题,这里的流向咱们须要指定一个参照物。

参照物确定不用过多解释了:就是咱们写的程序

  • 咱们是经过程序从源文件读取到数据,这里是一个输入流
  • 而后经过程序将数据写入到目标文件中,这里是输出流

查看一下图,咱们来清晰认识下流向

流

分类

如下类都是基类

按照流向分类

  • 输入流
    • InputStream
    • Reader
  • 输出流
    • OutputStream
    • Writer

按照内容处理方式

  • 字节流:8位通用字节流
    • InputStream
    • OutputStream
  • 字符流:16位unicode字符流
    • Reader
    • Writer

使用明白了其分类,那么咱们来看看流究竟是如何来操做文件的

字节流

FileInputStream

正如咱们所说,InputStream是一个接口类,天然咱们须要了解其子类

FileInputStream使咱们最经常使用的一个实现类,读取文件原始字节流,全部图像,文件等读取方式官方都推荐咱们采用该方法,可是并不仅是限定于字节文件,若是咱们想要读取文本文件,也是可使用的

下面咱们看具体方式:

FileInputStream fileInputStream = new FileInputStream("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\FileInputStreamDemo.java");

// 获得流中的文件大小
System.out.println(fileInputStream.available());
// 读取流中的内容
System.out.println((char)fileInputStream.read());

官方给定三种初始化方式:

  • 传入File对象的方式
new FileInputStream(new File("文件名称"));
  • 传入文件名称的方式

上面的方式

  • FileDescriptor

这种方式不多使用, 这是一个文件描述符类

上面的是一个个的读取,下面咱们来看看若是简化其读取操做

input read

FileInputStream fileInputStream = new FileInputStream("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\FileInputStreamDemo.java");

// 读取方式
byte[] buffer = new byte[1024];
int len  = 0;
// 将内容读取到byte数组中暂存起来
while ((len = fileInputStream.read(buffer)) != -1) {
    // 输出到控制台
    System.out.println(new String(buffer, 0, len));
}
fileInputStream.close();

在流中,若是读到文件的最后一个位置,咱们经过read()方法来读取的时候会获得-1,因此咱们只须要经过判断len是否为-1就能够知道文件是否已经读取完成

在使用流来操做文件的时候,咱们在最后最好将流进行关闭:

  • 流操做是一个特别消耗资源的过程,若是咱们只开启流而不关闭的话,那么资源消耗会很是大

FileOutputStream

说实话,咱们在进行文件操做的时候,不可能说将读取到的文件输出到控制台啊,这没有任何意义

简单点说,若是咱们须要须要将内容输出到一个文件中,那么又是该怎么处理呢?

这里就用到了咱们的输出流:FileOutputStream

下面咱们来操做下:

FileOutputStream fileOutputStream = new FileOutputStream("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\_FileInputStreamDemo.java", true);

// 输出到文件中
fileOutputStream.write(65);
fileOutputStream.write(66);
fileOutputStream.write(67);

// 关闭流:和FileInputStream同样
fileOutputStream.close();

FileInputStream构造方式相似,不一样点在于FileOutputStream中多了一个参数:boolean append

  • 默认状况下:append=false,经过FileOutputStream进行输出的时候会对文件中的内容进行覆盖,
  • 若是append=true的话,那么这里会已追加的方式将内容输出到文件的尾部

重点:咱们必定要搞清楚一个问题

  • 就是流的流向问题:记清楚上面那张流向图,永远已程序为参照物

小案例:复制文件

FileInputStreamFileOutputStream都已经说完了,那么咱们来作一个小案例:文件的复制,考虑一下应该如何实现

给大家3秒钟

private static void copyFile() {
    FileInputStream fis = null;
    FileOutputStream fos = null;

    try {
        File sourceFile = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\FileInputStreamDemo.java");
        File targetFile = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\_FileInputStreamDemo.java");

        fis = new FileInputStream(sourceFile);
        fos = new FileOutputStream(targetFile);

        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = fis.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }
    } catch (IOException e ) {
        e.printStackTrace();
    } finally {
        if (null != fis) {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if (null != fos) {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

有时候咱们会忘记关闭流,因此咱们也能够采用下面这种方式

private static void copyFile2() {
        File sourceFile = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\FileInputStreamDemo.java");
        File targetFile = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\_FileInputStreamDemo.java");

    try (FileInputStream fis = new FileInputStream(sourceFile);FileOutputStream fos = new FileOutputStream(targetFile);) {
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = fis.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

该方式会帮助咱们关闭流,减小咱们的一步操做

这里是字节流的方式,下面咱们来看看字符流的方式

字符流

上面咱们说过,若是咱们要处理文本文件之类的,推荐使用字符流的方式,官方为咱们提供了比较方便的操做

这里我想多一句,你们以为字符是物理概念仍是逻辑概念?

你们要清楚:字符是逻辑概念,在计算机的世界里,没有什么东西是表明字符的

FileReader

FileReaderReader的子类实现,咱们来看看具体操做

File file = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\ReadDemo.java");

FileReader fileReader = new FileReader(file);
System.out.println((char)fileReader.read());

大致上和FileInputStream是同样的方式,这里就不过多介绍

FileWriter

FileWriter是Writer的实现,如下为具体操做:

FileWriter fileWriter = new FileWriter("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\_ReadDemo.java");
fileWriter.write("这是我经过FileWriter写入的");
fileWriter.append("append追加到内容后面");
fileWriter.flush();

FileOutputStream的构造方法相同,可是在api方法上和其有些区别:

  • write():提供了直接写入字符串的方式
  • append():提供了追加的方法

最大的一个区别在于:

  • 虽然咱们在经过write()方法将内容输出到了指定的文件,可是咱们经过打开文件发现并无内容,由于FileWriter会将内容暂存在流内存中,咱们须要手动刷新才能将内容从内存刷新到文件中:也就是调用flush()方法

  • 调用的close()方法,至关于作了两个操做:

    • 刷新内容到文件

    • 关闭流

不过,通常状况下,咱们建议手动调用flush()

好,咱们了解了这些以后,在经过上面复制文件的小案例来熟悉下二者:

小案例:复制文件

一样,3秒钟考虑时间:

private static void copyFile() {
    File sourceFile = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\ReadDemo.java");
    File targetFile = new File("D:\\Working Directory\\project\\study\\study-java\\src\\main\\java\\zopx\\top\\study\\jav\\_file\\_AA.txt");
    try(FileReader fr = new FileReader(sourceFile); FileWriter fw = new FileWriter(targetFile)) {
        char[] buffer = new char[1024];
        int len = 0;
        while ((len = fr.read(buffer)) != -1) {
            fw.write(buffer, 0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

文档

下面给出他们的具体官方文档,更多的方法推荐查看API:

FileInputStream

FileOutputStream

FileReader

FileWriter

相关文章
相关标签/搜索