1、课程名称:Java IO操作
2、知识点
2.1、上次课程的主要知识点
1、StringBuffer和String类的关系及区别,CharSequence是这两个类所实现的共同的接口;
2、Date、SimpleDateFormat、String与Date之间的转换;
3、只要是对象数组(多个对象)的比较操作永远都要使用比较器完成——Comparable;
4、正则表达式;
5、理解通过Class类完成对象的实例化操作。
2.2、本次预计讲解的知识点
1、File类的使用;
2、字节流和字符流的操作;
3、内存操作流、打印流、文件流的使用;
4、了解字符的编码问题;
5、对象序列化的操作。
3、具体内容
java.io包里面所保存的所有类和接口都是用于IO操作的,此包基本上算是整个java中自学者最痛苦的地方,IO操作并不麻烦,关键是要彻底的理解面向对象中的各个核心概念,在整个的IO操作中记住一句话:“父类定义操作的标准,而具体的操作实现由子类完成”。
整个IO操作核心就是五个类一个接口:File、InputStream、OutputStream、Reader、Writer、Serializable;
Java的核心重点:
·面向对象:都是围绕接口和抽象类;
·Java集合框架:包含所有的Java数据结构的实现;
·Java IO:面向对象的各个概念的应用;
·JDBC:Java数据库操作,现在的程序都是围绕数据库进行的。
3.1、File类(重点)
java.io.File类是在整个java.io包中最特殊的一个类,表示的是文件本身的若干操作,那么所谓的文件本身指的并不是对文件的内容操作,而是对文件的创建、删除等等这些的操作。
File类中提供了如下的几个与文件本身有关的操作方法:
·构造方法:public File(String pathname),应该给出要操作文件的路径;
·创建文件:public boolean createNewFile() throws IOException;
·删除文件:public boolean delete();
·判断文件是否存在:public boolean exists();
范例:观察方法的操作
但是这个时候此程序有以下的几个问题:
·问题一:在定义File类对象的时候需要指定一个文件的路径,但是这个路径有分隔符的问题,由于Java属于多操作系统支持,所以每一个所编写的程序必须考虑到操作系统的问题:
|- windows之中分隔符是“\”;
|- linux之中分隔符是“/”
为了解决此问题,在File类提供了一个常量:public static final String separator
·问题二:操作之中会出现延迟的问题,当创建文件或者是删除文件的时候会出现延迟,因为Java的程序都是通过JVM与操作系统间进行交互的,这之中就一定会存在延迟的问题;
·问题三:此时是直接在一个硬盘的根目录下创建的新文件,如果说现在要创建的文件有文件夹呢?
此时应该先进行文件夹的创建,创建文件夹可以使用:public boolean mkdir()
找到指定File的上一级目录:public File getParentFile()
但是此程序有一个比较麻烦的问题在于此时只能创建一个级别的父文件夹,如果现在的文件夹较多的话,那么就必须采用另外一个方法:public boolean mkdirs()
所以在以后的开发之中肯定要使用mkdirs()方法操作是比较合理的做法。
如果现在要想列出一个文件夹中的全部内容,则可以采用以下的方法:
·列出所有文件:public String[] list()
·列出所有文件:public File[] listFiles()
·判断给定的路径是否是文件夹:public boolean isDirectory()
·判断给定的路径是否是文件:public boolean isFile()
范例:先使用list()方法列出
这个时候所列出的只是文件夹或文件的名称而已,如果真的是操作的话,并不方便。
范例:使用listFiles()方法列出
很明显,这个操作所返回的是多个File类的对象,所以如果从操作的方便性而言,此方式肯定是最方便的。
思考题:要求给定一个文件夹的名称,之后可以将此文件夹中的全部内容进行列出,列出的时候也要列出所有的子文件夹中的内容。
此时,根本就不知道本程序需要循环多少次,而唯一的结束条件就是所有的内容都被列出来,只要还有文件就要一直列出,则对于此种代码只能通过递归的方式完成。
如果将此代码稍微修改一下,就成恶性程序,把输出换成删除。
从人道角度而言,肯定不用,但是对付小人有用。
3.2、字节流和字符流(重点)
File 类本身可以操作文件,但是却无法进行文件内容的操作,而如果要想进行文件内容操作的话,则需要使用字节流和字符流两种类型的操作流完成:
· 字节流:InputStream 、OutputS tream ; · 字符流:Reader 、Writer ;
但是不管使用的是何种流,其基本的操作形式是固定的,以进行文件的操作流为例。
1、 如果要操作的文件则首先通过File 类找到一个文件;
2、 通过字节流或字符流的子类为父类实例化;
3、 进行读 / 写的操作;
4、 由于流属于资源操作,操作的最后必须关闭。
3.2.1、字节输出流:OutputStream
此类是一个抽象类,而且实现了Closeable 、Fluashable ,这两个接口的定义如下;
由于在OutputStream 类之中也提供了close()和flush()两个方法,所以在很多的情况下用户往往不会去关心Closeable
和Flushable 两个操作接口。
在OutputStream 类之中定义了如下几个用于输出的方法: · 输出全部的字节数据:public void write(byte[] b) throws IOException
· 输出部分的字节数据:public void write(byte[] b, int off, int len) throws IOException
· 输出单个的字节数据:public abstract void write(int b) throws IOException
OutputStream 属于字节输出流,所以所有的数据必须都以字节数组的形式输出,但是这里面有一个比较麻烦的问题,
OutputStream 本身是一个抽象类,那么抽象类要想实例化必须依靠子类,所有的抽象方法由子类负责实现,而且最为重要的是抽象类规定了操作的标准,而由子类决定输出的位置,既然是向文件输出,所以使用:FileOutputStream 。
· FileOutputStream 类的构造:public FileOutputStream(File file
) throws FileNotFoundException
范例:完成输出的操作
通过运行可以发现字节流的操作特点:
·特点一:如果要创建的文件不存在,则在输出之前会自动的为用户创建;
·特点二:字节流操作的永远都是字节数组。
范例:输出部分的内容
但是反复执行程序之后发现有一个问题,就是说现在所有的内容都是新的覆盖掉旧的,能否追加呢?
但是之前的所有操作都是将所有的字节数组一次性的输出,那么下面使用循环的方式完成输出。
字节流的使用相对而言比较固定,只要有字节数组就可以完成输出。
3.2.2、字节输入流:InputStream
InputStream类是专门处理字节输入流的操作类,此类的定义如下:
可以发现本身也是一个抽象类,也实现了Closeable接口,在此类中提供了如下几个读取的方法:·读取数据:public int read(byte[] b) throws IOException,返回读取的个数,如果没有了则返回-1;
·读取部分数据:public int read(byte[] b, int off, int len) throws IOException,如果没有了则返回-1;
·读取单个字节:public abstract int read() throws IOException,如果没有了则返回-1
通过对比可以发现InputStream类和OutputS tream类中的所有方法是一一对应的,那么既然这个类是一个抽象类,所以要想进行操作,肯定依靠子类:FileInputStream,此类的构造方法如下:
·构造方法:public FileInputStream(File file) throws FileNotFoundException
范例:读取数据
同样,现在如果只是进行部分数据的读取,则可以采用另外一个read()方法完成。
但是除了这种做法之外,也可以采用read()按照单个字节的形式完成读取。
可是这种写法比较麻烦一些,所以在开发之中可以将以上的代码简化。
这种写法在实际的开发之中,包括工作上也是使用最多的一种写法,另外,如果说现在觉得开辟数组比较麻烦,那么也可以采用StringBuffer的形式接收。
同样的操作如果使用了是String的话,则代码的垃圾就太多了,所以StringBuffer就是用于此类情况下的。
3.2.3、字符输出流:Writer
之前所有的字节流都是以byte数组的形式进行的,而字符输出流肯定操作的字符(字符串、字符数组),Writer类的定义如下:
这个类是一个抽象类,这个类的定义形式又和OutputStream一样,但是这个类比OutputS tream唯一强在输出:·输出字符串:public void write(String str) throws IOException
·输出部分字符串:public void write(String str, int off, int len) throws IOException
唯一的方便所有的数据不用再变为byte数组了,而世界输出字符串,那么既然这个类是一个抽象类,则肯定也要依靠子类,文件操作的子类是FileWriter。
字符输出流比字节输出流唯一的好处就在于中文的处理上,因为一个字符是两个字节,而中文如果使用的是字节处
理会出现乱码问题。
OutputStream和Wirter类的关系就好比数据库之中的BLOB和CLOB的关系,BLOB可以包含CLOB,但是反过来不行,CLOB只是文字。
3.2.4、字符输入流:Reader
虽然字符输出流提供了可以将字符串输出的操作,但是这个操作对于输入流可没有,不能说所有的内容直接使用输入按照字符串的形式返回,Reader类的定义结构:
读取的时候跟InputStream一样,唯一不同的是使用的是字符数组完成,而且也分为三种,那么既然这个类是抽象类,如果是文件读取则使用FileWriter子类。
这四个类其实就只有两个功能,而且使用上都是非常的类似的。
3.2.5、字节流和字符流的区别
之前通过代码可以发现字节流和字符流在使用上都很相似,那么在实际的开发之中使用那种更好呢?
1、字节流和字符流的区别?
如果说使用的是字节流则所有的操作直接与终端有关系,而如果是字符流的话,则中间会加入一个缓冲区。
那么在输出的时候如果使用的字符流没有关闭,则保存在缓冲区中的数据将无法输出,但是字节流没有此类限制,所以说如果要使用字符流的话不关闭就必须强制刷新缓冲:public abstract void flush() throws IOException
所谓的缓冲就是指一块内存空间,之所以会加入这个过渡,主要的原因是在于程序中可以在这个缓冲里面对一些数据进行处理,例如:中文,会方便一些。
如果按照这种方式进行的话,肯定字节流要比字符流快一些,因为属于点到点的操作。
2、从文件的存储上来讲。
字符流最强的功能是处理文字,但是在整硬盘上所保存的全部内容都是字节数据,像图片、音乐等都属于字节性的数据,那么这个时候使用字节流会更加的方便。
所以,综合以上两点,以后就以字节流的操作为主,如果细想的话,可以发现,字节流和字符流在代码的操作形式上基本上是没有区别的,字节流会使了,字符流就一定会使。
3.3、字节流和字符流的转换类(理解)
既然流分为两大阵营:字节流和字符流,所以在Java类之中有的时候为了方便两种操作流之间的转换,也会提供相应的转换流的操作类:
·将字节输出流变为字符输出流:OutputStreamWriter;
|- 类的继承如下:
|- 构造方法:public OutputStreamWriter(OutputStream out)
·将字节输入流变为字符输入流:InputStreamReader;
|- 类的继承结构如下:
|- 构造方法:public InputStreamReader(InputStream in)
范例:完成这种转换的操作
本程序没有任何的意义,因为即使将字符串变为了字节数组也不麻烦,但是之所以要将转换流的概念提出,主要是
有一个原因,观察FileOutputStream 和FileInputStream 类的继承结构。
FileWriter 不是Wirter 的直接子类,而是OutputStreamWriter 的直接子类,同理,FileReader 也是InputStreamReader
类的直接子类,所以从类的继承关系上就可以发现一个特点,本身保存在终端的数据肯定都是字节,而所有的字符都是经过处理后的,而且这里面也增加了一个过渡操作。
3.4、思考题(重点)
下面使用IO 流完成一个文件拷贝命令的实现,例如:Java 的类是Copy ,输入的路径名称通过初始化参数的形式完成,例如:java Copy 源路径名称 目标路径名称,但是要考虑一些常见的问题。 就完成拷贝一个文件的操作,但是要考虑到大数据量文件的问题,例如:要拷贝的文件的数据量有30M 以上。 需要考虑的判断:
1、 判断输入的参数是否是两个;
2、 源文件是否存在;
判断完成之后,下面需要进行文件的拷贝操作,所谓的拷贝就是读取和写入。 实现形式一:将整个文件读取进来之后一次性写入
此时的确是完成了复制的功能,但是这种操作使不了,如果现在要复制的文件很大。
如果现在有500M的文件呢?肯定不能使了,如果一次性的读取不行,那么就慢慢读,采用循环的方式,什么时候读取到尾了,则不读了,边读边写。
此时一个字节的一个字节的读,要命呢,所以要大口大口的读,采用另外一个读取和写入的方法。
这种代码为以后开发的时候写入输出流的标准做法,一块一块数据的读取和写入。
3.5、字符编码(了解)
电脑中所有的文字都是编码,那么在现在的开发之中常见的编码有如下几种:
·ISO8859-1:国际的通用编码,所有的中文需要进行额外的转换,但是英文异常;
·GBK / GB2312:中文的国标编码,GBK包含了简体中文和繁体中文,而GB2312只是简体中文;
·UNICODE:可以包含世界上任何文字的编码,所有的编码使用十六进制表示;
·UTF-8:将UNICODE和IOS 8859-1融合在一起,该用十六进制的使用十六进制,如果不用就按照ISO 8859-1那样编码,这样可以减少编码的长度,所以使用较多。
范例:取得本机的编码
发现以下几个部分:
而在开发之中之所以会造成乱码其根本的原因就在于编码不统一。
对于编码的形式以后会有更加详细的解释,此处唯一要记住的就是:乱码的造成就是编码和解码不统一,或者更简单的理解为,程序中指定的编码与本地环境不统一所造成。
3.6、内存操作流(重点)
之前所讲解的全部操作都是围绕着子类向父类进行转型的实现,所有的操作方法父类都有标准的定义,之前所用到的是文件的操作流:FileInputStream 、FileOutputStream ,读取和写入的位置都是文件,但是如果说现在要想更换位置,例如将输入/输出的位置变为内存,则就要使用内存操作流,内存操作流分为两种: · 内存的字符操作流:CharArrayReader 、
CharArrayWriter ;
· 内存的字节操作流:ByteArrayInputStream 、ByteArrayOutputStream ;
内存流主要使用在需要进行IO 操作,但是又不希望产生文件的情况下,等以后学习到了AJAX + XML + DOM 操作的时候就都使用内存流。
但是在讲解内存操作流之前首先要分析一下问题,关于输入输出点的问题: · 文件操作: |- 文件输出:程序 → OutputS tream → 文件; |- 文件输入:程序 ← InputS tream ← 文件;
· 内存操作:
|- 内存输出:程序 → ByteArrayInputStream → 内存;
|- 内存输入:程序 ← ByteArrayOutputStream ← 内存;
这两个字节流内存操作类的继承关系如下:
ByteArrayInputStream 类构造:public ByteArrayInputStream(byte[] buf),接收数据,表示把数据保存在内存中;
ByteArrayOutputStream 类构造:public ByteArrayOutputStream()
范例:完成一个字符串小写转大写的操作,利用内存流完成
此代码现阶段不使用,但是同时也能够发现一点,InputStream和OutputStream两个类都是父类,需要的时候接收子类的实例化对象,由子类最终决定输入/输出的位置。
3.7、打印流(重点)
使用OutputS tream可以完成内容的输出,但是这种操作本身并不方便,例如:现在要求输出一个数字、小数、字符等等等,不能直接给予支持,因为OutputStream中能输出的只有字节数组信息,那么就意味着要将这些的内容变为字符串之后再变成字节数组输出。
范例:自己编写一个这种工具类
在java.io包之中为了方便的完成信息的输出,往往会使用一种打印流的形式完成,打印流有两种:PrintStream、
此类是OutputStream的子类,此类的构造方法:public PrintStream(OutputStream out),要接收OutputStream类的对象,接收OutputStream类的目的是为了明确的表示出输出的位置,而将其归纳在OutputStream的子类范畴之中,是为了说明其是字节流,本质离不开输出的操作,此类之中定义了print()、println()等常见的方法,这些方法可以方便的输出各种数据类型,如果按照此方式理解,可以发现,PrintStream就相当于是一个工具类,包装了OutputS tream操作,但是提供了比OutputStream类更多的操作方法,所以这种设计称为装饰设计模式。
此时一定要记住的是,由构造方法传入的OutputStream对象决定输出的位置。
范例:向文件中输出
这个时候使用的是FileOutputStream为PrintStream对象实例化,所以是向文件输出,而如果现在操作的是内存输出流的话,则表示从内存读取。
所以所谓的PrintStream就是指对OutputStream操作的一种封装,但是最终的输出的位置还有由实例化的OutputStream 子类的对象所决定。
但是在JDK 1.5之后,PrintStream的功能增加了,增加了格式化的输出操作,提供了以下一个方法:·格式化输出:public PrintStream printf(String format, Object... args)
范例:格式化输出
不过这种操作一般不使用,咱们一般会将此种代码变一变形式,在String上使用,String有一个方法:
虽然这种操作可以完成准确的四舍五入,可是这种操作所转变后的类型是String型,而更多的操作应该是以double 型数据进行,所以之前讲解的BigDecimal类依然不可替代。
任何情况下的输出,永远都使用打印流封装。
3.8、System类对IO的支持(理解)
System类一直出现,但是这个类之中有三个常量的IO对象:
·系统输出:public static final PrintStream out
·错误输出:public static final PrintStream err
·系统输入:public static final InputStream in
所谓的System.out实际上就是找到了一个PrintStream类的对象,但是这个对象所具备的功能是向屏幕上输出,而print()或println()方法就是IO操作的支持。
3.8.1、错误输出:System.err
System.err也是一个PrintStream类的对象,主要的功能是输出错误信息的,而且这个错误信息都在于打印错误对象。