文章目录
作者信息
作者:黄钰朝
邮箱:kobe524348@gmail.com
日期:2019年7月14日
前言
今天整理了Java的File类的相关方法,和Java中IO流相关的知识,了解JavaIO包中的各种流的体系,学习JavaNIO和JavaIO的区别,并且了解Java中的socket编程,按照导师的要求,使用socket编写聊天程序。
一.Java中的File类
1.1 什么是File?
- File是Java中用来代表文件/文件夹的一个对象
- File不等于文件,只是文件的抽象,Flie作为一个Java类的实例而存在,不依赖于具体的文件,只是Java把对文件的操作抽象成为对File对象的操作。
- File对象可以实现对文件的创建,删除,修改等操作,但是无法进行文件内容的读取,只能依靠IO流.
1.2 两种文件路径
- 绝对路径:带盘符的完整路径
- 相对路径:不写盘符,默认以项目位置为当前路径
1.3 File类的三种构造方法
- 通过一个文件路径创建一个File对象
- 通过一个文件路径+文件名/文件夹名创建一个File对象
- 通过一个File对象+文件名/文件夹名创建一个File对象
其中第三种相当于把父目录封装成File对象,再根据文件名/文件夹名创建File对象
1.4 File类常用方法
创建:如果目标文件/文件夹已存在,就不创建了
- createNewFile:创建一个文件,如果目录不存在则无法创建
- mkdir:创建一个文件夹,如果目录不存在则无法创建
- mkdirs:创建文件夹,如果目录不存在则创建多级文件夹
删除:如果目标文件夹内有文件/文件夹,则无法删除
- delete:删除一个文件/文件夹
修改:
- renameTo(File newfile):重命名一个文件/文件夹,相当于把文件转移到另一个File对象所指的路径
另有获取文件名,文件是否存在/可读/可写等方法
1.5 List方法获取当前目录下的文件/文件名数组
- list方法:获取当前目录下的文件名数组
- listFiles方法:获取当前目录下的文件数组
1.6 文件过滤器FilenameFilter
使用FilenameFilter过滤器,其目的是给list()方法列出文件或文件名时增加筛选条件
主要思想:过滤器模式和模板模式
创建一个过滤器,通过重写接口的accept方法传递策略,list方法在遍历文件名时使用过滤器去除不符合条件的文件
扩展:使用Lambda表达式简化代码
FilenameFilter接口是单函数接口,因此可以使用Lambda表达式简化代码
二.Java中的I/O流
2.1 什么是I/O流?
- 程序中数据的输入输出,被抽象为流, 按照相对于程序的流向,可分为输出流和输入流, 按照数据流的格式,可分为字节流和字符流(类似于C语言中以二进制和以字符形式读取文件)
- 另有有节点流和处理流,节点流用来包装源头,处理流的作用是对Java程序提供统一的数据输入输出的支持,并提高性能。
2.2 处理流模型
- Java将来自任何节点的数据流都包装成为处理流,使得可以使用相同的代码来处理来自不同设备的数据流,并可以以增加缓冲的方式提高性能,并支持一次输出大批量的内容,这便是处理流模型,思想类似于C语言中流式文件的处理方式
2.3 字节流和字符流有什么区别?
- 类似于C语言中二进制文件和文本文件的区别,字符其实只是一种特殊的二进制字节,是按照一定的编码方式处理之后,按照一定规则来存储信息的数据,字符在计算机中也是由二进制组成的,只不过这种二进制可以按照一种规则解码后,成为人类可以直接阅读的自然语言,而普通的二进制文件只有计算机能直接“阅读”
2.4 I/O流的抽象基类
输入流的抽象基类
- 字节输入流 : InputStream
- 字符输入流 : Reader
输出流的抽象基类
- 字节输入流 : OutputStream
- 字符输入流 : Writer
2.5 转换流
- Java提供了从字节流到字符流的转换流,分别是InputStreamReader和OutputStreamWriter
- 字符流=字节流+编码表
- 没有从字符流到字节流的转换流
2.6 缓冲流
- 一次读取一个字节数组的效率明显比一次读取一个字节的效率高,因此Java提供了带缓冲区的字节类,称为缓冲区类。BufferedInputStream和BufferedOutputStream
- 字符缓冲区类BufferedReader和BufferedWriter与之类似.
- 缓冲区类的构造方法可以指定缓冲区的大小,如果没有指定,则使用默认大小,一般默认大小已经够用了
- 缓冲区类只是提供缓冲区,实际的读写操作还是基本IO流对象来做的,因此缓冲区类的构造方法的参数是基本IO对象
2.7 其他输入输出流
数据输入/输出流 DataInputStream
- 与缓冲区流的使用方式类似,也需要传入一个实际进行读写操作的InputStream实现类,数据输入/输出流的特殊之处在于它是对Java中的各种类型数据进行输入输出
内存操作流 ByteArrayInputStream
- 与FileInputStream类似,只是输入输出的对象是一段内存空间,即内存操作流内部的字节数组。相当于把内存操作流对象当成一个字节数组,对它进行输入输出。
打印流 PrintStream/PrintWriter
- 打印流是单向的,只有输出流,没有输入流
- 可以打印任意类型的数据
- 可以启用自动刷新
- 打印流可以直接操作文本文件
- 打印流是以字节方式输出
随机访问流 RandomAccessFile
- 仅可用于文件操作
- 相当于C语言中的文件读写操作,可以随机读写文件,即可以操作文件的读写指针,并且可以选择使用只读/只写等方式读写文件
- getFilePointer:得到当前文件指针位置
- seek:设置文件指针位置
合并流 SequenceInputStream
- 用于将多个输入流合并
序列化流 ObjectOutputStream
- 用于将对象写入文件,或者在网络传输,操作的对象必须是已经实现序列化接口的(序列化接口只是一种标记接口)
- 序列化流是将对象存入文件或在网络中传输,反序列化流就是将流数据还原成为对象
- 一个类的序列化值与类的成员变量有关,如果类的成员变量改变,则序列化值也会变,一个对象被存储到文件时会记录它的类的序列化值,如果将来类的序列化值改变了,这个对象就无法被反序列化,即无法还原为对象了,因此通常生成一个serialVersionUID,用来记录当前版本的类的序列化值,以便将来反序列化以往版本的对象
- 使用translent关键字可以让一个成员变量不被序列化
2.8 关于I/O一些问题的思考
system.out,system.in和Scanner的本质是什么?
- in和out时system类中的两个静态成员变量分别是标准输入流InputStream和标准输出流PrintStream,分别代表系统的标准输入输出设备,即键盘和显示器
- Scanner是JDK5之后出现的方便处理键盘输入的数据的类,本质相当于一个缓冲区,并且封装了将字节信息转成各种数据类型的方法
文件如何输入输出数据?
- 不管是字节流和字符流,都提供了read方法和write方法,分别用来读取和输出数据。
- 创建输入/输出流对象时的参数都是指向外部节点的对象(比如File对象)
- 调用read和write方法时的参数都是指向程序中内存空间的对象
- 文件输出操作步骤:创建输出流对象(提供输出目标)->调用write方法(提供数据源)->释放资源
- 文件输出的操作步骤可以看成:提供水管(让水管指向目标)->喷水(提供水龙头)->回收水管
为什么要使用close释放资源?
- 让流对象成为垃圾,可以被垃圾回收器回收
- 通知系统去释放该文件相关的资源
为什么要使用flush刷新缓冲区?
- 因为Java中输出流的内容会被先保存在缓冲区,直到缓冲区满,才会真正把数据发送出去,当数据很小时,可能导致数据接收不到,而使用flush可以强制将缓冲区的数据发送出去,不必等到缓冲区满
三.Java的NIO
3.1 传统BIO的缺陷和NIO的优点
Java IO包下的类是传统的BIO,也就是阻塞式IO(blocking-I/O),在做导师安排的控制台聊天项目的过程中,就被传统I/O的“阻塞”问题困扰了很久,“阻塞”就是线程在内存中等待一个事件的就绪,而不能去处理其他任务的状态 ,具体在项目中体现在,使用传统I/O的socket编程,只有一个线程时,一旦开始执行读取客户端的数据的read方法,就进入阻塞状态,直到客户端真的发了数据过来,这个方法才能返回,线程才能处理别的事情,而我一开始做的时候就是只有一个线程,导致消息发不出去,后来使用多线程才解决这个问题。
NIO就是为了解决“阻塞”问题而出现的。NIO的N的含义有两种说法:
- new 代表这是“新”的I/O
- non-blocking 代表这是“非阻塞”的I/O
在Java中,认为NIO是New I/O的说法比较多。
3.2 NIO实现非阻塞的原理
先上图:
可以看到,NIO的主要原理是执行read,write,accept,connect这些方法时,都会立即返回,不会等待,read方法如果没有数据可读,直接返回0,accept方法如果没有客户端连接,直接返回null。在这种非阻塞模式下,可以配合selector来实现单线程监听各个事件。
PS:其实很多时间在看NIO的知识,但是没时间写了,要开设计模式的内容了。
四.总结
I/O流的知识非常基础实用,是网络编程的基础。但是这两天,第一天早上补完了数据库设计的笔记,下午又开了一下午的全体例会,加上做聊天程序花的时间很多,没有先看一下别人的代码,自己看了socket几个方法的用法就开始搞,因为没有线程方面的知识,被线程阻塞的问题拖了很多时间,后来去使用了多线程,才终于能够和客户端互发消息。后面学了NIO,看了疯狂java讲义上NIO做的socket编程的例子,才发现使用非阻塞I/O来做会很容易。
由于做项目花了挺多的时间,NIO和socket的知识没有时间写多少笔记。