黑马程序员-Java基础总结10——IO流体系(一)

时间:2021-08-30 21:37:32

Java基础总结10——IO流体系(一)

-------  android培训java培训、期待与您交流!----------

内容:      字节流(InputStream与OutputStream)、字符流(Writer与Reader)、
File类与文件流(FileInputStream与FileOutputStream)、    字节/字符缓冲区、
转换流(InputStreamReader与OutputStreamWriter)、装饰设计模式、IO异常与递归、
Java访问文件注意事项 与 参数传递、Properties类(配置类)。

一、IO流概述:

文件是数据的静态存储形式,而流是指数据传输时的形态,而流操作对象除文件外还有内存、网络连接、字符串等。因此Java将不同来源和目标的数据都统一抽象为数据流

Java操作数据的流类存放在java.io软件包,因此通常将数据流称为I/O流

根据IO流的处理对象与处理方式不同,有多种不同的分类方式:

1、操作对象: 文件流、网络流、内存流、字符串流(操作字符串数据)、管道流等。

2、操作方式: 节点流(直接操作数据对象)和处理流(过滤流类,即对流中数据操作流类);

3、操作数据: 字节流(InputStream、OutputStream)字符流(Writer、Reader)

4、按流向分为: 输入流(InputStream、Reader)与输出流(OutputStream、Writer)。

备注:

程序可从中连续读取数据的对象叫输入流,程序可向其连续写入数据的对象叫输出流

流操作(读取写入)的数据是字节(8位二进制),则称为字节流,操作数据是字符(16位二进制),称为字符流

PS:  字节流不能操作Unicode字符,而Java采用16位的Unicode字符,即一个字符占16位(16Bit),因此字节流不能使用基于字符的输入输出操作。所以创造了字符流,以提供直接的字符输入输出的支持。        【Unicode: 统一码,即二进制编码】
语言编码: GBK(汉语编码)、UTF-8、ISO8859-1(欧洲编码)、ASCII(拉丁字母编码)等。
【FileReader、FileWriter默认当前系统字符编码,中文系统默认为GBK编码】

【解释,可略: 字符流是从字节流演化而来的;因为计算机操作数据除了图片、视频、音频等以字节为主的数据,还有文本文档的字符数据,而后者操作的字符多是以2个字节为一个字符,当使用字节流读取该类型数据时,如果发生中断,中断时存储的数据可能只有字符的单个字节,导致错码,因此引入字符流来操作字符数据,即使发生中断也可避免断层情况。】

二、IO流中四个抽象基类:

字符输入/读取流Reader、字符输出/写入流Writer ;

字节读取流InputStream、字节写入流OutputStream。

流使用格式: (创建字符流Reader、Writer,完整代码)

Reader fr = null;

Writer fw = null;

try{

fr = new FileReader(“D:/Read.txt”); //多态(父类接口引用指向子类对象)

fw = new FileWriter(“D:/Write.txt”); //从指定txt文件读取,写入其他文件;

char[] chs = new char[1024]; //定义字符数组,用于存储读取数据,减少内存读写次数;

int len = 0;

while((len = fr.read(chs))!=-1){

//将读取字符存储到字符数组,并判断字符数组是否为空;

System.out.println(new String(chs,0,len)); //以String形式打印字符数组;

fw.write(chs); //将字符数组中的数据输出到流,写入到文件等;

}

catch (IOException e){ //处理IO流的异常;

System.out.println("read-Exception :"+e.toString());

} //此处仅打印语句,实际应进行异常处理和存储至异常日志;

finally{ //finally语句,一定会执行的代码;

if(fr!=null & fw!=null){ //判断流中数据是否为空。

try{

fr.close(); //关闭流(要养成习惯),释放流占用的内存空间;

fw.close();

}catch (IOException e){ //处理关闭流失败时抛出的异常;

System.out.println("close-Exception :"+e.toString());

}

} }

要及时关闭流的原因:

因为创建流就相当于在内存中创建数据对象,占用内存空间,当处理大量流数据时如果没有及时关闭流会不断在内存创建大量流对象、占用内存空间,而流只有在长时间未被使用或虚拟机关闭时才能被释放,因此没有及时关闭流的程序可能在运行时发生内存不足等异常。

IO流中字节流与字符流体系图:

黑马程序员-Java基础总结10——IO流体系(一)
功能简概:

1、File + 文件流: 用于读取文件数据或写入数据到文件;

2、Buffered + 缓冲流: 起到缓冲区的作用,相当于字节或字符数组,提高流读写效率;

【流读写数据是单字节或单字符操作,通过创建数组来存储每次读写数据,可减少内存频繁读写操作;而缓冲流则是Java提供的内部封装字节/字符数组的流,直接使用对应的缓冲流无需自定义数组,简化代码编写。】

3、转换流:

读取字节数据转换成字符数据或将字符数据转换成字节数据写入到文件等中;

文件存储形式是字节,文本数据在内存等的显示则是字符,通过转换流即可直接读/写并转换,字符流读写文件字符数据也是基于这一原理】


三、File类:

File:  操作文件、目录的类;
作用:    1、可以通过指定目录或指定文件,从而获取该目录下的所有文件对象
         2、除获取目录、文件对象信息外,可对进行设置操作(读写权限等)
可获取文件对象的信息:  
文件/目录大小、是否为隐藏文件、文件名称、文件路径(包括绝对路径)等;
   【PS: 无法判断该文件是否为系统受保护权限,故可能抛空指针异常】
盘符、目录对象的信息:
获取盘符名称、盘符(分区)的大小、分区剩余空间等;
目录的路径(绝对路径)、完整路径、相对路径等。
代码示例: 
File f1 = new File("指定目录","指定文件");   //创建操作File对象,并指定文件路径
//以下为较繁杂的创建方式,但可将路径定义为变量,通过反射实现启动时任意赋值;
File f = new File("指定目录")   
String str = "指定文件";       
File f2 = new File(f,str);  
File类字段:   (均为static,其他详见java.io.File类)
String  pathSeparator   : 通用路径分隔符;
String  separator       : 名称分隔符\,跨平台通用;
【Window的路径 File("c:\\"),通用写法:File("c:\\"+File.separator)】

File类常见方法: 
String[]  list();     : 返回指定目录下所有文件或目录的名称;(可指定过滤器)
File[] listFiles()       : 返回指定目录下所有文件或目录的对象(对象可操作,同上);
long  length() : 获取文件的长度(大小);
boolean  exists();       : 判断文件或目录是否存在
isDirectory(); : 判断该File是否为目录/文件夹;
boolean   createNewFile()  : 创建指定文件,如果已有同名文件则不创建;
delete()          : 删除文件(失败返回false);
deleteOnExit()    : 程序退出时删除指定文件;
String  getParent();  : 返回的是文件绝对路径中的父目录;
[ 没有封装路径(即只有相对路径),返回null ]
renameTo(File dest);  : 重命名文件(如果文件有指定目录,还会发生移动);
其他:    
1、字节读取流的int available()方法是实际上是调用File类的length()方法。
2、IO流中的输出流的文件保存其实是覆盖,先通过File判断文件是否存在,存在则删除,新建一个新的同名文件,写入数据。而File类中的createNewFile()方法先判断文件是否存在,如果存在则不创建文件
3、判断文件是否是文件或目录时,必须先判断该文件是否存在(exists判断)。
————————————————————————————————————
四、文件流:                   (数据读取、输出间可对其数据进行操作)
FileInputStream   : 文件输入流;(读取指定文件数据存储到文件缓冲区中)
FileOutputStream  : 文件输出流;(从文件缓冲区输出数据到指定文件)
FileReader    : 字符文件读取流;(只能读取字符文件的数据,例:txt等)
FileWriter    : 字符文件输出流;(只能输出字符数据到文件中)
因为文件是该类流操作的对象,所以必须要指定操作文件及路径(没有无参构造方法)。
【因为要操作的文件的路径可能不存在等,所以IO流赋值动作基本都要try或throws异常】
————————字符流——————————
Write类方法:
  write(T t);  : 写入数据(单个字符、字符串或字符串片段、字符数组或片段);
  append(char c);  : 将指定字符添加到该Writer中(是添加到内存中,等待刷新);
  flush();     : 刷新该流的缓冲;
  (write先将数据写到内存,通过flush将数据从内存写入到指定文件);
  close();     : 关闭流,但会先刷新它;(清空内存中数据,保存避免数据丢失)
Reader类方法:
  read(T t);   : 读取数据(存储到数组、数组指定范围、字符缓冲区);
  ready();     : 判断是否准备读取此流;
  reset();     : 重置该流;
  close();     : 关闭流;
  long  skip(long n);  : 跳过字符(将不读取指定字符,返回但不是存储到流中)
  markSupported()      : 判断此流是否支持mark()操作;
  mark(int readAheadLimit)   : 标记流中的当前位置;
FileWriter类(仅有构造函数,无一般方法,使用Writer类方法)
  FileWriter(File file,boolean append)  : 根据给定File对象构造FileWriter对象;
  FileWriter(String fileName,boolean append)   根据指定文件构造FileWriter对象;
【boolean append() 是是否在指定文件后面添加内容,可不写默认为false,将覆盖原有文件数据;添加true,则将此次写入的数据添加到该文件已有数据的末尾。】
FileReader类   (是InputStreamReader转换流的子类
(仅构造函数,只需指定文件,使用默认编码表——GBK)
———————字节流———————
FileInputStream
  read();                             : 读取单个字节;
  read(byte[] b,int off,int len)  : 读取指定个数字节存储到byte数组指定位置;
int  available();      : 返回读取流所能操作文件中的估计字节数。
  close();             : 关闭流;
  finalize();          : 确保在不再引用文件输入流时调用其 close 方法;
FileOutputStream类: 
  write(byte[]或缓冲区);           : 将指定容器中的数据写入到文件输出流;
  write(byte[] b,int off,int len) : 将指定byte数组中数据写入到文件输出流;
  close()
  finalize()           : 清理到文件的连接,当不再引用此流时调用close 方法;
———————缓冲流——————
缓冲流:    (输入流读取的数据会存储到缓冲区中,而输出流从缓冲区中输出数据)
BufferedInputStream    : 字节输入流缓冲区;
BufferedOutputStream   : 字节输出流缓冲区;
BufferedReader          : 字符输入流缓冲区;
BufferedWriter : 字符输出流缓冲区;
BufferedWriter 类方法: 
        .write(……);    : 写入数据到流中;
        .newLine();      : 换行符,在Window中代替/r/n,在L代替/n;
        .flush();        : 同理,刷新缓冲;
BufferedReader
.read(char[] cbuf, int off, int len);  : 读取字符,存储到数组;
        .readLine();   : 读取一行数据但不包含行终止符(无数据返回null);           
【是基于read方法,借助行终止符判断一行是否结束,最后将提取的不包括行终止符的数据写入到内存中,因此通常配合BufferedWriter的newLine方法实现完整复制】

——————————转换流————————————
字节流转字符流 : InputStreamReader;     (读取字节数据,以字符形式存在流/内存中操作)
字符流转字节流 : OutputStreamWriter;    (以字符形式操作数据,但字节(流)形式存储到硬盘中)
     (在读取/写入时使用的还是前者,只不过是以后者的形式操作)
两个转换方法都属于字符流类的原因: 字符流是基于字节流产生的,只不过字符流操作每次操作字节数不同(例如一个汉字等于两个字节(Byte)),实际在操作存储时用的是字节流(字符流底层是字节流,每次存储字节数不同)。

键盘录入读取时用的是字节流(InputStream is = System.in);
                 System.in  返回值类型是InputStream;
存储到文件等时用的也是字节流(OutputStream os = System.out)
 System.in 返回值类型是InputStream
System.out  返回值类型是PrintStream(是OutputStream体系子类)。
流程化明确使用流:     (全解析)
源: (可以用什么流提取)
文本文件: 字符流、字节流;   (处理文本数据建议优先使用字符流)
文件(所有类型文件): 字节流;
键盘录入(System.in): 字节流;
代码示例:     (键盘录入:
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
字节流读取键盘录入的字节数据,将字节流(数据)转换成字符流,存储到字符流缓冲区中。
控制台输出:
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
从字符流缓冲区读取字符数据,将字符流(数据)转换成字节流,打印到控制台上。
System.in和System.out是标准输入、输出流,默认指向键盘、控制台
1、可通过System类中的setIn()和setOut()方法修改,
2、要实现通用,最好: 源(字节)———>内存存储(字符流)———>目的(字节)】
3、两者本身是彼此分开使用,就是要注意中间过程操作的是字符流数据;
4、操作(读取/存储)数据时是使用引用对象 (此处为缓冲流)的方法)
【PS: 可通过String类getBytes()方法,将字符转换成字节】
————————————
流操作的基本规律:
通过三个明确来完成操作数据时需要使用什么流对象。
1,明确源和目的
        源:输入流。InputStream  Reader
        目的:输出流。OutputStream  Writer。
2,操作的数据是否是纯文本
        是:字符流。   Reader、Writer;
        不是:字节流。 InputStream、OutputStream。
3,当体系明确后,在明确要使用哪个具体的对象
        通过设备来进行区分:
        源设备:内存,硬盘(文件)。键盘(System.in);
        目的设备:内存,硬盘(文件),控制台(System.out)。
PS: 1、是否需要提高效率:      "Buffered + 对应超类流" ;
    2、是否需要转换:  InputStreamReader、OutputStreamWriter。
   【注意: 因为InputStreamReader是涉及字节转字符转换,默认使用本机GBK编码表,可根据需要修改成UTF-8、GB2312、Unicode等编码表】
GBK: 一个汉字对应两个字节;
UIF-8: 一个汉字对应三个字节;
—————————————

五、装饰设计模式:

已知BufferedReader字符缓冲流中有read(读取单个字节)与readLine(读取一行)两个方法,

而readLine功能即是装饰设计模式的体现,因为BufferedReader内部封装了字符数组,拥有read功能并对其进行增强成readLine功能,因此将这样对已有功能的封装并加强成单独的类称为装饰类,使用的是装饰设计模式
装饰类与继承的区别:
  1、装饰模式比继承灵活,避免了继承体系的臃肿,降低类与类之间的关系
  2、装饰类是增强已有对象,具备功能相同,只不过更强;而继承功能可能不同
注意: 
  不建议为了少数功能而去创建接口提供给子类,可以通过装饰模式(封装功能的装饰类)接收子类,为其实现所需的功能。(降低类与类之间的耦合性)
示例:  
数据 ——> 缓冲区BufferedReader ——>内存——> 缓冲区BufferedWriter ——> 数据
注:   缓冲区在读写数据步骤可跳过,不过就像读取单个字节和读取到字符数组的区别;
通过缓冲区技术可以一次操作大量数据,提高操作效率。
(当内存可处理一定量数据时没必要单个字节/字符来回读写数据)
IO流中典型装饰类:   
LineNumberReader类的方法:       (BufferedReader子类)
.readLine();         读取一行数据;
.setLineNumber();    指定开始行号;
.getLineNumber();    返回当前行号;
——————————————————————————————————
六、IO异常(IOException):
处理方式一:    处理异常;
try     {   }
catch(Exception e){  }
finally   {    }
处理方式二:    在函数上抛出对应的异常;
throws Exception;
处理方式三:    采用异常包装技术包装异常再抛出;
此方式针对无法直接抛出又无法处理的异常,详见“继承”章节的“自定义异常”。
异常信息日志: 
catch(Exception e / 即异常类型 异常类名){
e.printStackTrace(  指定输出流  );
}      //通过输出流将异常情况存储到异常日志文件中。
异常处理(try、throws)及异常日志文件(异常日志文件工具log4j)。
—————————————————递归————————————————
七、递归:  重复调用本方法(要求该方法能结束,不能无限循环,否则会发生内存溢出)。
示例: 
类名调用静态方法并传值: XXX.getNum(100);
public static int getNum(int i){
  int num = 0;      //用于返回值的变量;
if(i >= 0)        //根据需要选择判断条件,是递归结束的关键;
      num = i + getNum(i-1);   //在此处不断调用本方法,直到达到递归结束条件;
return num;      //返回值,既用于最后的返回值,也用于返回递归值。
}
可实现功能:    
不断循环,(在File类时)可实现指定目录则自动搜索、获取该目录中所有子目录中数据。
1、删除文件夹的原理(从里到外,逐个删除);
2、获取指定目录下所有文件的对象(包括子目录中的);
   2.1、即可对文件、目录对进行增删改查操作;
        (增加文件、删除文件/目录、复写文件或追加数据、获取文件相关信息)
   2.2  根据文件对象获取文件名、路径,可创建文件列表等。
实现详见:  http://blog.sina.com.cn/s/blog_647ef76d0101dkpr.html (个人小软件)
—————————————小知识点————————————————
八、小知识点:  Java访问文件注意事项 与 参数传递;
一、Java访问文件注意事项:   (两种权限、两种状态)
1、完全控制权限: 电脑系统中一般文件的默认系统权限,Java可操作的文件是建立在这种权限上,此权限分为两种状态 :     
非隐藏状态 : 默认显示状态,此权限下的这种状态文件,Java程序可对其进行IO流的读取、写入操作包括复写功能)。
隐藏状态 : 即隐藏文件/文件夹,隐藏状态的文件,Java的IO流技术只能对其进行读取和追加数据操作(即不能复写该文件)。
【FileWriter、FileOutputStream流可以在隐藏文件的末尾追加数据内容】
【除了正在操作中的文件,只要是这种权限的文件都能够被删除,包括隐藏状态】
2、保护(限制)权限: 系统中可对文件进行不同类型的权限保护(限制),禁止访问、禁止删除、禁止添加数据等。
在系统盘(C盘等)中会有不少系统文件夹被设置为此类权限,即拒绝访问(列出文件、读取数据)该文件等,所以当访问、遍历该文件夹时,File类的list()等方法会返回null值,那么在遍历返回的File[]数组时就会发生空指针异常
详解与设置方法等,详见:  http://bbs.itheima.com/thread-49303-1-1.html
注意:  
    虽然在Window系统中默认将这些受保护的系统文件进行隐藏,但是一般的遍历还是会搜索到,容易发生NullPointerException,建议如下: 
    A、尽量不要“全盘”访问或操作系统盘中的文件与文件夹;
    B、如果需要进行全盘遍历操作,建议对File类的方法返回的数据进行判断操作,判断返回值是否null,如果为null,建议跳出该文件或文件夹,不对其进行操作
—————————————————————————————————————
二、参数传递:  (小提示: 巧用参数传递功能)
当需要对一个容器(例如: 数组、集合等)做操作、存储等动作时,可以在本方法中创建该容器,然后将该容器作为参数传递给方法通过该方法的执行实现所需要的操作(添加元素或者删除、遍历)。
示例:   [ArrayList al = new ArrayList();]  
调用:   xxx.sortDate(al);   //调用方法,传递参数: 集合或数组的引用
public void sortDate(Collection c){    //接收集合,进行所需的操作;
   Collections.sort(c); //直接对集合进行操作,无需返回操作后的集合或数组
}
好处:  
  1、此操作容器(容器)的代码被封装成为独立函数/方法,减少本方法(特别是主函数时)中的代码篇幅;
  2、提高了代码的复用性,该方法可以被重复使用;
  3、不用专门定义返回值、返回类型,通过传递变量的引用,该方法是直接操作堆内存等中的数据(通过原来的变量引用,能直接操作该变量数据)。
(注意: 这种特殊的参数传递功能只适用于集合、数组容器,因为其他数值变量的传递实际上是在传入的方法中建立自己的变量,然后存储该引用。)
———————————————Properties类———————————————
九、Properties类: 
1、是HashMap的子类,键、值都是字符串
    2、可保存在流中或从流中加载,是集合中和IO流相结合的集合容器。
.properties文件存储键值对形式的配置文件
【加载数据时,需要数据有固定格式: 键=值 。】
Properties类中方法: 
load(InputStream in)或load(Reader in): 从输入流中读取属性列表(键值对)
list(PrintStream out)或list(PrintWriter out) : 输出属性列表到指定输出流;
Set  stringPropertyNames()   : 返回属性列表中键集(用于遍历等)

在Properties信息存储文件中:
1、#修饰的信息表示是注释信息,不会被Properties的方法所加载;
2、加载信息必须是键值对("="连接的两个字符串),获取才有意义。
应用:  应用于设置文件的配置信息(字体颜色、试用次数等);
        每次启动文件时读取该文件指定的配置文件读取信息,如果有修改就保存等。 
Java程序使用的配置文件一般为.properties或者.xml两种;
.properties  : 是简单键值对形式存储,通常只用于存储数据关系简单的配置数据文件。
【properties存储格式:  name=zhangsan ; age=20 等,这些可看做一个Person类,但是如果要存储多个类似的数据时就容易混淆,不易直观区分,而XML就能解决】
.xml  : 有特定的存储形式,可存储数据关系较复杂的配置文件;
————————————
【xml存储格式】:
<persons>
        <person id="001">
               <name>zhagnsan</name>
               <age>30</age>
               <address>bj</address>
        </person>
        <person id="002">
               <name>............
        </person>
</persons>
——————————————
通过XML配置文件就可以存储数据关系较复杂的配置信息,虽然它的存储格式较复杂些,但是Java中有专门的类: org.w3c.dom包中接口Document,它提供方法可处理整个HTML或XML文档,因此可使用“dom4j”(dom for java,是Java的XMLAPI)工具来操作此类数据。