---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------

黑马程序员-------------(七)IO(Input Output)流

目录

一 概念

    1.分类

    2.IO流常用基类

    3.IO程序的书写

二 字符流

    1.FileReader 和 FileWriter

    2.字符流缓冲区

    3.装饰设计模式

三 字节流

    1.基本操作

    2.特殊操作

    3.字节流缓冲区

四 转换流

    1.读取键盘录入

    2. InputStreamReader 和 OutputStreamWriter

    3.流操作的基本规律

    4.流的基本应用

五 File 类

六 递归

七 Properties

八 IO包中的其它类

九 字符编码

注:本章重点内容:装饰设计模式,递归,字符编码。对于重点内容,根据知识的重要程度用◆◆◆、◆◆◆◆、◆◆◆◆◆进行了标注。


####################################################################################
一 概述
####################################################################################

IO流用来处理设备之间的数据传输,Java对数据的操作是通过流的方式,Java用于操作流的对象都在IO包中。流按操作数据分为:字符流与字节流。流按流向分为:输入流,输出流。
 
1.分类



|--字符流
    |--Reader 用于读取字符流的抽象类
        |--BufferedReader 从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。使用了装饰设计模式。
            |--LineNumberReader 跟踪行号的缓冲字符输入流。定义了setLineNumber(int i)和getLineNumber(),分别用于设置和获取当前行号。
        |-- InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。
            |-- FileReader 用来读取字符文件的便捷类。
    |--Writer 写入字符流的抽象类。
        |--BufferedWriter 将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。使用了装饰设计模式。
        |--OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。
            |--FileWriter 用来写入字符文件的便捷类。
        |--PrintWriter 向文本输出流打印对象的格式化表示形式。永远不会抛出 IOException;

|--字节流
    |--InputStream 此抽象类是字节输入流的所有类的超类。
        |--FilterInputStream
            |--BufferedInputStream 字节输入流的缓冲区
            |--DataInputStream 可以读取基本 Java 数据类型。
        |--FileInputStream 用于读取诸如图像数据之类的原始字节流。
        |--SequenceInputStream 可以将多个输入流合并为一个输入流
        |--ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
        |--PipedInputStream 管道输入流,应连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。使用单线程可能会死锁。
        |--ByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
    |--OutputStream
        |--FilterOutputStream
            |--BufferedOutputStream 字节输出流的缓冲区
            |--PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。永远不会抛出 IOException;
            |--DataOutputStream 可以将基本 Java 数据类型写入输出流中。
        |--FileOutputStream 用于写入诸如图像数据之类的原始字节的流。
        |--ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。通过在流中使用文件可以实现对象的持久存储。
        |--PipedOutputStream 管道输出流,应连接到管道输入流来创建通信管道。管道输出流是管道的发送端。
        |--ByteArrayOutputStream 可使用 toByteArray() 和 toString() 获取缓冲区中的数据。关闭 ByteArrayOutputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。

|--RandomAccessFile 随机访问文件。此类的实例支持对随机访问文件的读取和写入。自身具备读写的方法。通过skipBytes(int x),seek(int x) 来达到随机访问。

2.IO流常用基类
    字符流的抽象基类:
    Reader      , Writer 。
    字节流的抽象基类:
    InputStream , OutputStream 。
    注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。后缀名是父类名,前缀名是该流对象的功能。
    如:    InputStream 的子类 FileInputStream 。 Reader 的子类 FileReader.

3.IO程序的书写
    导入IO包中的类
    进行IO异常处理
    在finally中对流进行关闭


####################################################################################
二 字符流
####################################################################################

1.FileReader 和 FileWriter
    专门用于操作文件的 Reader 和 Writer 子类对象
    
1.1字符流——创建文件

(1)方法
FileWriter fw = new FileWriter("demo.txt");
    创建一个FileWriter对象。该对象一被初始化就必须要明确被操作的文件。而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖。其实该步就是在明确数据要存放的目的地。
FileWriter fw1 = new FileWriter("demo.txt",true);
    创建一个FileWriter对象。传递一个true参数,代表不覆盖已有的文件。并在已有文件的末尾处进行数据续写(如果没有该文件则会创建一个)。
fw.write("nihao\r\nxiexie");
    调用write方法,将字符串写入到流中。在windows里,\n记事本不识别,会显示一个实心黑框,可以用\r\n来实现
fw.flush();
    刷新流对象中的缓冲中的数据。将数据刷到目的地中。
fw.close();
fw1.close();
    关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据。将数据刷到目的地中。

close和flush区别:
    flush刷新后,流可以继续使用,close刷新后,会将流关闭。

1.2 字符流——读取文件

1.2.1方式一:普通读取
FileReader fr = new FileReader("demo.txt");
    创建一个文件读取流对象,和指定名称的文件相关联。要保证该文件是已经存在的,如果不存在,会发生异常 FileNotFoundException
int ch = 0;
while((ch=fr.read())!=-1)
{
    System.out.println((char)ch);
}
fr.close();
    调用读取流对象的read方法。read()一次读一个字符。而且会自动往下读。返回的是该字符对应的整数,如果到达流的末尾则返回-1.

1.2.2方式二:通过字符数组进行读取。
FileReader fr = new FileReader("Test.txt");
    建立一个流对象,将已存在的一个文件加载进流。
char[] ch = new char[1024];
    创建一个临时存放数据的数组。
int num = 0;
while((num=fr.read(ch))!=-1)
//read(char[])返回的是读到字符个数。如果数据没有超过数组长度if也可以,但如果超出了数组长度就要用while,所以使用while
{
    System.out.println(new String(ch,0,num));
}
fr.close();
    调用流对象的读取方法将流中的数据读入到数组中。返回的是读取到的字符数。如果到达流的末尾则返回-1.

1.3 IO异常的处理方式

1.3.1 对于创建文件

 1         FileWriter fw = null;
 2         try
 3         {
 4             fw = new FileWriter("Test.txt");
 5             fw.write("text");
 6         }
 7         catch (IOException e)
 8         {
 9             System.out.println(e.toString());
10         }
11         finally
12         {
13             If(fw!=null)
14             try
15             {
16                 fw.close();
17             }
18             catch (IOException e)
19             {
20                 System.out.println(e.toString());
21             }            
22         }

 



1.3.2 对于读取文件

 1         FileReader fr = null;
 2         try
 3         {
 4             fr = new FileReader("c:\\test.txt");
 5             char[] buf = new char[1024];
 6             int len= 0;
 7             while((len=fr.read(buf))!=-1)
 8             {
 9                 System.out.println(new String(buf,0,len));
10             }
11         }
12         catch (IOException e){
13             System.out.println("read-Exception :"+e.toString());
14         }
15         finally
16         {
17             if(fr!=null)
18             {
19                 try
20                 {
21                     fr.close();
22                 }
23                 catch (IOException e)
24                 {
25                     System.out.println("close-Exception :"+e.toString());
26                 }
27             }
28         }

 



    注意:
        (1)定义文件路径时,可以用"/"或者"\\"。
        (2)在读取文件时,必须保证该文件已存在,否则会抛出异常。

2.字符流缓冲区 BufferedWriter,BufferedReader
    缓冲区的出现提高了对数据的读写效率。缓冲区是为了提高流的操作效率而出现的。所以在创建缓冲区之前,必须要先有流对象。

2.1 BufferedWriter 字符写入流缓冲区
    该缓冲区中提供了一个跨平台的换行符。newLine();

2.2 BufferedReader 字符读取流缓冲区:
    该缓冲区提供了一个一次读一行的方法 readLine(),方便于对文本数据的获取。当返回null时,表示读到文件末尾。readLine方法返回的时候只返回回车符之前的数据内容。并不返回回车符。
readLine()方法的原理:
    无论是读一行,还是读取多个字符,其实最终都是在硬盘上一个一个读取。所以最终还是使用一次读一个的read()方法。

◆◆◆◆◆【3.装饰设计模式】◆◆◆◆◆
缓冲区要结合流才可以使用。在流的基础上对流的功能进行了增强。当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,并提供加强功能。那么自定义的该类称为装饰类。装饰类通常会通过构造方法接收被装饰的对象。并基于被装饰的对象的功能,提供更强的功能。通常情况下,装饰类和被装饰类都会同属于一个接口或者类。是同一体系中的成员。

3.1 装饰类示例

 1 class Person
 2 {
 3     public void chifan()
 4     {
 5         System.out.println("吃饭");
 6     }
 7 }
 8 class SuperPerson
 9 {
10     private Person p ;
11     SuperPerson(Person p)
12     {
13         this.p = p;
14     }
15     public void superChifan()
16     {
17         System.out.println("开胃酒");
18         p.chifan();
19         System.out.println("甜点");
20         System.out.println("来一根");
21     }
22 }
23 class  PersonDemo
24 {
25     public static void main(String[] args)
26     {
27         Person p = new Person();
28         //p.chifan();
29         SuperPerson sp = new SuperPerson(p);
30         sp.superChifan();
31     }
32 }


3.2 装饰和继承的区别:
以前是通过继承让每一个子类都具备缓冲功能。那么做继承体系会很复杂,并不利于扩展。现在优化思想。单独描述一下缓冲内容。将需要被缓冲的对象传递进来。也就是,谁需要被缓冲,谁就作为参数传递给缓冲区。这样继承体系就变得很简单。优化了体系结构。装饰模式比继承要灵活。避免了继承体系臃肿。而且降低了类与类之间的关系。装饰类因为是增强已有对象,它具备的功能和已有的是相同的,只不过提供了更强功
能。所以装饰类和被装饰类通常是都属于一个体系中的。

3.3 LineNumberReader
BufferedReader 的子类,跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int lineNumber) 和 getLineNumber(),它们可分别用于设置和获取当前行号。默认情况下,行编号从 0 开始。    

练习
对当前路径下的一个文本文件进行复制。
复制的原理:其实就是将一个文件中数据存储到另一个文件中。
步骤:
(1)创建一个文件。用于存储源文件中的数据。
(2)定义读取流和源文件关联。
(3)通过不断的读写完成数据存储。
(4)关闭资源。

 1 import java.io.*;
 2 class CopyText
 3 {
 4     public static void main(String[] args) throws IOException
 5     {
 6         copy_1();
 7         copy_2();
 8     }
 9 //方法一。从源文件读一个字符,就往目的文件写一个字符。
10     public static void copy_1()throws IOException
11     {
12         //创建目的地。
13         FileWriter fw = new FileWriter("RuntimeDemo_copy1.txt");
14         //与已有文件关联。
15         FileReader fr = new FileReader("RuntimeDemo.java");
16         int ch = 0;
17         while((ch=fr.read())!=-1)
18         {
19             fw.write(ch);
20         }
21         fw.close();
22         fr.close();
23     }
24 //方法二:使用数组作为缓冲
25     public static void copy_2()
26     {
27         FileWriter fw = null;
28         FileReader fr = null;
29         try
30         {
31             fw = new FileWriter("SystemDemo_copy2.txt");
32             fr = new FileReader("SystemDemo.java");
33 
34     //定义一个缓冲数组
35             char[] buf = new char[1024];
36             int len = 0;
37             while((len=fr.read(buf))!=-1)
38             {
39                 fw.write(buf,0,len);
40             }
41         }
42         catch (IOException e)
43         {
44             throw new RuntimeException("读写失败");
45         }
46         finally
47         {
48             if(fr!=null)
49                 try
50                 {
51                     fr.close();
52                 }
53                 catch (IOException e)
54                 {
55                     System.out.println("读取流关闭失败");
56                 }
57             if(fw!=null)
58                 try
59                 {
60                     fw.close();
61                 }
62                 catch (IOException e)
63                 {
64                     System.out.println("写入流关闭失败");
65                 }
66         }
67     }
68 }

 



模拟BufferedReader

 1 import java.io.*;
 2 class MyBufferedReader extends Reader
 3 {
 4     private Reader r;
 5     MyBufferedReader(Reader r)
 6     {
 7         this.r = r;
 8     }
 9     //可以一次读一行数据的方法。
10     public String myReadLine()throws IOException
11     //该异常应该抛不应try,因为这是一个功能,为了被其它使用者调用,出现的问题也应该由调用者处理
12     {
13         //定义一个临时容器。BufferReader封装的是字符数组。为了演示方便。定义一个StringBuilder容器。因为最终要将数据变成字符串。
14         StringBuilder sb = new StringBuilder();
15         int ch = 0;
16         while((ch=r.read())!=-1)
17         {
18             if(ch=='\r')
19                 continue;
20             if(ch=='\n')
21                 return sb.toString();
22             else
23                 sb.append((char)ch);
24         }
25         if(sb.length()!=0)
26             return sb.toString();
27         return null;        
28     }
29     //覆盖Reader类中的抽象方法。
30     public int read(char[] cbuf, int off, int len) throws IOException
31     {
32         return r.read(cbuf,off,len) ;
33     }
34     public void close()throws IOException
35     {
36         r.close();
37     }
38     public void myClose()throws IOException
39     {
40         r.close();
41     }
42 }
43 class  MyBufferedReaderDemo
44 {
45     public static void main(String[] args) throws IOException
46     //该异常应该try而不应该抛,因为是自己在调用另外的方法,如果出现问题应该自己解决。
47     {
48         FileReader fr = new FileReader("buf.txt");
49         MyBufferedReader myBuf = new MyBufferedReader(fr);
50         String line = null;
51         while((line=myBuf.myReadLine())!=null)
52         {
53             System.out.println(line);
54         }
55         myBuf.myClose();
56     }
57 }

 


####################################################################################
三 字节流 InputStream  OutputStream
####################################################################################

1.常见操作
(1)基本操作:与字符流类相同

 1 import java.io.*;
 2 class  FileStream
 3 {
 4     public static void main(String[] args) throws IOException
 5     {
 6         //readFile_1();
 7         //readFile_2();
 8         readFile_3();
 9     }
10 //方法一
11     public static void readFile_1()throws IOException
12     {
13         FileInputStream fis = new FileInputStream("fos.txt");
14         int ch = 0;
15         while((ch=fis.read())!=-1)
16         {
17             System.out.println((char)ch);
18         }
19         fis.close();
20     }
21 //方法二,以该方法为主
22     public static void readFile_2()throws IOException
23     {
24         FileInputStream fis = new FileInputStream("fos.txt");
25         byte[] buf = new byte[1024];
26         int len = 0;
27         while((len=fis.read(buf))!=-1)
28         {
29             System.out.println(new String(buf,0,len));
30         }
31         fis.close();
32     }
33 //方法三
34     public static void readFile_3()throws IOException
35     {
36         FileInputStream fis = new FileInputStream("fos.txt");
37         //int num = fis.available();
38         byte[] buf = new byte[fis.available()];
39         //定义一个刚刚好的缓冲区。不用再循环了。但使用需谨慎,当数据较大时有可能发生内存溢出
40         fis.read(buf);
41         System.out.println(new String(buf));
42         fis.close();
43     }
44     public static void writeFile()throws IOException
45     {
46         FileOutputStream fos = new FileOutputStream("fos.txt");
47         fos.write("abcde".getBytes());//先将字符串转为字符数组,再将字符数组写入到文件中
48         fos.close();
49     }
50 }


(2)特殊操作:它不仅可以操作字符,还可以操作其他媒体文件

2.字节流缓冲区
    同样是提高了字节流的读写效率。

字节流读一个字节的read方法为什么返回值类型不是byte,而是int?
因为有可能会读到连续8个二进制1的情况,8个二进制1对应的十进制是-1.那么就会数据还没有读完,就结束的情况。因为我们判断读取结束是通过结尾标记-1来确定的。所以,为了避免这种情况将读到的字节进行int类型的提升。并在保留原字节数据的情况前面了补了24个0,变成了int类型的数值。而在写入数据时,write方法其实在做强转动作,只写该int类型数据的最低8位。所以,read方法在做提升,write方法在做强转。可以保证原数据不变化。

练习:
通过几种方式对MP3的进行拷贝,比较它们的效率。模拟一个BufferedInputStream
//方法一。通过字节流的缓冲区完成复制。

 1 import java.io.*;
 2 class  CopyMp3
 3 {
 4     public static void main(String[] args) throws IOException
 5     {
 6         long start = System.currentTimeMillis();
 7         copy_1();
 8         long end = System.currentTimeMillis();
 9         System.out.println((end-start)+"毫秒");
10     }
11     public static void copy_1()throws IOException
12     {
13         BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("c:\\0.mp3"));
14         BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\1.mp3"));
15         int by = 0;
16         while((by=bufis.read())!=-1)
17         {
18             bufos.write(by);
19         }
20         bufos.close();
21         bufis.close();
22     }
23 }

 


//方法二。通过自定义缓冲区

 1 import java.io.*;
 2 class MyBufferedInputStream
 3 {
 4     private InputStream in;
 5     private byte[] buf = new byte[1024*4];
 6     private int pos = 0,count = 0;
 7     MyBufferedInputStream(InputStream in)
 8     {
 9         this.in = in;
10     }
11     //一次读一个字节,从缓冲区(字节数组)获取。
12     public int myRead()throws IOException
13     {
14         //通过in对象读取硬盘上数据,并存储buf中。
15             if(count==0){
16             count=in.read(buf);
17             if(count<0)
18                 return -1;
19             pos=0;
20         }
21         byte b=buf[pos];
22         pos++;
23         count--;
24         return b&255;//将b提升为int型并在前面补0
25     }
26     public void myClose()throws IOException
27     {
28         in.close();
29     }
30 }
31 class  CopyMp3
32 {
33     public static void main(String[] args) throws IOException
34     {
35         long start = System.currentTimeMillis();
36         copy_2();
37         long end = System.currentTimeMillis();
38 
39         System.out.println((end-start)+"毫秒");
40     }
41     public static void copy_2()throws IOException
42     {
43         MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream("c:\\9.mp3"));
44         BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\3.mp3"));
45         int by = 0;
46         //System.out.println("第一个字节:"+bufis.myRead());
47         while((by=bufis.myRead())!=-1)
48         {
49             bufos.write(by);
50         }
51         bufos.close();
52         bufis.myClose();
53     }
54 }

 


####################################################################################
四 转换流 InputStreamReader,OutputStreamWriter
####################################################################################

1.读取键盘录入
    System 类中的字段:in,out。它们各代表了系统标准的输入和输出设备。默认输入设备是键盘,输出设备是显示器。
    System.in的类型是 InputStream.对应的标准输入设备:键盘。
    System.out的类型是 PrintStream,它是 OutputStream 的子类 FilterOutputStream 的子类,对应的是标准输出设备:控制台。

2. InputStreamReader 和 OutputStreamWriter
通过键盘录入一行数据并打印其大写,发现其实就是读一行数据的原理。也就是readLine方法。能不能直接使用readLine方法来完成键盘录入的一行数据的读取呢?readLine方法是字符流BufferedReader类中的方法。而键盘录入的read方法是字节流InputStream的方法。那么能不能将字节流转成字符流再使用字符流缓冲区的readLine方法呢?
Reader 的一个子类 InputStreamReader :字节转字符,字节流通向字符流的桥梁。将字节解码为字符。专门用于操作字节流的字符流对象。
Writer 的一个子类 OutputStreamWriter :字符转字节,字符流通向字节流的桥梁。将字符编码成字节。
字节流转换为字符流 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));(键盘录入最常见写法)
字符流转换为字节流 BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

注意:
转换流的由来是因为它可以指定编码表;它是字符流与字节流之间的桥梁,方便了字符流与字节流之间的操作。
转换流的应用:字节流处理的是文本数据,即字节流中的数据都是字符时,转成字符流操作更高效。

3.流操作的基本规律
最痛苦的就是流对象有很多,不知道该用哪一个。通过三个明确来完成。

(1)明确源和目的。
      源:输入流 InputStream   Reader
    目的:输出流 OutputStream  Writer

(2)操作的数据是否是纯文本。
      是:字符流。
    不是:字节流。
    (1)和(2)取交集就能明确最终要使用的体系。

(3)当体系明确后,在明确要使用哪个具体的对象。通过设备来进行区分:
      源设备:内存,硬盘。键盘
    目的设备:内存,硬盘,控制台。

转换流什么时候用?
    转换流是字符和字节之间的桥梁,通常,涉及到字符编码转换时,需要用到转换流。

注意:
    通过System类的setIn,setOut方法对默认设备进行改变。
System.setIn(new FileInputStream(“1.txt”));//将源改成文件1.txt。
System.setOut(new FileOutputStream(“2.txt”));//将目的改成文件2.txt

4.流的基本应用
流是用来处理数据的。处理数据时,一定要先明确数据源,与数据目的地(数据汇)。数据源可以是文件,可以是键盘。数据目的地可以是文件、
显示器或者其他设备。而流只是在帮助数据进行传输,并对传输的数据进行处理,比如过滤处理.转换处理等。

####################################################################################
五 File类
####################################################################################

用来将文件或者文件夹封装成对象,方便对文件与文件夹的属性信息进行操作。File对象可以作为参数传递给流的构造函数。流对象也能操作文件,但不能操作文件夹和文件的属性信息。流只能操作数据,想要操作被数据封装成的文件的信息,必须使用file对象。

1.File类常见方法:
    
    (1)创建。
    boolean createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false。
    boolean mkdir() :只能创建一级文件夹。
    boolean mkdirs():可以创建多级文件夹。
    
    (2)删除。
    boolean delete():删除失败返回false。如果文件正在被使用,则删除不了返回false
    void deleteOnExit();在程序退出时删除指定文件。

    (3)修改
    boolean renameTo(File dest) 重新命名此抽象路径名表示的文件。有剪切的效果。
    
    (4)判断。
    boolean canExecute() 判断文件是否可执行
    boolean exists() :文件是否存在.用流操作对象时,如果文件存在了才能去读取,如果文件不存在流一读就会抛异常。可以先用本方法判断
    boolean isFile():是否是文件
    boolean isDirectory();是否是目录
    boolean isHidden();是否是隐藏文件
    boolean isAbsolute();是否是绝对路径
 
    (5)获取信息。
    long length() 文件大小,不能获取文件夹大小
    long lastModified() 最后一次修改的时间
    static File[] listRoots()返回当前的系统盘符
    String getName()
    String getPath()
    String getAbsolutePath()当路径是绝度路径时,path和AbsolutePath返回的是一样的,当路径是相对路径时,path返回相对路径,AbsolutePath返回的是当前路径的前面加上所属目录的绝对路径。
    String getParent()    getParent()+getName()=getPath()
    File getAbsoluteFile()    File和String 可以互换,字符串new一下可以变成对象,对象toString一下变成字符串
    File getParentFile()

    (6)其它
    String[] list() 返回指定路径下的文件和文件夹名称。包括隐藏文件。调用list方法的file对象必须是封装了一个目录。该目录还必须存在。当对象是一个文件时,返回的是null,会造成空指针异常。如果对象是一个空目录,则        返回一个长度为0的数组。
    File[] listFiles()     返回指定路径下的文件和文件夹对象。不能拿文件夹内的文件

####################################################################################
六 递归
####################################################################################
函数自己调用自己。
注意:递归时一定要明确结束条件。
应用场景:
当某一功能要重复使用时。

列出指定目录下文件和文件夹,包含子目录中的内容。也就是列出指定目录下所有内容。因为目录中还有目录,只要使用同一个列出目录功能的函数完成即可。在列出过程中出现的还是目录的话,可以再次调用本功能。也就是函数自身调用自身。这种表现形式,或者编程手法,称为递归。

递归要注意:
(1)限定条件。
(2)要注意递归的次数。尽量避免内存溢出。

 1 import java.io.*;
 2 class FileDemo3
 3 {
 4     public static void main(String[] args)
 5     {
 6         File dir = new File("d:\\testdir");
 7         showDir(dir,0);
 8         //System.out.println(dir.delete());
 9     }
10     public static String getLevel(int level)
11     {
12         StringBuilder sb = new StringBuilder();
13         sb.append("|--");
14         for(int x=0; x<level; x++)
15         {
16             //sb.append("|--");
17             sb.insert(0,"|  ");
18         }
19         return sb.toString();
20     }
21     public static void showDir(File dir,int level)
22     {
23         System.out.println(getLevel(level)+dir.getName());
24         level++;
25         File[] files = dir.listFiles();
26         for(int x=0; x<files.length; x++)
27         {
28             if(files[x].isDirectory())
29                 showDir(files[x],level);
30             else
31                 System.out.println(getLevel(level)+files[x]);
32         }
33     }
34 }


递归要有限定条件,不然会是无限死循环

    public static void method()
    {
        method();
    }

####################################################################################
七 Properties
####################################################################################

Properties 是 Hashtable 的子类。它具备 Map 集合的特点。而且它里面存储的键值对都是字符串。是集合中和IO技术相结合的集合容器。
该对象的特点:可以用于键值对形式的配置文件。那么在加载数据时,需要数据有固定格式:键=值。

public void load(InputStream inStream) throws IOException
    从输入流中读取属性列表(键和元素对)。
public void load(Reader reader) throws IOException
    按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。 JDK1.6
public void list(PrintStream out)
    将属性列表输出到指定的输出流。
public void list(PrintWriter out)
    将属性列表输出到指定的输出流。
public Object setProperty(String key, String value)
    调用 Hashtable 的方法 put。 改变的是内存中的结果,store方法是将内存中结果保存到文件中。
public String getProperty(String key)
     用指定的键在属性列表中搜索属性。
public Set<String> stringPropertyNames() JDK1.6
    返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键
public void store(OutputStream out, String comments)
    以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。
public void store(Writer writer, String comments)
    以适合使用 load(Reader) 方法的格式,将此 Properties 表中的属性列表(键和元素对)写入输出字符流。comments注释信息.限英文

练习:
限制程序运行次数。当运行次数到达5次时,给出,请您注册的提示。并不再让该程序执行。
远离:记录应用程序运行次数。如果使用次数已到,那么给出注册提示。很容易想到的是:计数器。可是该计数器定义在程序中,随着程序的运行而在内存中存在,并进行自增。可是随着该应用程序的退出,该计数器也在内存中消失了。下一次在启动该程序,又重新开始从0计数。这样不是我们想要的。程序即使结束,该计数器的值也存在。下次程序启动在会先加载该计数器的值并加1后在重新存储起来。所以要建立一个配置文件。用于记录该软件的使用次数。该配置文件使用键值对的形式。这样便于阅读数据,并操作数据。键值对数据是map集合。数据是以文件形式存储,使用io技术。那么map+io -->properties.配置文件可以实现应用程序数据的共享。

 1 import java.io.*;
 2 import java.util.*;
 3 class  RunCount
 4 {
 5     public static void main(String[] args) throws IOException
 6     {
 7         Properties prop = new Properties();
 8 
 9         File file = new File("count.ini");
10         if(!file.exists())
11             file.createNewFile();
12         
13         FileInputStream fis = new FileInputStream(file);
14 
15         prop.load(fis);
16         
17 
18         int count = 0;
19         String value = prop.getProperty("time");
20         
21         if(value!=null)
22         {
23             count = Integer.parseInt(value);
24             if(count>=5)
25             {
26                 System.out.println("您好,使用次数已到,拿钱!");
27                 return ;
28             }
29 
30         }
31 
32         count++;
33 
34         prop.setProperty("time",count+"");
35 
36         FileOutputStream fos = new FileOutputStream(file);
37 
38         prop.store(fos,"");
39 
40         fos.close();
41         fis.close();
42         
43     }
44 }

 


####################################################################################
八 IO包中的其他类
####################################################################################

打印流
PrintWriter 与 PrintStream
能直接操作文件和输出流。为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。

序列流    
SequenceInputStream
对多个流进行合并。

对象流
ObjectInputStream 与 ObjectOutputStream
可以实现对象的持久化存储。被操作的对象需要实现Serializable (标记接口);没有方法的接口也称为标记接口。

管道流
PipedInputStream 和 PipedOutputStream
输入输出可以直接进行连接,通过结合线程使用.

随机访问文件
RandomAccessFile
自身具备读写的方法。通过skipBytes(int x),seek(int x)来达到随机访问。

操作基本数据类型
DataInputStream 与 DataOutputStream
可以用于操作基本数据类型的数据的流对象。

操作字节数组
ByteArrayInputStream 与 ByteArrayOutputStream
用于操作字节数组的流对象。

操作字符数组
CharArrayReader 与 CharArrayWriter

操作字符串
StringReader 与 StringWriter
    
1.打印流:
该流提供了打印方法,可以将各种数据类型的数据都原样打印。

字节打印流: PrintStream
构造函数可以接收的参数类型:
(1)file对象。File
(2)字符串路径。String
(3)字节输出流。OutputStream
字符打印流: PrintWriter
构造函数可以接收的参数类型:
(1)file对象。File
(2)字符串路径。String
(3)字节输出流。OutputStream
(4)字符输出流,Writer。

2.序列流    
SequenceInputStream

练习:文件分割和合并程序。

 1 import java.io.*;
 2 import java.util.*;
 3 class SplitFile
 4 {
 5     public static void main(String[] args) throws IOException
 6     {
 7         //splitFile();
 8         merge();
 9     }
10     public static void merge()throws IOException
11     {
12         ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
13 
14         for(int x=1; x<=3; x++)
15         {
16             al.add(new FileInputStream("c:\\splitfiles\\"+x+".part"));
17         }
18         final Iterator<FileInputStream> it = al.iterator();
19 
20         Enumeration<FileInputStream> en = new Enumeration<FileInputStream>()
21         {
22             public boolean hasMoreElements()
23             {
24                 return it.hasNext();
25             }
26             public FileInputStream nextElement()
27             {
28                 return it.next();
29             }
30         };
31         SequenceInputStream sis = new SequenceInputStream(en);
32         FileOutputStream fos = new FileOutputStream("c:\\splitfiles\\0.bmp");
33         byte[] buf = new byte[1024];
34         int len = 0;
35         while((len=sis.read(buf))!=-1)
36         {
37             fos.write(buf,0,len);
38         }
39         fos.close();
40         sis.close();
41     }
42     public static void splitFile()throws IOException
43     {
44         FileInputStream fis =  new FileInputStream("c:\\1.bmp");
45         FileOutputStream fos = null;
46         byte[] buf = new byte[1024*1024];
47         int len = 0;
48         int count = 1;
49         while((len=fis.read(buf))!=-1)
50         {
51             fos = new FileOutputStream("c:\\splitfiles\\"+(count++)+".part");
52             fos.write(buf,0,len);
53             fos.close();
54         }
55         fis.close();
56         
57     }
58 }

 


3.RandomAccessFile
IO中能实现多线程下载的流。该类不能算是IO体系中子类。而是直接继承自Object。但是它是IO包中成员。因为它具备读和写功能。内部封装了一个数组,而且通过指针对数组的元素进行操作。可以通过getFilePointer获取指针位置,同时可以通过seek改变指针的位置。其实完成读写的原理就是内部封装了字节输入流和输出流。通过构造函数可以看出,该类只能操作文件。而且操作文件还有模式:只读r,读写rw等。如果模式为只读 r。不会创建文件。会去读取一个已存在文件,如果该文件不存在,则会出现异常。如果模式为读写 rw。操作的文件不存在,会自动创建。如果存在不会覆盖。自身具备读写的方法。通过skipBytes(int x),seek(int x)来达到随机访问。


    RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");
    
    //跳过指定的字节数,只能向下跳,不能向回跳
    raf.skipBytes(8);
    
    //调整对象中指针。爱指哪指哪。任意读
    raf.seek(8*1);
    byte[] buf = new byte[4];
    raf.read(buf);
    
    //调整对象中指针。爱指哪指哪。任意写
    raf.seek(8*0);//此时覆盖0-8角标上的元素
    raf.write("张三".getBytes());
    
    raf.seek(8*3);//此时写在8*3-8*4角标上,而且前面可以有空(即8*2-8*3可以是没有数据的)
    raf.write("周七".getBytes());

因为名字由两个字节组成,年龄写成int型也由两个字节。对应的字节数组中每8个表示一个个人信息。所以8*x可以方便使用。8不是固定的,对于中国人姓名可以定义16个字节(最高8个汉字)和int来存储,共20个。20*x

4.ByteArrayInputStream 与 ByteArrayOutputStream
ByteArrayInputStream :在构造的时候,需要接收数据源,而且数据源是一个字节数组。
ByteArrayOutputStream :在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。这就是数据目的地。
因为这两个流对象都操作的数组,并没有使用系统资源。所以,不用进行close关闭。
在流操作规律讲解时:
源设备,
    键盘 System.in,硬盘 FileStream,内存 ArrayStream。
目的设备:
    控制台 System.out,硬盘FileStream,内存 ArrayStream。
用流的读写思想来操作数组数据。
    //数据源。
        ByteArrayInputStream bis = new ByteArrayInputStream("ABCDEFD".getBytes());
    //数据目的
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
    writeTo() 只有这个方法会抛出IO异常

####################################################################################
九 字符编码
####################################################################################

字符流的出现是为了方便操作字符。更重要是的加入了编码转换。通过子类转换流来完成。 InputStreamReader OutputStreamWriter 在两个对象进行构造的时候可以加入字符集

1.编码表的由来
计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。这就是编码表。

2.常见的编码表
ASCII: 美国标准信息交换码。用一个字节的7位可以表示。
ISO8859-1:拉丁码表,欧洲码表。用一个字节的8位表示。
GB2312:中国的中文编码表,用两个字节表示。GBK:中国的中文编码表升级,融合了更多的中文文字符号。
Unicode:国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java语言使用的就是unicode
UTF-8:最多用三个字节表示一个字符。一个字节可以的用一个字节,一个字节无法表示的用两个字节,两个字节无法表示的用3个字节,一个字节0开头,两个字节分别用110和10开头,三个字节分别用1110、10和10开头。

3.转换流的编码应用
可以将字符以指定编码格式存储。可以对文本数据指定编码格式来解读。指定编码表的动作由构造函数完成。

将“你好”两个字符查指定的utf-8的码表,获取对应的数字,并写入到text.txt文件中。
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("text.txt"),"utf-8");
osw.write("你好");
osw.close();
读取硬盘上的文件数据,将获取到的数据查指定utf-8的码表来解析该数据。
InputStreamReader isr = new InputStreamReader(new FileInputStream("text.txt"),"utf-8");
char[] buf = new char[10];
int num = isr.read(buf);
String s = new String(buf,0,num);
System.out.println(s);
传入编码表的方法都会抛出不支持编码异常(UnsupportedEncodingException);

4.字符编码
编码:字符串变成字节数组。
解码:字节数组变成字符串。
String-->byte[]: str.getBytes(charsetName);
byte[]-->String: new String(byte[],charsetName);

举例:
将一个中文用gbk编码,用iso8859-1解码,乱码,
解决方式:对乱码进行iso8859-1编码,再用gbk解码即可。
应用:TomCat服务器就是使用的iso8859-1编码表。因为如果使用GBK或者utf-8的话,就只能支持使用对应码表的应用程序,使用另外一个编码表发送的数据会解码为乱码且无法通过再编码和解码还原。

问题:
用gbk编码,用utf-8解码,乱码,可是对乱码进行utf-8编码后,再用gbk解码,还是乱码。这是因为用gbk编码后的数字去查utf-8的码表时u8不识别,就直接用一个未知字符来表示,而这个未知字符是3个字节的。所以原数字码已经变化了,所以还原不回来了。如果解错了,可以更换一个码表再解。

posted @ 2013-10-13 22:42  赵晨光  阅读(271)  评论(0编辑  收藏  举报
---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ------- --------------- 详细请查看:http://edu.csdn.net