java第七节 IO
/* *第七讲 IO/输入与输出 * * File类 * RandomAccessFile类 * 各种节点流类 * 字符编码 * 各种过滤流与包装类 * *File类 * File类是IO包中唯一代表磁盘文件本身信息的类,而不是文件中的内容 * FIle类定义了一些与平台无关的方法来操纵文件,例如,创建,删除文件和重命名文件 * Java中的目录被当作一种特殊的文件使用,List方法可以返回目录中的所有子目录和文件名 * 在Unix下的路径分隔符为(/),在DOS下的路径分分隔符为(\),java可以正确处理Unix和DOS的路径分隔符 * *编程举例:判断某个文件是否存在,存在则删除,不存在则创建 * * *RandomAccessFile类 * RandomAccessFile类提供了众多的文件访问方法 * RandomAccessFile类支持“随机访问”方式 * RandomAccessFile类在随机(相对顺序而言)读写等长记录格式的文件时有很大优势 * RadnomAccessFile类仅限于操作文件,不能访问其他的IO设备,如网络,内存映象等 * 两种构造方法: * new RandomAccessFile(f, "rw"); 读写方式 * new RandomAccessFile(f, "r"); 只读方式 *编程实例:往文件中写入三名员工的信息,每个员工含有姓名和年龄两个字段,然后按照第二名,第一句,第三名的先后顺序读出员工信息 * * *节点流 * 1 理解流的概念 * 2 InputStream与OutputStream类 * 3 FileInputStream与FileOutputStream类 * 4 Reader与Write类 * 5 PipedInputStream与PipedOutputStream类 * 6 ByteArrayInputStream与ByteArrayOutputSteam类 * * *理解流的概念 * 流是字节序列的抽象概念 * 文件是数据的静态存储形式,而流是指数据传输时的形态 * 流类分类两个大类:节点流类和过滤流类(也叫处理流类) * * InputStream类 * 程序可以从中连接读取字节的对象叫输入流,在java中,用InputStream类来描述所有输入流的抽象概念 * InputStram类的方法: * int read(); //只从流中读取一个字节 * 二进制形式为11111111的数据,以byte类型表示为-1,以int类型表示为255 * int read(byte[] b); * int read(byte[] b, int off, int len); * long skip(login n) * int available() * void mark(int readlimit) * void reset() * boolean markSupported() * void close() * ?有了垃圾回收器,为什么还要调用close()方法 * * OutputStream类 * 程序可以向其中连接写入字节的对象叫输入流,在Java中,用OutputSteam类中描述所有输出流的抽象概念 * OutputStream类的方法; * void write(int b); * void write(byte[] b) * void write(byte[] b, int off, int len) * void flush(); //用于将内存缓存内容清空,输出到目标上 * void close() * 应用程序 -> 内存缓冲区 ->IO设备 * 1 由应用程序调用的读写,每次读写少量数据 * 2 有系统自动完成的读写,每次读写大量数据 * * 一个关于IO缓冲区的刻骨铭心的经历 * * * *FileInputStream与FileOutputStream类 * *1 FileInputStream和FileOutputStream类分别用来创建磁盘文件的输入流和输入流对象,通过它们的构造 *函数来指定文件路径和文件名 * *2 创建FileInputStream实例对象时,指定的文件应当是存在和可读的,创建FileInputStream实例对象时,如果指定的文件已经存在,这个文件中的原来内容将被覆盖清除 * *3 对同一个磁盘文件创建FileInputStream对象的两种方式: * 1 FileInputStram inOne = new FileInputStream("htllo.test"); * 2 File f = new File("hello.test"); * FileInputStream inTow = new FileInputStream(f); * *创建FileOutputStream实例对明时,可以指定还不存在的文件名,不能指定一个已经被其他程序打开的文件 * * *思考:要将A文件的内容写入B文件,在程序代码中,是用输出类对明,还是用输入类对象来连接A文件并完成对A文件的操作呢 *读取是输入流对象,对A进行读取, * *这里所提出的输出和输入都是相对于程序而言的,并不是相对于文件而言的 *所以我们应该创建一个输入类来读取文件A中的内容,然后创建一个输出类来将内容写入到B文件 * * *Reader与Writer类 * Reader和Writer是所有字符流类的抽象基类,用于简化对字符串的输入输出编程 ,即用于读写文本数据,二进制文件和文本文件的区别 * 二进制文件和文本文件的区别 * *编程举例: 用FileWriter类向文件中写入一个字符串,然后用FileReader读出写入的内容 * * * *编程实例:用FileOutputStream类向文件中写入一个串字符,然后用FileInputStream读出写入的内容 * * *PipedInputStram与PipedOutputStream类 * PipedInputStream类与PipedOutputStream类用于在应用程序中的创建管道通信 * * PipedInputStream与PipedOutputStream类的编程实例 * * PipedWriter和PipedReader类 * * 使用管道流类,可以实现各个程序模块之间的松耦合通信 *XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX *XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX * 这个例子测试了二次也没有成功,无语了 * * * * *ByteArrayInputStream与ByteArrayOutputStream类 * ByteArrayInputStream与ByteArrayOutputStream,用于以IO流的方式来完成对字节数组内容的读写,来支持类似内存虚拟文件或者内存映像文件的功能 * * ByteArrayInputStream的两个构造函数: * ByteArrayInputStream(byte[] buf) * BytearrayInputStream(byte[] buf, int offset, int lenght); * * ByteArrayOutputStream的两个构造函数: * ByteArrayOutputStream() * ByteArrayOutputStream(int) * * 编程举例: 编写一个函数,把输入流中所有英文字母变成大写字母,然后将结果写入到一个输出流对象,用这个函数来将一个字符串中的所有字符转换成大写 * *StringReader类和StringWrite类来以字符IO流的方式处理字符串 * * *重视IO程序代码的复用 * System.in连接到键盘,是InputStream类型的实例对象,System.out连接到显示器,是PrintStream类的实例对象 * 不管各种低层物理设备用什么方式实现数据终止点,InputSteam的read方法总是返回-1来表示输入流的结束 * 在Windows下,按下Ctrl+Z组合键 可以产生键盘输入流的结束标记,在LInux下,则是按下Ctrl+D组合键来产生键盘输入流的结束标记 * *编程举例:借助上一页编写的函数,将键盘上输入的内容转变成大写字母后打印在屏幕上 * * *建议: 要编程从键盘上连续读取一大段数据时,应尽量将读取数据的过程放在函数中完成,使用-1来作为键盘输入的结束点, *在该函数中编写的程序代码下应直接使用System.in读取数据,而是用一个InputStream类型的形式参数对象来读取数据,然后将System.in作为实参传递给InputStream类型的形式参数来调用该函数 * * * *字符编码 * 计算机里只有数字,计算机软件里的一切都是用数字来表示的,屏幕上显示的一个个字符也不例外 * * 字符a对应数字97,字符b对应数字98等,这种字符与数字对应的编码规则被称为ASCII(美国标准信息交换码) * ASCII的最高bit位都为0,也就是说这些数字都是0到127之间 * * 中国大陆将每一个中文字符都用两个字节的数字来表示,中文字符的每个字节的最高位bit都为1,中国大陆为每个中文字符制定的编码规则称为GB2312(国标码) * * 在GB2312的基础上,对更多的中文字符(包括繁体)进行了编码,新的编码规则称为GBK * * 在中国大陆使用的计算机系统上,BGK和GB2312就被称为该系统的本地字符集 * * "中国"的"中"字,在中国大陆的编码是十六进制的D6D0,而在中国台湾的编码是十六进投制的A4A4,台湾地区对中文字符集的编码规则称为BIG5(大五码) * * 在一个国家的本地化系统中出现的一个字符,通过电子邮件传送到另外一个国家的本地化系统中,看到的就不是那个原始字符了,而是另外那个国家的一个字符或乱码 * * *Unicode 编码 * ISO(国际标准化组织)将全世界所有的符号进行了统一编码,称之为Unicode编码 * * "中"这个符号,在全世界的任何角落始终对应的都是一个十六进制的数字4e2d * * 如果所有的计算机的系统都使用Unicode编码,在中国大陆的本地化系统中显示的"中"这个符号,发送到伊位克的本地化系统中,显示的仍然是"中"这个符号 * * Unicode编码的字符都占用两个字符节的大小,对于ASCII码所表示的字符,只是简单地在ASCII码原来占有用的一个字节前面,增加一个所有bits为0的字节 * * Unicode只占有用两个字节,在世界范围内所表示的字符个数不会超过2的16次方(65536),实际上,Unicode编码中还保留了两千多个数值没有用于字符编码 * * 在相当长的一段时期内,本地化字符编码将与Unicode编码共存 * * java中的字符使用的都是Unicode编码,Java在通过Unicode保证跨平台特性的前提下,也支持本地平台字符集 * * *UTF-8编码 * ASCII码字符保持原样,仍然只占用一个字节,对于其它国家的字符,UTF-8使用两个或三个字节来表示, * 使用UTF-8编码的文件,通常都要用EF BB BF作为文件开头的三个字节数据 * * 字符的TUF-8编码与Unicode编码之间的转换关系对应下例规则: * * \u0001和\u0007f之间的字符,UTF-8编码为:(byte)c. * 0001---007E; 0xxxxxxx ===7bits * * \u0000或其范围在\u0080和\u07ff之间的字符,UTF-8编码为: * (byte)(0xc0|0x1 f&(c>>6)), (byte)(0x80|(0x3f&c)) * 0080-07FF和0000 * 八进制为: 110xxxxx 到 10xxxxxx 11bits * * \u0800和uffff之间的字符,UTF-8编码为: * (byte)(0xe0|(0x0f&(c>>12))), (byte)(0x80|(0x3f &(c>>6))), (byte)(0x80|(0x3f& c)) * 1111xxxx 10xxxxxx 10xxxxx 16bits * * UTF-8的优点 * 1 不出现内容为0x00字节 * 2 便于应用程序检测数据在传输过程中是否发生了错误 * 3 直接处理使用ASCII码的英文文档 * UTF-8的缺点: * 某些字符需要使用三个字节,特别是中、日、韩字符 * * *TUF-16编码 * UTF-16编码在Unicode基础上进行了一些细节上的扩充,增加了对Unicode编码没有包括的那些字符的表示方式 * * UTF-16对Unicode的扩充并没有影响Unicode编码所包括的那些字符,只是增加了对Unicode编码没有包括的那些字符的表示方式 * 一个使用Unicode编码的字符就是UTF-16格式的 * * Unicode编码将0xD800-0xDFFFF区间的数值保留出来,UTF-16扩充的字符,占有用四个字节,前面两个字节的数值为 * 0xD800-0xD8FF之间,后面两具字节的数值为0XDC00-0XDFFF之间 * * 为什么不让前面和后面的两个字节的数值都位于0xD800-0xDFFFF之间呢? * * 在不同体系结构的计算机系统中,UTF-16编码的Unicode字符在内存中的字节存储顺序是不同的 * * * 对于0x1234这样的一个双字节数据,使用Little-Endian和Big-Endian两种方式在内存中存储的格式如图所示 * * 如果文件以0xFE 0XFF这两个字节开头,则表明文本的基余部分是Big-Endian的UTF-16编码; * 如果文件以0xFF 0xFE这两个字节开头,则表示文本的其余部分是Little-Endian的UTF-16编码 * * * *字符编码的操作体验 * 查看中文字符的GB2312码 * 查看中文字符的UTF-8码 * 查看中文字符的Unicode码 * 在Windows记事本程序中用不同的编码格式存储文本文件 * *字符编码应用的一个奇怪现象 * 用Windows记事本程序创建三个文件,基中分别输入"联通" "联想" "联", 然后打开这三个文件,内容为"联通" 和"联"的文件显示异常 * * * *字符编码的编程体验 * * 打印中文字符的Unicode码 * 打印中文字符的GB2312码 * 验证写入到屏幕输出流的中文字符所采用的编码 * 查看系统的缺省编码 * 修改系统的缺省编码 --这个没有测试成功 * * 验证从键盘输入流中读取的中文字符所采用的编码 * * * 研究GB2312码到Unicode码的解码过程 * GB2312码的中文字符被按照ISO8859-1字会集解码生成了Unicode字符串后,如何将这个字符串转换成正确的Unicode编码字符串 * * * *过滤流与包装类 * 包装类的概念与作用 * 1 通过FileOutputStream对象将一个浮点小数写入到文件中,你感觉有点困难吧?能否通过FileOutputStream对象直接将一个整数写入到文件呢? * 2 假如有DataOutputStream类提供了往各种输出流对象中写入各种类型(当然包括浮点小数)的方法,你现在所要做的工作就是: * 传递一个FileOutputStream输出流对象给DataOutputStream实例对象和调用DataOutputStream实例对象的用于写入浮点小数的方法 * 3 DataOutputStream并没有对应到任何具体的流设备,一定要给它传递一个对应具体流设备的输出流对象, * 完成类似DataOutputStream功能的类就量个包装类,也叫过滤流类或处理流类 * 4 DataOutputStream包装类的构造函数语法: * public DataOutputStream(OutputStream out) * DataOutputStream的部分方法列表: * public final void writeBoolean(boolean v) * public final void writeShort(int v) * public final void writeChar(int v) * public final void writeInt(int v) * public final void writeLong(long v) * public final void writeFloat(float v) * public final void writeDouble(double v) * public final void writeBytes(String s) * * BufferedInputStream 与 BufferedOutputStream类 * 缓冲流为I/O流增加了内存缓存区,增加缓冲区有两个基本目的: * 允许java程序一次不只操作一个字节,这样提高了程序的性能 * 由于有了缓冲区,使得在流上执行skip, mark和reset方法都成为可能 * BufferedInputStream和BufferedOutputStream是Java提供的两个缓冲区包装类,不管低层系统是否使用缓冲区 * 这两个类在自己的实例对象中创建缓冲区,想想这种缓冲区与底层系统提供的缓冲区的区别 * * BufferedInputStream的两个构造函数: * BufferedInputStream(InputStream in) * BufferedInputStream(InputStream in, int size) * * BufferedOutputStream类的两个构造函数: * BufferedOutputStream(OutputStream out) * BufferedOutputStream(OutputStream out, int size) * BufferedReader和BufferedWriter类 * BufferedReader的readLine方法可以一次读取一行文本,BufferedWrite的newLine方法可以向字符流中写入不同操作系统下的换行符 * * * * DataInputStream与DataOutputStream类 * PrintStream类 * ObjectInputStream与ObjectOutputStream类 * 字节流与字符流的转换 * * * DataInputStream与DataOutputStream类 * DataOutputStream类提供了三个写入字符串的方法: * public final void writeBytes(String s) * public final void writeChars(String s) * public final void writeUTF(String str) * * 为什么DataIpuutStream类中有一个readUTF方法,而没有"readBytes"和"readChars"方法呢? * * 编程实例:分别使用DataOutputSteam类的writeUTF,WriteBytes和WriteChars方法,比交这几个方法的差导师,程序中所使用的流栈如下: * * DataOutputStream -> BufferedOutputStream -> FileOutputStream * 程序 文件 * DataInputStream -> BufferedInputStream -> FileInputStream * * 关闭流框中的最上层的流对象,将会自动关闭流框中的所有底层流对象 * * *PrintStream类 * PrintStream类提供了一系列的print和println方法,可以将基本数据类型的数据格式化成字符串输出 * 格式化输出是什么意思?例如,97格式化输出的实际字节数据为0x39和0x37 * * PrintStream的三个构造函数: * PrintStream(OutputStream out) * PrintStream(OutputStream out, boolean autoflush) * PrintStream(OutputStream out, boolean autoflush, Stream encodeing) * * 与PrintStream对应的PintWritrer类,即使遇到了文本换行标识符(\n),PrintWriter类也不会自动清空缓冲区 * PrintWriter的Println方法能根据操作系统的不同而生成相应的文本换行标识符,在Windows下的文本换行标识符是'\r\n',而Linux下的文本换行标识符是"\n" * * *ObjectInputStream 与ObjectOutputStream类 * ObjectInputStream和ObjectOutputStram这两个包装类,用于从低层输出流中读取对象类型的数据和将对象类型的数据写入到底层输出流 * * ObjectInputStream和ObjectOutputStream类所读写的对象必须实现了Serializable接口,对象中的transient和static类型的成员变量不会被读取和写入 * * 一个可以被序列化的MyClass类的定义 * public class MyClass implements Serizlizable * { public transient Thread t; private String custormerID; private int total; } //transient 表示定义的暂时成员方法 编程举例:创建一个可序列化的学生对象,并用ObjectOutputStream类把它存储到一个文件(mytext.txt)中 然后再用ObjectInputStream类把存储的数据读取到一个学生对象中,即恢复保存的学生对象 字节流与字符流的转换 能不能找到一种简单的方式来读取键盘上输入的一行字符,如何找? InputStreamReader 和 OutputStreamWrite,是用于将字节流转换成字符流来读写的两个类,InputStreamReader可以将一个字节流中的字节解码成字符后读取,OupputStreamWriter将字符编码成字节后写入到一个字节流中 InputStreamReader的两个主要的构造函数: InputStreamReader(InputStream in) InputStreamReader(InputStream in, String CharsetName) OutputStreamWriter的两个主要的构造函数 OutuptStreamWriter(OutputStream ou) OutputStreamWriter(OutputStream ou, String CharsetName) 避免频繁地在字符与字节间进行转换,最好不要直接使用InputStreamReader和OutputStreamWriter类来读写数据,应尽量使用 BufferedWriter类包装OutputStreamWriter类,用BufferedReader类包装InputStreamReader; Java程序与其它进程的数据通信 在java程序中可以用process类的实例对象来表示子进程 ,子进程的标准输入和输出不再连接到键盘和显示器,而是以管道流的形式连接到父进程 的一个输出流和输入流的对象上 调用Process类的getOutputStream和getInputStream方法可以获得连接到子进程 的输出流和输入流对象 编程实例:在TestInOut类中启动java.exe命令执行别外一个MyTest类,TestInOut和MyTest通过进程间的管道相互传递数据 验证管道缓冲区满后,将最前面写入的数据挤出去,从而发生数据丢失 (1) 新的数据写入,将最前面写入的数据挤出去,从而发生数据丢失 (2) 与PipedInputStream相连的PipedOutputStream无法再写入新的数据 PipedOutputStream.writer方法处理阻塞状态 (3) 与PipedInputStream相连的PipedOutputStream无法再写入新的数据,PipedOutputStream.writer方法抛出异常 记住调用Process类的destroy方法结束子进程的运行 提高程序的运行效率 (1)for(int i=0; i<str.length(); i++){ .. } 与下面的代码比较 var len = str.length(); for(in i=0; i<len; i++){ } (2) byte[] buf = new byte[1024]; while(true){ 对buf元素的操作语句 } 与下面的语句对比 while(true){ byte [] buf = new byte[1024]; } 字节输入流类 InputStream -----FileInputStream -----objectInputStream -----PipedInputStream -----SequenceInputStream -----FilterInputStream ------DataInputStream ------PushbackInputStream ------BufferedInputStream ------LineNumberInputStream -----StringBufferIputStream -----ByteArrayInputStream 字节输出流类 OutputStream -----FileOutputStream -----ObjectOutputStream -----FilterOutputStream --------DataOutputStream --------BufferedOutputStream --------PrintStream -----PipedOutputStream -----ByteArrayOutputStream 字符输入流类 Reader -----BufferedReader -----LineNewberReader -----CharArrayReader -----StringReader -----InputStreamReader -----FileReader -----PipedReader -----FilterReader -----PushbackReader 字符输出流类 Writer ------BufferedWriter ------CharArrayWriter ------StringWriter ------OutputStreamWrtier -------FileWriter ------PrintWriter ------PipedWriter ------FilterWriter Decorator(包装)设计模式 在程序中用一个对象(the Decorators)包装另外一个对象,这就是一种被称为Decorator的设计模式 如果要设计自己的IO包装类,这个类需要继承以FilterXXX命名的类,例如, 设计一对输入输出包装类: RecordIputStream和RecordOutputStream,来完成从数据库文件中读取记录和住数据库文件中写入记录 Exception类从Throwable类继承的三个printStackTrace方法的定义如下: --public void printStackTrace(); --public void printStackTrace(PrintStream s) --public void printStackTrace(PrintWriter s) 该如何把printStackTrace方法打出的详细异常信息存储到一个字符串中? 思考与实践(1) 1 编写一个程序,将一个目录及子目录下的所有txt类型的文本文件中的内容合并到若干个新的文本文件中,当第一个新产生的文件中存储的达到1Mbytes时 剩下的内容存储到第二个新的文件中,依次往下,新产生的文本文件名依次为1.txt, 2.txt 2 用自己的话途述什么是流,什么是节点流,什么包装类 3 编写一个函数,把StringReader输入流中所有英文字母变成大写字母,并结果写入到一个StringWriter输出流对象中,然后 用这个函数将一个字符串中的所有字符转换成大写 **/ public class Lesson7 { /** * Method main * * * @param args * */ public static void main(String[] args) { // TODO: Add your code here } }