1. FileInputStream与FileOutputStream类
a) FileInputStream和FileOutputStream类分别用来创建磁盘文件的输入流和输出流对象,通过它们的构造函数来指定文件路径和文件名。
b) 创建FileInputStream实例对象时,指定的文件应当是存在和可读的。创建FileOutputStream实例对象时,如果指定的文件已经存在,这个文件中的原来内容将被覆盖清除。
c) 对同一个磁盘文件创建FileInputStream对象的两种形式:
i. FileInputStream inOne = new FileInputStream(“hello.txt”);
ii. File f = new File(“hello.txt”);
FileInputStream inTwo = new FileInputStream(f);
d) 创建FileOutputStream实例对象时,可以指定还不存在的文件名,不能指定一个已经被其他程序打开的文件。
e) 思考:要将A文件的内容写入B文件,在程序代码中,是用输出流对象,还是用输入流对象来连接A文件并完成对A文件的操作呢?
输入输出是对程序本身而言的。所以要使用输入流对象连接A文件。
f) 变成举例:用FileOutputStream类向文件中写入一个字符串,然后用FileInpuStream读出写入的内容。
i. FileStream类
package com.file;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileStream {
public static void main(String[] args) throws IOException {
File file = new File("hello.txt");
FileOutputStream out = new FileOutputStream(file);
String str = new String("hello world!");
out.write(str.getBytes());//写入文件中,内部调用flush
out.flush();
out.close();
FileInputStream in = new FileInputStream(file);
String receiveStr = "";
/* int b = in.read();
while(b != -1) {
receiveStr += (char) b;
b = in.read();
}*/
byte[] buf = new byte[1024];
int len = in.read(buf);
receiveStr = new String(buf, 0, len);
in.close();
System.out.println(receiveStr);
}
}
2. Reader与Writer类
a) Reader和Writer是所有字符流类的抽象基类,用于简化对字符串的输入输出编程,即用于读写文本数据。
b) 二进制文件和文本文件的区别:
i. 本质上文件系统中的文件都是二进制文件。
ii. 文本文件是二进制文件的一种特例。
iii. 如果一个文件只用来存储字符,没有字符以外的数据,称这个文件为文本文件。
c) 编程举例:用FileWriter类向文件中写入一个字符串,然后用FileReader读出写入的内容。
i. ReaderAndWriter类:
package com.file;
import java.io.FileReader;
import java.io.FileWriter;
public class ReaderAndWriter {
public static void main(String[] args) throws Exception {
FileWriter fw = new FileWriter("hello2.txt");
fw.write("hello world!"); //内部不会调用flush
fw.flush();
fw.close();
FileReader fr = new FileReader("hello2.txt");
char[] cbuf = new char[1024];
int len = fr.read(cbuf);
System.out.println(new String(cbuf, 0, len));
fr.close();
}
}
3. PipedInptStream与PipedOutputStream类
a) PipedInputStream类与PipedOutputStream类用于在应用程序中创建管道通信。完成线程之间的通信。
b) PipedInputStream与PipedOutputStream类的变成实例。
i. Dispatcher类:
package com.pipe;
import java.io.IOException;
import java.io.PipedOutputStream;
public class Dispatcher extends Thread {
private PipedOutputStream out = new PipedOutputStream();
public PipedOutputStream getOutputStream() {
return out;
}
@Override
public void run() {
String message = new String("hello world!");
try {
out.write(message.getBytes()); //发送消息
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Catcher类:
package com.pipe;
import java.io.IOException;
import java.io.PipedInputStream;
public class Catcher extends Thread {
private PipedInputStream in = new PipedInputStream();
public PipedInputStream getInputStream() {
return in;
}
@Override
public void run() {
byte[] buf = new byte[1024];
int len;
try {
len = in.read(buf);
String receieMsg = new String(buf, 0, len); //接收消息
System.out.println("接收到的字符串是:" + receieMsg);
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
PipedStreamTest类:
package com.pipe;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class PipedStreamTest {
public static void main(String[] args) throws IOException {
Dispatcher dis = new Dispatcher();
Catcher cat = new Catcher();
PipedOutputStream out = dis.getOutputStream();
PipedInputStream in = cat.getInputStream();
out.connect(in);
dis.start();
cat.start();
}
}
c) 使用管道流类,可以实现各个程序模块之间的松耦合通信。(模块具有强内聚弱耦合的特点)
d) PipedWriter和PipedReader类
4. ByteArrayInputStream与ByteArrayOutputStream类
a) ByteArrayInputStream与ByteArrayOutputStream,用于一IO流的方式来完成对字节数组内容的读写,来支持类似内存虚拟机文件或者内存映像文件的功能。
b) ByteArrayInputStream的两个构造函数:
i. ByteArrayInputStream(byte[] buf)
ii. ByteArrayInputStream(byte[] buf, int offset, int length)
iii. 该流会从内存从中读取数据
c) ByteArrayOutputStream的两个构造函数:
i. ByteArrayOutputStream()
ii. ByteArrayOutputStream(int)
iii. 流会数据会被写入缓存中。
d) 编程举例:编写一个把输入流中的所有英文字母变成大写字母,然后将结果写入到一个输出流对象。用这个函数来将一个字符串中的所有字符转换成大写。
i. ByteArrayTest类:
package com.bytes;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class ByteArrayTest {
public static void main(String[] args) throws IOException {
String str = "abcdefghinklmnopqrstuvwxyz";
ByteArrayInputStream bis = new ByteArrayInputStream(str.getBytes());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
transForm(bis, bos);
System.out.println(new String(bos.toByteArray()));
}
public static void transForm(InputStream is, OutputStream os) throws IOException {
int b = 0;
while((b = is.read()) != -1) {
int result = Character.toUpperCase((char) b);
os.write(result);
}
os.close();
is.close();
}
}
e) StringReader类和StringWriter类来以字符IO流的方式处理字符串。
5. 重视IO程序代码的复用
a) System.in连接到键盘,是InputStream类型的实例对象。System.out连接到显示器,是PrintStream类的实例对象。
b) 不管各种底层物理设备用什么方式实现数据的终止点,InputStream的read方法总是返回-1来表示输入流的结束。
c) 在Windows下,按下Ctrl+Z组合键可以产生键盘输入流的结束标记,在Linux下,则是按下Ctrl+D组合键来产生键盘输入流的结束标记。
d) 编程举例:借助上一页编写的函数,将键盘上输入的内容转变成大写字母后打印在屏幕上。
i. 加入此段代码:transForm(System.in, System.out);
e) 建议:要编程从键盘上连续读取一大段数据时,应尽量将读取数据的过程放在函数中完成。使用-1来作为键盘输入的结束点。在该函数中编写的程序代码不应直接使用System.in读取数据,而是用一个InputStream类型的形式参数对象来读取数据,然后将System.in作为实参传递给InputStream类型的形式参数来调用该函数。
6. 字符编码
a) 计算机里只有数字,计算机软件里的一切都是用数字表示的,屏幕上显示的一个个字符也不例外。
b) 字符a对应的数字是97,字符b对应的数字是98等,这中字符与数字对应的编码规则被称为ASCII(美国标准信息交换码)。ASCII的最高bit位都为0,即这些数字都在0到127之间。
c) 中国大陆将每一个中文字符都用两个字节的数字来表示,中文字符的每个字节的最高位bit窦唯1,中国大陆为每个中文字符制定的编码规则成为GB2312(国标码)。
d) 在GB2312的基础上,对更多的中文字符(包括繁体)进行了编码,新的编码规则成为GBK。
e) 在中国大陆使用的计算机系统上,GBK和GB2312就被称为该系统的本地字符集。
f) “中国”的“中”字,在中国大陆的编码是十六进制ideD6D0,而在中国台湾的编码是十六进制的A4A4,台湾地区对中文字符集的编码规则称为BIG。
g) 在一个国家的本地化系统中出现的一个字符,通过电子邮件传送到另外一个国家的本地化系统中,看到的就不是那个原始字符了,而是另外那个国家的一个字符或乱码。
7. Unicode编码
a) ISO(国际标准化组织)将全世界所有的符号进行了统一编码,称之为Unicode编码。
b) “中”这个符号,在全世界的任何角落始终对应的都是同一个十六进制的数字4e2d。
c) 如果所有的计算机系统都是用Unicode编码,在中国大陆的本地化系统中显示的“中”这个符号,发送到伊拉克的本地化系统中,显示的仍然是“中”这个符号。
d) Unicode编码的字符都占用两个字节的大小,对于ASCII码所表示的字符,只是简单地在ASCII码原来占用一个字节的前面,增加一个所有bits为0的字节。
e) Unicode只占用两个字节,在全世界范围内所表示的字符个数不会超过2的16次方(65536),实际上,Unicode编码中还保留了两千多个数值没有用于字符编码。
f) 在相当长的一段时间内,本地化字符编码将于Unicode编码共存。
g) Java中的字符使用的都是Unicode编码,Java在通过Unicode保证跨平台特性的前提下,也支持本地平台字符集。
8. UTF-8编码
a) ASCII码字符保持原样,仍然只占用一个字节,对于其他国家的字符,UTF-8使用两个或三个字节表示,使用UTF-8编码的文件,通常都要用EF BB BF作为文件开头的三个字节数据。
b) UTF-8的优点:不会出现内容为0x00字节
便于应用程序检测数据在传输过程中是否发生了错误。
直接处理使用ASCII码的英文文档。
c) UTF-8的缺点:有些字符需用三个字节。
9. UTF-16编码
a) 是对Unicode的扩充,编码格式与Unicode一样。占用两个字节,扩充字符占用4个字节。
10. 过滤流与包装类
a) 包装类的概念与作用
i. 作用:简化各种不同类型数据写入到文件的步骤,使操作数据更加方便。
ii. 通过FileOutputStream对象将一个浮点小数写入到文件中(可用floatToIntBits(float value)方法)比较复杂。
iii. 假如有个DataOutputStream类提供了往各种输出流对象中写入各种类型数据(包括浮点小数)的方法。只需传递一个FileOutputStream输出流对象给DataOutputStream调用。
iv. 包装流类与节点流的关系图
b) BufferedInputStream与BufferedOutputStream类
i. 缓冲流为I/O流增加了内存缓冲区,增加缓冲区的目的:
1. 允许Java程序一次操作多个字节,提高程序性能。
2. 有了缓冲区,使得在流上之心skip、mark和reset方法都成为可能。
ii. BufferedInputSteam和BufferedOutputStream是Java提供的两个缓冲区包装类,不管底层是否使用缓冲区,这两个类的实例对象都创建缓冲区。
iii. BufferedReader和BufferedWriter类
1. BufferedReder的readLine可以一次读取一行文本。读到回车(‘/n’)或换行(‘/r’)为止。
2. BufferedWriter的newLine方法可以向字符流中写入不同操作系统下的换行符。
c) DataInputStream与DataOutputStream类
i. 由于DataOutputStream的writeBytes(String s) 和writeChars(String s) 没有指定写入的字符串长度,所以DataInputStream没有readBytes()和readCahrs()方法。
ii. 编程实例:分别使用DataOutputStream类的writeUTF、writeBytes和writeChars方法,比较这几个方法的差异。程序中所使用的流栈如下图:
1.
2. 关闭流栈的最上层的流对象(DataOutputStream和DataInputStream),将会自动关闭流栈中的所有底层流对象。
d) PrintStream类
i. PrintStream类提供了一系列的print和println方法,可以将基本数据类型的数据格式化成字符串输出。
ii. 格式化输出是:将数据转换成ASCII码形式。(例如97被格式化的实际字节数据为0x39和0x37)
iii. 与PrintStream对应的PrintWriter类,即使遇到了文本换行标识符(/n),PrintWriter类也不自动清空缓冲区。其生成的换行标识符是根据系统默认的标识符而确定的。
e) ObjectInputStream与ObjectOutputStream类
i. ObjectInputStream和ObjectOutputStream这两个包装类用于从底层输入流中读取对象类型的数据和将对象类型的数据写入到底层输入流。
ii. ObjectInputStream与ObjectOutputStream类所读写的对象必须实现Serializable接口。对象中的transient和static类型的成员变量不会被读取和写入。
iii. 一个可以被序列化的MyClass类的定义:
1. public class MyClass implements Serializable {
public transient Thread t;
private String customerID;
private int total;
2. }
iv. 编程举例:创建一个可序列化的学生对象,并用ObjectOutputStream类把它存储到一个文件(object.txt)中,然后再用ObjectInputStream类把存储的数据读取到学生对象中,即恢复保存的学生对象。
import java.io.Serializable;
public class Student implements Serializable {
private int id;
private int age;
private String name;
private String department;
public Student(int id, int age, String name, String department) {
this.id = id;
this.age = age;
this.name = name;
this.department = department;
}
public String toString() {
return "id=" + id + " ,age=" + age + " ,name=" + name + " ,department=" + department;
}
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectStreamTest {
public static void main(String[] args) throws Exception {
Student s1 = new Student(1, 18, "xiaoming", "nature");
Student s2 = new Student(2, 20, "xiaogang", "nature");
FileOutputStream fos = new FileOutputStream("object.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.writeObject(s2);
oos.close();
FileInputStream fis = new FileInputStream("object.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Student fromS1 = (Student) ois.readObject();
Student fromS2 = (Student) ois.readObject();
ois.close();
System.out.println(fromS1);
System.out.println(fromS2);
}
}
f) 字节流与字符流的转换
i. 能不能找到一种简单的方式来读取键盘上输入的一行字符,如何找?
1. 在帮助文档中找带有readLine的方法的类。
2. 可以找到BufferedReader等类,使用BufferedReader类读取键盘上的一行字符需用转换流InputStreamReader。
ii. InputStreamReader和OutputStreamWriter,是用于将字节流转换成字符流来读写的两个类。InputStreamReader可以将一个字节流中的字节解码成字符后读取,OutputStreamWriter将字符编码成字节后写入到一个字节流中。
iii. 为了避免频繁地在字符与字节间进行转换,最好不要直接使用InputStreamReader和OutputStreamWriter类来读写数据,应尽量使用BufferedWriter类包装OutputStreamWriter类,用BufferedReader类包装InputStreamReader类。
11. Java程序与其它进程的数据通信
a) 在Java程序中可以用Process类的实例对象来表示子进程,子进程的标准输入和输出不再连接到键盘和显示器,而是以管道流的形式连接到父进程的一个输出流和输入流对象上。
b) 调用Process类的getOutputStream和getInputStream方法可以获得连接到子进程的输出流和输入流对象。
c) 编程实例:在InOutTest类中启动Java.exe命令执行另外一个MyTest 类,InOutTest和MyTest通过进程间的管道相互传递数据。
d) 验证管道缓冲区满后,将发生下面的哪种情况:
i. 新的数据写入时,将最前面写入的数据挤出去,从而发生数据丢失。
ii. 与PipedInputStream相连接的PipedOutputStream无法再写入新的数据,PipedOutputStream.write方法处于阻塞状态,并抛出异常。
iii. 调用Process类的destory方法结束子进程的运行。
e) 提高程序的运行效率
i. for(int i=0; i<str.length(); i++) {
1. ……
ii. }
iii. 改写成:
iv. int len = str.length();
v. for(int i=0; i<len; i++) {
1. ……
vi. } 就是多想几步。
12. 总结
a) java.io包中定义了多个流类型(类或抽象类)来实现输入/输出功能:可以从不同的角度对其进行分类:
i. 按数据流的方向不同可以分为输入流和输出流。
ii. 按处理数据单位的不同可以分为字节流和字符流。
iii. 按照功能的不同可以分为节点流和处理流。
b) c)
d)