来小伙伴们,咱们认识一下。html
俗世游子:专一技术研究的程序猿java
在前面的几篇文章中,咱们分别介绍了api
LinkedHashMap继承自HashMap,咱们在这里不作过多介绍数组
根据以前的结构图,咱们还缺乏Set没有介绍。缓存
说实在的,这里我不打算和详细的来介绍Set集合,介绍Set也只是调用其api方法。咱们先快速过一下。网络
Set是不包含重复元素的集合,底层基于Map实现oracle
这也是为何不介绍Set的缘由app
在Set集合中,其源码实现直接依赖Map来实现,Set添加的元素,经过Map的Key来存储,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的一些特色:
public boolean add(E e) { return map.put(e, PRESENT)==null; }
Set元素不可重复,惟一,且无序
public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); }
public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); }
这里咱们引出一个方式叫作:组合方式
在java中,基于基础类而后再其类中进行创新操做的方式,有两种方式:
对比以上两种方式,组合基础类的优势:
本文章主要仍是要介绍一下Java中的IO。IO也就是输入/输出;Java中的IO操做本质上是对磁盘文件的操做
这种方式属于磁盘IO,固然,还存在一种叫作:网络IO
能够说咱们开发的程序,系统瓶颈主要就出如今这两块IO上,咱们在开发中也是针对这两块在代码层面来进行优化的
从总体划分上,能够分为三大类
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
那么接下来咱们来看看应该如何操做文件,在Java中为咱们提供了一个专门用来处理文件的类: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方法查看官方文档:
这是关于文件方面的相关内容,下面咱们来聊一聊另一个内容
上面咱们能获得当前文件了,可是咱们若是想要经过代码来读取文件中的内容,那么咱们应该怎么作呢?
这就是咱们下面要聊的内容:流
能够这么理解:
从一个文件将数据返送到另外一个文件。这里包含一个流向问题,这里的流向咱们须要指定一个参照物。
参照物确定不用过多解释了:就是咱们写的程序
查看一下图,咱们来清晰认识下流向
如下类都是基类
使用明白了其分类,那么咱们来看看流究竟是如何来操做文件的
正如咱们所说,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());
官方给定三种初始化方式:
new FileInputStream(new File("文件名称"));
上面的方式
这种方式不多使用, 这是一个文件描述符类
上面的是一个个的读取,下面咱们来看看若是简化其读取操做
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 = 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
重点:咱们必定要搞清楚一个问题
FileInputStream和FileOutputStream都已经说完了,那么咱们来作一个小案例:文件的复制,考虑一下应该如何实现
给大家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是Reader的子类实现,咱们来看看具体操做
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是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()
方法将内容输出到了指定的文件,可是咱们经过打开文件发现并无内容,由于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: