黑马程序员——IO(二)--流包装类、字符编码、Properties

来源:互联网 发布:清华大学软件工程硕士 编辑:程序博客网 时间:2024/06/05 17:58

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

第一部分 流包装类

一.包装类的概念与作用

通过FileOutputStream对象将一个浮点小数写入到文件中,你感觉有点困难吧?能否通过FileOutputStream对象直接将一个整数写入到文件呢?

假如有个DataOutputStream类提供了往各种输出流对象中写入各种类型的数据的方法。你现在需要做的工作就是:传递一个FileOutputStream对象给DataOutputStream对象和调用DataOutputStream对象的用于写入浮点小数的方法。

DataOutputStream对象并没有对应到任何具体的流设备,一定要给他传递一个对应具体流设备的输出流对象。完成类似DataOutputStream功能的类就是一个包装类,也叫过滤流类或处理流类。

 

二.过滤流基类

1.FilterInputStreamFilterOutputStream

FilterInputStreamFilterOutputStream是很多字节流包装类的基类,如果想实现自己的字节流包装类应该继承这两个类。

2.FilterReaderFilterWriter

FilterReaderFilterWriter是很多字符流包装类的抽象基类,如果想实现自己的字符流包装类应该继承这两个类。

三.缓冲区流

1.BufferedInputStreamBufferedOutputStream

1>字节缓冲区流为IO流增加了内存缓冲区,增加缓冲区有两个基本目的:

--允许Java程序一次不止操作一个字节,这样提高了程序的性能。

--由于有了缓冲区,是的流上执行skipmarkreset方法都成为可能。

2>BufferedInputStreamBufferedOutputStreamJava提供的两个缓冲区包装类,不管底层系统是否使用了缓冲区,这两个类在自己的实例对象中都创建缓冲区。

3>这种缓冲区与底层系统提供的缓冲区的区别:

底层系统缓冲区直接与设备相连接,而BufferedInputStreamBufferedOutputStream对象的缓冲区通过调用节点流对象与底层系统缓冲区或设备进行数据读写。

底层缓冲区虽然可以一次读写大量数据,但它无法对字节的内容进行判断处理。而BufferedInputStreamBufferedOutputStream对象则是一个字节一个字节的和底层缓冲区进行读写,边读写边处理。

2.BufferedReaderBufferedWriter

1>BufferedReaderBufferedWriter实现了上面两个类的字符流的输入输出。

缓冲区的大小可以使用默认的大小,也可以指定大小。在创建对象时确定。

2>BufferedWriter类提供了 newLine() 方法,它使用平台自己的行分隔符概念,此概念由系统属性 line.separator 定义。

3>BufferedReaderread方法从缓冲区中读取字符数据。它覆盖了父类中的read方法。

4>BufferedReaderreadLine方法会创建一个临时容器,使用read方法读取字符,当读取到行分隔符后不再读取并且不把行分隔符写入临时容器中,让后把容器中的内容作为字符串返回。这个临时容器可以是StringBuilder

5>关闭缓冲区流其实关闭的就是它所包装的流对象。

3.使用举例:

1>使用缓冲区流向文件写入多行字符串:

FileWriter fw=new FileWriter("buf.txt");BufferedWriter bw=new BufferedWriter(fw);bw.write("abcde");bw.newLine();//写入一个与系统相关的换行符bw.write("ABCDE");bw.newLine();bw.flush();//把缓冲区中的内容刷出到设备bw.close();

2>使用缓冲区流从文件中读取多行字符串:

FileReader fr=new FileReader("buf.txt");BufferedReader br=new BufferedReader(fr);String line=null;while((line=br.readLine())!=null){System.out.println(line);}br.close();

3>使用缓冲区流按行复制文本文件:

FileReader fr=new FileReader("");FileWriter fw=new FileWriter("");BufferedReader br=new BufferedReader(fr);BufferedWriter bw=new BufferedWriter(fw);String line=null;while((line=br.readLine())!=null){bw.write(line);bw.newLine();bw.flush();}br.close();bw.close();


4.实现自己的字符缓冲区输入流

分析:

缓冲区中元素就是封装了一个数组,

并对外提供了更多的方法对数组进行访问。

其实这些方法最终操作的都是数组的索引。

缓冲的原理:

其实就是从源中读取一批数据装进缓冲区中,

再从缓冲区中取出一个一个的数据。

在此次取完后,再从源中继续取一批数据进缓冲区。

当源中的数据取光后,用-1作为结束标记。

public class MyBufferedReader {private FileReader fr;//定义一个数组作为缓冲区private char[] buf = new char[1024];//定义一个指针用于操作数组中的元素,当操作到最后一个元素后指针应该归零private int pos = 0;//定义一个计数器哟与记录缓冲区中的数据个数。当该数据减到0,就从源中继续获取数据到缓冲区private int count = 0;public MyBufferedReader(FileReader fr) {this.fr = fr;}public int myRead() throws IOException {if(count==0){count=fr.read(buf);pos=0;}if(count<0)return -1;char ch=buf[pos++];count--;return ch;}public String myReadLine() throws IOException {StringBuilder sb=new StringBuilder();int ch=0;while((ch=myRead())!=-1){if(ch=='\r')continue;if(ch=='\r')return sb.toString();//将从缓冲区中读取到的数组存储到缓存行数据的缓冲区中。sb.append(ch);}if(sb.length()!=0)return sb.toString();return null;}public void myClose() throws IOException{fr.close();}}


5.装饰设计模式:对一组功能进行增强时,就可以使用该模式进行问题解决。

装饰设计模式的特点:装饰类和被装饰类都必须所属于同一个接口或者父类。

装饰和继承都能实现一样的特点:让功能进行扩展。

装饰和继承的区别:继承会导致集成体系的越来越臃肿,不够灵活。

相对而言,继承足够灵活。

四.基本数据类型流

1.DataOutputStream

DataOutputStream类提供了三个写入字符串的方法:

void writeBytes(String s);//将字符串中的每个字符的低字节字符写入到设备中,丢弃高字节。 

void writeChars(String s);//将字符串中的每个字符的两个字节写入到设备中。

void writeUTF(String str);//UTF-8编码的方式将字符串写入到设备中,且数据开头有以UTF-8编码的头指示字符串长度。

2.DataInputStream

DataInputStream类提供了一个读取字符串的方法:

String readUTF();//DataOutputStreamwriteUTF方法配合使用。

为什么DataOutputStream有三个写入字符串的方法,而DataInputStream类只有一个读取字符串的方法?

在一个连续的流中读取字符串,如果没有特殊标记作为结尾或在开始部分说明字符串长度,就无法知道字符串结束的位置。而写入的三个方法中只有writeUTF方法指定了字符串长度,其他方法没有所以无法解析。

五.打印流(PrintStreamPrintWriter)

1.PrintStream

1>提供了多种方法可以对多种数据类型的值进行打印。并保持数据的表示形式(将数据变为字符串形式)。

2>会自动刷新,若不想自动刷新可指定字符串参数autoFlush为false.

3>不会抛出IOException

4>构造方法接收三种类型的值:

文件字符串路径;File对象;字节输出流。

5>println方法和print方法不同之处在于,会在输出完数据后加上换行符。

2.PrintWriter

1>它的方法每次输出完数据之后默认不自动刷新,若想自动刷新就要在构造方法中指定autoFlush值为true.这时println、printf 或 format 方法将自动刷新。

2>构造方法接收三种类型的值:

文件字符串路径;File对象;字节输出流;字符输出流。

六.对象类型流

1.ObjectInputStreamObjectOutputStream这两个包装类,用于从底层输入流读取对象类型的数据和将对象类型的数据写入到底层输出流。

2.ObjectInputStreamObjectOutputStream类所读写的对象必须实现了Serializable接口。对象中的transient类型和静态的成员变量不会被读取可写入。(transient是定义一个瞬态变量)

3.通过ObjectOutputStreamwriteObject方法向流中写入对象,通过ObjectInputStreamread

4.通常存放对象的文件以.object作为后缀。

5.Serializable接口用于给被序列化的类加入ID号,这个ID号用与判断类和对象是否是同一版本。serialVersionUID就是用来存放ID号的,如果它未被显式声明,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值。强烈建议所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,有可能相同的类不同的编译器会计算成不同值,从而在反序列化过程中出现意外的InvalidClassException。如果显式声明了即使内容有变化也可以进行类型转换。

6.当某属性属于对象,但我们又不想把它序列化化时,就把它定义为transient,即瞬态的。

七.转换流

1.InputStreamReaderOutputStreamWriter,是用于字节流和字符流之间转换的两个类,InputStreamReader可以将字节流中的字节解码成字符后读取,OutputStreamWriter将字符编码成字节后写入到一个字节流中。

2.在创建转换流对象时,可以使用默认字符编码,也可以通过给构造方法传入指定字符集字符串、字符集对象或字符集编码器对象来使用指定字符集。

3.避免频繁地在字符与字节之间进行转换。最好不要直接使用InputStreamReaderOutputStreamWriter类来读写数据,尽量使用BufferedWriter类包装OutputStreamWriter类,用BufferedReader类包装InputStreamReader.

4.使用举例:

1>将键盘输入的信息转换为大写按行输出到屏幕:

InputStream in=System.in;OutputStream out=System.out;//使用输入转换流将键盘输入的字节流转换为字符流InputStreamReader isr=new InputStreamReader(in);//使用输出转换流将字符流转换为字节流输出到屏幕OutputStreamWriter osr=new OutputStreamWriter(out);BufferedReader br=new BufferedReader(isr);BufferedWriter bw=new  BufferedWriter(osr);String line=null;while(!"over".equalsIgnoreCase(line=br.readLine())){bw.write(line.toUpperCase());bw.newLine();bw.flush();}br.close();bw.close();

2>将键盘输入的数据写入到文件中:

把上面代码的:

                       OutputStream out=System.out;

                改为:

                      OutputStream out=new FileOutputStream("a.txt");

3>将文件中的字符变为大写显示在屏幕上:

               把上面代码的:

                       InputStream in=System.in;

               改为:

                       InputStream in=new FileInputStream("a.txt");

4>将一个文件中的字符变为大写复制到另一个文件中:

              把上面代码的:

                         InputStream in=System.in;

                         OutputStream out=System.out;

             改为:

                       InputStream in=new FileInputStream("filetest.txt");

                       OutputStream out=new FileOutputStream("a.txt");

八.合并流(SequenceInputStream

1.SequenceInputStream用于将多个输入流合并成一个输入流。

2.简单使用案例:将多个文件合并到一个文件中,

方式一:使用Vector存储输入流对象

FileInputStream fis1=new FileInputStream("d:/1.txt");FileInputStream fis2=new FileInputStream("d:/2.txt");FileInputStream fis3=new FileInputStream("d:/3.txt");FileOutputStream fos=new FileOutputStream("d:/tofile.txt");//定义一个向量来存放所有输入流对象Vector<InputStream> vector=new Vector<InputStream>();vector.add(fis1);vector.add(fis2);vector.add(fis3);//获取向量的枚举Enumeration<InputStream> em=vector.elements();SequenceInputStream sis=new SequenceInputStream(em);byte[] buf=new byte[1024];int len=0;while((len=sis.read(buf))!=-1){fos.write(buf, 0, len);}sis.close();fos.close();

方式二:使用ArrayList存储输入流对象,

FileInputStream fis1=new FileInputStream("d:/1.txt");FileInputStream fis2=new FileInputStream("d:/2.txt");FileInputStream fis3=new FileInputStream("d:/3.txt");FileOutputStream fos=new FileOutputStream("d:/tofile.txt");//定义一个ArrayList集合来存放输入流对象ArrayList<InputStream> al=new ArrayList<InputStream>();al.add(fis1);al.add(fis2);al.add(fis3);//通过集合工具类获取集合的枚举Enumeration<InputStream> em=Collections.enumeration(al);SequenceInputStream sis=new SequenceInputStream(em);byte[] buf=new byte[1024];int len=0;while((len=sis.read(buf))!=-1){fos.write(buf, 0, len);}sis.close();fos.close();

3.文件切割:将一个文件按大小切割成多个文件碎片,

//将文件切割成指定大小的碎片文件到dir指定的目录下void splitfile(File file,File dir) throws IOException{if(!dir.exists())dir.mkdirs();FileInputStream fis=new FileInputStream(file);FileOutputStream fos=null;//将原文件切割成1M的碎片文件byte[] buf=new byte[1024*1024];int len=0;int count=1;while((len=fis.read(buf))!=-1){fos=new FileOutputStream(new File(dir, (count++)+".part"));fos.write(buf, 0, len);fos.close();}fis.close();}

4.合并文件:将上面程序分割的文件合并成一个文件,

void mergeFile(File dir) throws IOException{ArrayList<InputStream> al=new ArrayList<InputStream>();for(int i=1;i<=12;i++){al.add(new FileInputStream(new File(dir,i+".part")));}Enumeration<InputStream> em=Collections.enumeration(al);SequenceInputStream sis=new SequenceInputStream(em);FileOutputStream fos=new FileOutputStream(new File(dir,"11.mp3"));byte[] buf=new byte[1024];int len=0;while((len=sis.read(buf))!=-1){fos.write(buf, 0, len);}sis.close();fos.close();}

九.行号流

1.LineNumberInputStream

行是以回车符 ('\r')、换行符 ('\n') 或回车符后面紧跟换行符结尾的字节序列。在所有这三种情况下,都以单个换行符形式返回行终止字符。 

行号以 开头,并在 read 返回换行符时递增 1。 

此类已过时,建议使用LineNumberReader

2.LineNumberReader

1>跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。 

2>默认情况下,行编号从 开始。该行号随数据读取在每个行结束符处递增,并且可以通过调用 setLineNumber(int) 更改行号。但要注意的是,setLineNumber(int) 不会实际更改流中的当前位置;它只更改将由 getLineNumber() 返回的值。 

3.使用示例:

FileReader fr=new FileReader("d:/tofile.txt");LineNumberReader lnr=new LineNumberReader(fr);String line=null;lnr.setLineNumber(20);//设置初始行号为21while((line=lnr.readLine())!=null){System.out.println(lnr.getLineNumber()+":"+line);}lnr.close();

十.回退流

1.PushbackInputStream

1>介绍

    PushbackInputStream类继承了FilterInputStream类是iputStream类的修饰者。提供可以将数据插入到输入流前端的能力。能够插入的最大字节数与推回缓冲区的大小相关。

    Pushback用于输入流允许字节被读取然后返回(即“推回”)到流。PushbackInputStream类实现了这个想法。它提供了一种机制来“窥视”在没有受到破坏的情况下输入流生成了什么

2>属性

    protected byte[] buf;用于保存插入到输入流前端的数据,读取时先从缓存区读取。

    protected int pos;表示缓存区当前读取的位置。

3>构造函数

    public PushbackInputStream(InputStream in, int size) ;in为被修饰的输入流。size为推回缓冲区的大小。默认缓冲区大小为1字节。

4>方法

    public int read() throws IOException;推回缓冲区中包含数据,则从推回缓冲区读取,否则从输入流中读取。

    public int read(byte[] b, int off, int len) throws IOException推回缓冲区中包含数据,则从推回缓冲区读取,然后从输入来看中读取。

    public void unread(int b);将数据插入到推回缓冲区中。int类型将被转换为byte类型。

    public void unread(byte[] b, int off, int len) throws IOException;将字节数组数据插入到缓冲区中。

    public int available() throws IOException;返回推回缓冲区和输入流的可用字节数。

    public long skip(long n) throws IOException;先跳过推回缓冲区再跳过输入流。

2.PushbackReader

java.io.PushbackReader与前面提到的PushbackInputStream类似,都拥有一个PushBack缓冲区,只不过PushbackReader所处理的是字符。

从这个对象读出数据后,如果愿意的话,只要PushBack缓冲区没有满,就可以使用unread()将数据推回流的前端。

第二部分 字符编码

一.字符编码

1.计算机里只有数字,计算机软件里的一切都是用数字来表示的,屏幕上显示的一个个字符也不例外。

2.字符a对应97,字符b对应数字98等,这种字符与数字对应的编码规则被称为ASCII(美国标准信息交换码)。ASCII的最高bit位都为0,也就是说这些数字都在0127之间。

3.中国大陆将每一个中文字符都用两个字节的数字来表示,中文字符的每个字节的最高bit位都为1,中国大陆为每个中文字符制定的编码规则称为GB2312(国标码)。

4.在GB2312的基础上,对更多的中文字符(包括繁体)进行了编码,新的编码规则称为GBK(国标扩展码)。

5.“中国”的“中”字,在中国大陆的编码是十六进制的D6D0,而在中国台湾的编码是十六进制的A4A4,台湾地区对中文字符集的编码规则称为BIG5(大五码)。

6.在一个国家的本地化系统中出现的一个字符,通过电子邮件传送到另外一个国家的本地化系统中,看到的就不是那个原始字符了,而是另外那个国家的一个字符或乱码。

二.Unicode编码

1.ISO(国际标准化组织)将全世界所有的字符进行了同一编码,称之为Unicode编码。

2.“中”这个字符,在全世界的任何一个角落始终对应的都是十六进制的数字4E2D

3.如果所有的计算机系统都是用Unicode编码,在中国大陆的本地化系统中显示的“中”这个符号,发送到伊拉克的本地化系统中,显示的仍然是“中”这个字符。

4.Unicode编码的字符都占用两个字节的大小,对于ASCII码所表示的字符,只是简单地在其原来占用的一个字节前面,增加一个所有bits都为0的字节。

5.Unicode值只占用两个字节,在全世界范围内所表示的字符数不会超过216次方(65536),实际上,Unicode编码中还保留了两千多个数值没有用于字符编码。

6.在相当长的一段时间内,本地化字符编码将与Unicode编码共存。

7.Java中的字符使用的都是Unicode编码,Java在通过Unicode保证跨平台特性的前提下,也支持本地平台字符集。

三.UTF-8编码

1.ASCII码字符保持原样,仍然只占用一个字节,对于其它国家的字符,UTF-8使用两个或三个字节来表示。使用UTF-8编码的文件,通常都要用EF BB BF作为文件开头的三个字节数据。

2.字符UTF-8编码与Unicode编码之间的转换关系对应下列规则:

1>\u0001\u007F之间的字符,UTF-8编码为:(byte)c

2>\u0000或其范围在\u0080\u07FF之间的字符,UTF-8编码为:

(byte)(0xC0|0x1F&(c>>6)),(byte)(0x80|0x3F&c)

3>\u0800\uFFFF之间的字符,UTF-8编码为:

(byte)(0xE0|0x0F&(c>>12)),(byte)(0x80|0x3F&(c>>6)),(byte)(0x80|0x3F&c)

 

3.UTF-8的优点:

1>不出现内容为0x00字节。

2>便于应用程序检测数据在传输过程中是否发生了错误。

3>直接处理使用ASCII码的英文文档。

  UTF-8的缺点:某些字符需要使用三个字节。

四.UTF-16编码

1.UTF-16编码在Unicode基础上进行了一些细节上的扩充,增加了对Unicode编码没有包括的哪些字符的表示方式。

2.UTF-16编码对Unicode的扩充并没有影响Unicode编码所包括的那些字符,只是增加了对Unicode编码没有包括的那些字符的表示方式,一个使用Unicode编码的字符就是UTF-16格式的。也就是说UnicodeUTF-16的一个子集。

3.Unicode编码将0xD800-0xDFFF区间的数值保留出来,UTF-16扩充的字符占用四个字节,前面连个字节的数值为0xD800-0xD8FF之间,后面两个字节的数值为0xDC00-0xDFFF之间。

4.为什么不让前面和后面的两个字节的数值都位于0xD800-0xDFFF之间呢,这样不是能表示现在的四倍的字符吗?

答:首先,1024*1024个字节足以存储世界上各个民族的文字。其次,前后两个字节位于不同区间可以更好的区分字符头部和尾部,防止因一个丢失导致后面所有字节的错位。

5.在不同体系结构的计算机中,UTF-16编码的Unicode字符在内存中的字节顺序是不同的。对于0x1234这样一个双字节数据,使用Little-EndianBig-Endian两种方式在内存中存储的格式如下图所示。此外Intel采用的是Little-Endian

 

如果文件以0xFE 0xFF这两个字节开头,则表明文本的其余部分是Big-EndianUTF-16编码;如果文件以0xFF 0xFE这两个字节开头,则表明文本的其余部分是Little-EndianUTF-16编码。

第三部分 Properties

一.概述

 Map

  |-----Hashtable

            |------Properties

1.该集合中的键值对都是字符串类型。

2.集合中的数据可以保存到流中,或者从六中读取。

3.通常该集合用于操作以键值对的形式存在的配置文件。

二.特有方法

1.操作一个键值对

String getProperty(String key);//从集合中获取指定键的值。如果未找到该属性,则返回 null。 

String getProperty(String key, String defaultValue);//从集合中获取指定键的值。如果未找到该属性,则返回默认值变量defaultValue

Object setProperty(String key, String value);//向集合对象中的某个键设置指定的值,如果该键存在则覆盖,不存在则新建。

2.操作多个键值对

Enumeration<?> propertyNames();//获取集合对象中所有键的枚举。先在以不建议使用。 

Set<String> stringPropertyNames();//获取集合对象中所有键的键集。

3.输出到打印流

void list(PrintStream out);//将属性列表输出到指定的字节打印流。这个方法主要用于测试。 

void list(PrintWriter out);//将属性列表输出到指定的字符打印流。

4.集合对象与配置文件的数据转换

void store(OutputStream out, String comments);//将集合中的键值对信息写入到输出字节流中,通常out是一个配置文件。comments是用于说明注释的信息,它以ISO-8859-1的形式输出,所以不要写中文。 

void store(Writer writer, String comments)将集合中的键值对信息写入到输出字符流中。

void load(InputStream inStream);//将字节输入流中的键值对信息加载到集合对象中。 

void load(Reader reader) ;//将字符输入流中的键值对信息加载到集合对象中。

三.使用案例

1.将一个配置文件读取到集合对象中,对信息进行修改后再存储到配置文件中。

Properties prop=new Properties();File file=new File("propfile.properties");prop.load(new FileReader(file));System.out.println(prop.getProperty("zhangsan"));prop.setProperty("zhangsan", "11");<span style="white-space:pre"></span>prop.store(new FileWriter(file),"name+age");


public void myLoad(InputStream in) throws IOException{BufferedReader br=new BufferedReader(new InputStreamReader(in));String line=null;while((line=br.readLine())!=null){if(line.startsWith("#"))continue;String[] arr=line.split("=");String key=arr[0]; String value=arr[1];table.put(key, value);}}

3.定义功能,获取一个应用程序运行的次数,如果超过5次,给出使用次数已经到请注册的提示,并不在运行程序。

public static void registorControl() throws IOException{File cfg=new File("registorCount.properties");if(!cfg.exists()){cfg.createNewFile();}FileReader fr=new FileReader(cfg);Properties prop=new Properties();prop.load(fr);Set<String> keys=prop.stringPropertyNames();if(!keys.contains("runtimes")){prop.setProperty("runtimes", "1");}String runTimesStr=prop.getProperty("runtimes");int runTimesInt=Integer.parseInt(runTimesStr);if(runTimesInt>=5){throw new RuntimeException("使用次数已到5次,请马上注册。。。");}else{runTimesInt++;}prop.setProperty("runtimes", String.valueOf(runTimesInt));FileWriter fw=new FileWriter(cfg);prop.store(fw, "");System.out.println("程序正常运行。。。。");System.out.println("这是第"+(runTimesInt-1)+"次运行");fr.close();fw.close();}

0 0
原创粉丝点击