尽管字节流提供了处理任何类型输入/输出操作的足够的功能,它们不能直接操作Unicode字符。既然Java的一个主要目的是支持“只写一次,到处运行”的哲学,包括直接的字符输入/输出支持是必要的。这里在学习几个字符输入/输出类。字符流层次结构的顶层是Reader 和Writer 抽象类。
字符输入/输出类是在java 的1.1版本中新加的。由此,你仍然可以发现遗留下的程序代码在应该使用字符流时却使用了字节流。当遇到这种代码,最好更新它
由于Java采用16位的Unicode字符,因此需要基于字符的输入/输出操作。从Java1.1版开始,加入了专门处理字符流的抽象类Reader和Writer,前者用于处理输入,后者用于处理输出。这两个类类似于InputStream和OuputStream,也只是提供一些用于字符流的规定,本身不能用来生成对象
Reader和Writer类也有较多的子类,与字节流类似,它们用来创建具体的字符流对象进行I/O操作。字符流的读写等方法与字节流的相应方法都很类似,但读写对象使用的是字符
Reader中包含一套字符输入流需要的方法,可以完成最基本的从输入流读入数据的功能。当Java程序需要外设的数据时,可根据数据的不同形式,创建一个适当的Reader子类类型的对象来完成与该外设的连接,然后再调用执行这个流类对象的特定输入方法,如read(),来实现对相应外设的输入操作
Writer中包含一套字符输出流需要的方法,可以完成最基本的输出数据到输出流的功能。当Java程序需要将数据输出到外设时,可根据数据的不同形式,也要创建一个适当的Writer子类类型的对象来完成与该外设的连接,然后再调用执行这个流类对象的特定输出方法,如write(),来实现对相应外设的输出操作
Reader是定义Java的流式字符输入模式的抽象类。该类的所有方法在出错情况下都将引发IOException 异常
•Writer 是定义流式字符输出的抽象类。所有该类的方法都返回一个void 值并在出错条件下引发IOException 异常
Reader的类层次:
Writer的类层次:
Java程序语言使用Unicode来表示字符串和字符, Unicode使用两个字节来表示一个字符,即一个字符占16位
InputStreamReader和OutputStreamWriter:
这是java.io包中用于处理字符流的基本类,用来在字节流和字符流之间搭一座“桥”。这里字节流的编码规范与具体的平台有关,可以在构造流对象时指定规范,也可以使用当前平台的缺省规范
InputStreamReader和OutputStreamWriter类的主要构造方法如下
–public InputSteamReader(InputSteam in)
–public InputSteamReader(InputSteam in,String enc)
–public OutputStreamWriter(OutputStream out)
–public OutputStreamWriter(OutputStream out,String enc)
其中in和out分别为输入和输出字节流对象,enc为指定的编码规范(若无此参数,表示使用当前平台的缺省规范,可用getEncoding()方法得到当前字符流所用的编码方式)。
读写字符的方法read()、write(),关闭流的方法close()等与Reader和Writer类的同名方法用法都是类似的。
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; public class StreamTest { public static void main(String[] args) throws Exception { FileOutputStream fos = new FileOutputStream("file.txt"); OutputStreamWriter osw = new OutputStreamWriter(fos); BufferedWriter bw = new BufferedWriter(osw); bw.write("www.google.cn"); bw.write("\n"); bw.write("www.baidu.com"); bw.close(); FileInputStream fis = new FileInputStream("file.txt"); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr); System.out.println(br.readLine()); System.out.println(br.readLine()); } }
System的out属性,是一个标准的输出流,对于我们的计算机标准输出就是输出到显示器。还有一个in属性,代表标准输入,就是键盘
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class StreamTest2 { public static void main(String[] args) throws Exception { InputStreamReader isr = new InputStreamReader(System.in); BufferedReader br = new BufferedReader(isr); String str; while(null != (str = br.readLine())) { System.out.println(str); } br.close(); } }
FileReader:
FileReader类创建了一个可以读取文件内容的Reader类。FileReader继承于InputStreamReader。它最常用的构造方法显示如下
–FileReader(String filePath)
–FileReader(File fileObj)
–每一个都能引发一个FileNotFoundException异常。这里,filePath是一个文件的完整路径,fileObj是描述该文件的File 对象
import java.io.BufferedReader; import java.io.FileReader; public class FileReader1 { public static void main(String[] args) throws Exception { FileReader fr = new FileReader("src\\com\\cdtax\\io\\FileReader1.java"); BufferedReader br = new BufferedReader(fr); String str; while(null != (str = br.readLine())) { System.out.println(str); } br.close(); } }
FileWriter:
FileWriter 创建一个可以写文件的Writer 类。 FileWriter继承于OutputStreamWriter.它最常用的构造方法如下:
–FileWriter(String filePath)
–FileWriter(String filePath, boolean append)
–FileWriter(File fileObj)
–append :如果为 true,则将字节写入文件末尾处,而不是写入文件开始处
import java.io.FileWriter; import java.io.IOException; public class FileWriter1 { public static void main(String[] args) throws Exception { String str = "hello world welcome"; char[] buffer = new char[str.length()]; str.getChars(0, str.length(), buffer, 0); FileWriter f = new FileWriter("file2.txt"); for(int i = 0;i < buffer.length;i++) { f.write(buffer[i]); } f.close(); } }
它们可以引发IOException或SecurityException异常。这里,filePath是文件的完全路径,fileObj是描述该文件的File对象。如果append为true,输出是附加到文件尾的。
FileWriter类的创建不依赖于文件存在与否。在创建文件之前,FileWriter将在创建对象时打开它来作为输出。如果你试图打开一个只读文件,将引发一个IOException异常
CharArrayReader:
CharArrayReader 是一个把字符数组作为源的输入流的实现。该类有两个构造方法,每一个都需要一个字符数组提供数据源
–CharArrayReader(char array[ ])
–CharArrayReader(char array[ ], int start, int numChars)
–这里,array是输入源。第二个构造方法从你的字符数组的子集创建了一个Reader,该子集以start指定的索引开始,长度为numChars
import java.io.CharArrayReader; import java.io.IOException; public class CharArrayReader1 { public static void main(String[] args) throws Exception { String temp = "hello world"; char[] ch = new char[temp.length()]; temp.getChars(0, temp.length(), ch, 0); CharArrayReader input = new CharArrayReader(ch); int i; while(-1 != (i = input.read())) { System.out.print((char)i); } input.close(); } }
该例子用到了上述CharArrayReader的两个构造方法
–public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
将字符从此字符串复制到目标字符数组。 要复制的第一个字符在索引 srcBegin 处;要复制的最后一个字符在索引 srcEnd-1 处(因此要复制的字符总数是 srcEnd-srcBegin)。要复制到 dst 子数组的字符从索引 dstBegin 处开始,并结束于索引:dstbegin + (srcEnd-srcBegin) - 1
CharArrayWriter 实现了以数组作为目标的输出流。CharArrayWriter 有两个构造方法
–CharArrayWriter( )
–CharArrayWriter(int numChars)
–第一种形式,创建了一个默认长度的缓冲区。
–第二种形式,缓冲区长度由numChars指定。缓冲区保存在CharArrayWriter的buf 成员中。缓冲区大小在需要的情况下可以自动增长。缓冲区保持的字符数包含在CharArrayWriter的count 成员中。buf 和count 都是受保护的域 (protected)
BufferedReader 通过缓冲输入提高性能。它有两个构造方法
–BufferedReader(Reader inputStream)
–BufferedReader(Reader inputStream, int bufSize)
–第一种形式创建一个默认缓冲区长度的缓冲字符流。第二种形式,缓冲区长度由bufSize传入
和字节流的情况相同,缓冲一个输入字符流同样提供支持可用缓冲区中流内反向移动的基础。为支持这点,BufferedReader 实现了mark( )和reset( )方法,并且BufferedReader.markSupported( ) 返回true
BufferedWriter是一个增加了flush( )方法的Writer。flush( )方法可以用来确保数据缓冲区确实被写到实际的输出流。用BufferedWriter 可以通过减小数据被实际的写到输出流的次数而提高程序的性能
BufferedWriter有两个构造方法:
–BufferedWriter(Writer outputStream)
–BufferedWriter(Writer outputStream, int bufSize)
–第一种形式创建了使用默认大小缓冲区的缓冲流。第二种形式中,缓冲区大小是由bufSize参数传入的
字符集的编码:
ASCII(American Standard Code for Information Interchange,美国信息互换标准代码),是基于常用的英文字符的一套电脑编码系统。我们知道英文中经常使用的字符、数字符号被计算机处理时都是以二进制码的形式出现的。这种二进制码的集合就是所谓的ASCII码。每一个ASCII码与一个8位(bit)二进制数对应。其最高位是0,相应的十进制数是0-127。如,数字“0”的编码用十进制数表示就是48。另有128个扩展的ASCII码,最高位都是1,由一些制表符和其它符号组成。ASCII是现今最通用的单字节编码系统。
GB2312:GB2312码是中华人民共和国国家汉字信息交换用编码,全称《信息交换用汉字编码字符集-基本集》。主要用于给每一个中文字符指定相应的数字,也就是进行编码。一个中文字符用两个字节的数字来表示,为了和ASCII码有所区别,将中文字符每一个字节的最高位置都用1来表示。
GBK:为了对更多的字符进行编码,国家又发布了新的编码系统GBK(GBK的K是“扩展”的汉语拼音第一个字母)。在新的编码系统里,除了完全兼容GB2312 外,还对繁体中文、一些不常用的汉字和许多符号进行了编码。
ISO-8859-1:是西方国家所使用的字符编码集,是一种单字节的字符集 ,而英文实际上只用了其中数字小于128的部分。
Unicode:这是一种通用的字符集,对所有语言的文字进行了统一编码,对每一个字符都用2个字节来表示,对于英文字符采取前面加“0”字节的策略实现等长兼容。如 “a” 的ASCII码为0x61,UNICODE就为0x00,0x61。(在internet上传输效率较低)
UTF-8:Eight-bit UCS Transformation Format,(UCS,Universal Character Set,通用字符集,UCS 是所有其他字符集标准的一个超集)。一个7位的ASCII码值,对应的UTF码是一个字节。如果字符是0x0000,或在0x0080与0x007f之间,对应的UTF码是两个字节,如果字符在0x0800与0xffff之间,对应的UTF码是三个字节(汉字为3个字节)。
如果UNICODE字符由2个字节表示,则编码成UTF-8很可能需要3个字节。而如果UNICODE字符由4个字节表示,则编码成UTF-8可能需要6个字节。用4个或6个字节去编码一个UNICODE字符可能太多了,但很少会遇到那样的UNICODE字符。 UTF-8转换表表示如下:
UNICODE | bit数 | UTF-8 | byte数 | 备注 |
0000 0000 ~ 0000 007F |
0~7 | 0XXX XXXX | 1 | |
0000 0080 ~ 0000 07FF |
8~11 | 110X XXXX 10XX XXXX |
2 | |
0000 0800 ~ 0000 FFFF |
12~16 | 1110 XXXX 10XX XXXX 10XX XXXX |
3 | 基本定义范围:0~FFFF |
0001 0000 ~ 001F FFFF |
17~21 | 1111 0XXX 10XX XXXX 10XX XXXX 10XX XXXX |
4 | Unicode6.1定义范围:0~10 FFFF |
0020 0000 ~ 03FF FFFF |
22~26 | 1111 10XX 10XX XXXX 10XX XXXX 10XX XXXX 10XX XXXX |
5 | |
0400 0000 ~ 7FFF FFFF |
27~31 | 1111 110X 10XX XXXX 10XX XXXX 10XX XXXX 10XX XXXX 10XX XXXX |
6 | |
实际表示ASCII字符的UNICODE字符,将会编码成1个字节,并且UTF-8表示与ASCII字符表示是一样的。所有其他的UNICODE字符转化成UTF-8将需要至少2个字节。每个字节由一个换码序列开始。第一个字节由唯一的换码序列,由n位连续的1加一位0组成,
首字节连续的1的个数表示字符编码所需的字节数。
示例
UNICODE uCA(11001010) 编码成UTF-8将需要2个字节:
uCA -> C3 8A
UNICODE uF03F (11110000 00111111) 编码成UTF-8将需要3个字节:
u F03F -> EF 80 BF
Unicode 16进制 | Unicode 2进制 | bit数 | UTF-8 2进制 | UTF-8 16进制 |
CA | 1100 1010 | 8 | 1100 0011 1000 1010 | C3 8A |
F0 3F | 1111 0000 0011 1111 | 16 | 1110 1111 1000 0000 1011 1111 | EF 80 BF |
译者注:由上分析可以看到,UNICODE到UTF-8的转换就是先确定编码所需要的字节数,然后用UNICODE编码位从低位到高位依次填入上面表示为x的位上,不足的高位以0补充。
file.encoding=GBK,这说明在该系统上采用的字符编码方式为GBK
RandomAccessFile:
RandomAccessFile包装了一个随机访问的文件。它不是派生于InputStream和OutputStream,而是实现定义了基本输入/输出方法的DataInput和DataOutput接口。它支持定位请求——也就是说,可以在文件内部放置文件指针。它有两个构造方法:
RandomAccessFile(File fileObj, String access) throws FileNotFoundException
RandomAccessFile(String filename, String access) throws FileNotFoundException
第一种形式,fileObj指定了作为File 对象打开的文件的名称。
第二种形式,文件名是由filename参数传入的。
两种情况下,access 都决定允许访问何种文件类型。如果是“r”,那么文件可读不可写,如果是“rw”,文件以读写模式打开
RandomAccessFile类同时实现了DataInput和DataOutput接口,提供了对文件随机存取的功能,利用这个类可以在文件的任何位置读取或写入数据。
RandomAccessFile类提供了一个文件指针,用来标志要进行读写操作的下一数据的位置。
常用方法:
–public long getFilePointer()
–返回到此文件开头的偏移量(以字节为单位),在该位置发生下一个读取或写入操作
–public void seek(long pos)
–设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。偏移量的设置可能会超出文件末尾。偏移量的设置超出文件末尾不会改变文件的长度。只有在偏移量的设置超出文件末尾的情况下对文件进行写入才会更改其长度
–public long length()——返回此文件的长度
–public int skipBytes(int n)——尝试跳过输入的 n 个字节以丢弃跳过的字节
import java.io.RandomAccessFile; public class RandomAccessFile2 { public static void main(String[] args) throws Exception { Person1 p = new Person1(1,"hello",5.42); RandomAccessFile raf = new RandomAccessFile("text.txt","rw"); p.write(raf); Person1 p2 = new Person1(); raf.seek(0); p2.read(raf); System.out.println(p2.getId() + ":" +p2.getName() + ":"+p2.getHeight()); } } class Person1 { int id; String name; double height; public int getId() { return id; } public String getName() { return name; } public double getHeight() { return height; } public Person1() { } public Person1(int id,String name,double height) { this.id = id; this.name = name; this.height =height; } public void write(RandomAccessFile raf) throws Exception { raf.writeInt(this.id); raf.writeUTF(this.name); raf.writeDouble(this.height); } public void read(RandomAccessFile raf) throws Exception { this.id = raf.readInt(); this.name = raf.readUTF(); this.height = raf.readDouble(); } }
在写上面程序时,曾不小心将Person1类中的write()方法的raf.writeUTF(this.name);写成了raf.writeChars(this.name);导致程序运行时总是报:java.io.EOFException异常,并定位在main方法的p2.read(raf);
字符集:CharSet类
import java.nio.charset.Charset; import java.util.Iterator; import java.util.Set; import java.util.SortedMap; public class CharSetTest { public static void main(String[] args) { SortedMap<String,Charset> map = Charset.availableCharsets(); Set<String> set = map.keySet(); for(Iterator<String> iter = set.iterator();iter.hasNext();) { System.out.println(iter.next()); } } }
打印出系统支持的字符集:
Big5,Big5-HKSCS,EUC-JP,EUC-KR,GB18030,GB2312,GBK,IBM-Thai,IBM00858,IBM01140,IBM01141,IBM01142,IBM01143,IBM01144,IBM01145,IBM01146,IBM01147,IBM01148,IBM01149,IBM037,IBM1026,IBM1047,IBM273,IBM277,IBM278,IBM280,IBM284,IBM285,IBM297,IBM420,IBM424,IBM437,IBM500,IBM775,IBM850,IBM852,IBM855,IBM857,IBM860,IBM861,IBM862,IBM863,IBM864,IBM865,IBM866,IBM868,IBM869,IBM870,IBM871,IBM918,ISO-2022-CN,ISO-2022-JP,ISO-2022-JP-2,ISO-2022-KR,ISO-8859-1,ISO-8859-13,ISO-8859-15,ISO-8859-2,ISO-8859-3,ISO-8859-4,ISO-8859-5,ISO-8859-6,ISO-8859-7,ISO-8859-8,ISO-8859-9,JIS_X0201,JIS_X0212-1990,KOI8-R,KOI8-U,Shift_JIS,TIS-620,US-ASCII,UTF-16,UTF-16BE,UTF-16LE,UTF-32,UTF-32BE,UTF-32LE,UTF-8,windows-1250,windows-1251,windows-1252,windows-1253,windows-1254,windows-1255,windows-1256,windows-1257,windows-1258,windows-31j,x-Big5-HKSCS-2001,x-Big5-Solaris,x-euc-jp-linux,x-EUC-TW,x-eucJP-Open,x-IBM1006,x-IBM1025,x-IBM1046,x-IBM1097,x-IBM1098,x-IBM1112,x-IBM1122,x-IBM1123,x-IBM1124,x-IBM1364,x-IBM1381,x-IBM1383,x-IBM33722,x-IBM737,x-IBM833,x-IBM834,x-IBM856,x-IBM874,x-IBM875,x-IBM921,x-IBM922,x-IBM930,x-IBM933,x-IBM935,x-IBM937,x-IBM939,x-IBM942,x-IBM942C,x-IBM943,x-IBM943C,x-IBM948,x-IBM949,x-IBM949C,x-IBM950,x-IBM964,x-IBM970,x-ISCII91,x-ISO-2022-CN-CNS,x-ISO-2022-CN-GB,x-iso-8859-11,x-JIS0208,x-JISAutoDetect,x-Johab,x-MacArabic,x-MacCentralEurope,x-MacCroatian,x-MacCyrillic,x-MacDingbat,x-MacGreek,x-MacHebrew,x-MacIceland,x-MacRoman,x-MacRomania,x-MacSymbol,x-MacThai,x-MacTurkish,x-MacUkraine,x-MS932_0213,x-MS950-HKSCS,x-MS950-HKSCS-XP,x-mswin-936,x-PCK,x-SJIS_0213,x-UTF-16LE-BOM,X-UTF-32BE-BOM,X-UTF-32LE-BOM,x-windows-50220,x-windows-50221,x-windows-874,x-windows-949,x-windows-950,x-windows-iso2022jp