对 IO 进行缓冲是一种常见的性能优化。缓冲流为 IO 流增加了内存缓冲区。增加缓冲区有两个基本目的:
允许 Java 的IO 一次不只操作一个字节,这样提高了整个系统的性能
出于有缓冲区,使得在流上执行 skip、mark 和reset 方法都成为可能。
BufferedInputStream
Java的BufferedInputStream类可以对任何的InputStream进行带缓冲区的封装以达到性能的改善。BufferedInputStream 有两个构造函数:
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in,int size)
第一种形式的构造函数创建了一个带有 32 字节缓冲区的缓冲流,第二种形式的构造函数按指定的大小来创建缓冲区。通常缓冲区大小是内存、磁盘扇区或其他系统容量的整数倍,这样就可以充分提高 I/O 的性能。一个最优的缓冲区的大小,取决于它所在的操作系统、可用的内存空间以及机器的配置
对输入流进行缓冲可以实现部分字符的回流。除了 InputStream 中常用的 read 和 skip方法,BufferedInputStream 还支持mark 和reset 方法。mark 方法在流的当前位置作一个标记,该方法接收的一个整数参数用来指定从标记处开始,还能通过 read 方法读取的字节数reset 方法可以让以后的read 方法重新回到 mark 方法所作的标记处开始读取数据
mark 只能限制在建立的缓冲区内
BufferedOutputStream
往 BufferedOutputStream 输出和往 OutputStream 输出完全一样,只不过BufferedOutputStream 有一个 flush 方法用来将缓冲区的数据强制输出完。与缓冲区输入流不同,缓冲区输出流没有增加额外的功能。在 Java 中使用输出缓冲也是为了提高性能。它也有两个构造函数:
BufferedoutputStream(OutputStream out)
BufferedOutputStream(OutputStream out,int size)
字符的 UTF 编码对应下列规则:
假如字符c的范围在u0001 和u007f 之间,对应的UTF 码占1个字节,内容为:(byte)c.
假如字符c是u0000 或其范围在u0080和u07ff之间,对应的UTF码占2个字节内容为:(byte)(0xc0l(0x1f&(c>>6))),(byte)(0x801(0x3f&c))。
假如字符c的范围在0800 和 uffff 之间,对应的UTF 码占3个字节,内容为:byte)0xe0l(0x0f&(c>>12))),(byte)0x801(0x3f &(c>>6))),(byte)0x801(0x3f&c))
import java.io.*;
public class DateStreamTest {
public static void main(String[] args) {
try {
FileOutputStream fos = new FileOutputStream("hello.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
DataOutputStream dos = new DataOutputStream(bos);
dos.writeUTF("ab中国");
dos.writeBytes("ab中国");
dos.writeChars("ab中国");
dos.close();
FileInputStream fis = new FileInputStream("hello.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
DataInputStream dis = new DataInputStream(bis);
System.out.println(dis.readUTF());
fis.close();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
PrintStream类提供了一系列的print和printin方法,可以实现将基本数据类型的格式化成字符串输出。在前面,我们在程序中大量用到“Systemoutprintin”语句中的Systemout就是PrintStream类的一个实例对象,读者已经多次使用到这个类了
println 方法与 print 方法的区别是:前者会在打印完的内容后再多打印一个换行符(n),所以println()等于print(“n")
Java的PrintStream对象具有多个重载的 print和println 方法,它们输出各种类型(包括Object)的数据。对于基本数据类型的数据,print和printn 方法会先将它们转换成字符串的形式后再输出,而不是输出原始的字节内容,如:整数 123 的打印结果是字符'1' '3’所组合成的一个字符串,而不是整数 123 在内存中的原始字节数据。对于个非基本数据类型的对象,print和 println 方法会先调用对象的 toString 方法,然后再输出toString 方法返回的字符串
I0包中提供了一个与PrintStream对应的 PrintWriter类,PrintWriter 即使遇到换行符()也不会自动清空缓冲区,只在设置了autoflush 模式下使用了println方法后才自动清空缓冲区。PrintWriter 相对 PrintStream 最有利的一个地方就是 printIn 方法的行为,在 Windows的文本换行是“rin”,而 Liux 下的文本换行是“m”,如果我们希望程序能够生成平台相关的文本换行,而不是在各种平台下都用“m"作为文本换行,我们就应该使用 PrintWriter 的printIn方法时,PrintWriter的printIn方法能根据不同的操作系统而生成相应的换行符
格式化输出是指将一个数据用其字符串格式输出,如我们使用 print 方法把 97 这个整数打印到一个文件中,该方法将把“9’和“7’这两个字符的 ASCII 码写入到文件中,也就是文件中会被写入两个字节,这两个字节中的数字分别为 57(十六进制的 0x39)和 55(十六进制的0x37),在记事本程序中显示为“9’和“7’这两个字符。如果我们使用 write方法把97 这个整数写到一个文件中,只有一个字节会写入到这个文件中,字节中的数字就是97,正好是字符“a’的ASCII 码,所以在记事本程序中显示为一个字符“a’
序列化的好处在于:它可以将任何实现了 Serializable 接口的对象转换为连续的字节数据,这些数据以后仍可被还原为原来的对象状态,即使这些数据通过网络传输也没问题。序列化能处理不同操作系统上的差异,我们可以在 Windows 上产生某个对象,将它序列化存储,然后通过网络传到 Linux 机器上,该对象仍然可以被正确重建出来,在这期间,我们完全不用担心不同机器上的不同的数据表示方式
import java.io.*;
public class Serializatioan {
public static void main(String[] args) throws IOException,ClassNotFoundException {
Student stu = new Student(19, "dintdding", 50, "huaxue");
FileOutputStream fos = new FileOutputStream("mytext.txt");
ObjectOutputStream os = new ObjectOutputStream(fos);
try {
os.writeObject(stu);
os.close();
}catch (IOException e){
System.out.println(e.getMessage());
}
stu=null;
FileInputStream fi = new FileInputStream("mytext.txt");
ObjectInputStream si = new ObjectInputStream(fi);
try {
stu=(Student)si.readObject();
si.close();
}catch (IOException e){
System.out.println("ID is:"+stu.id);
System.out.println("name is:"+stu.name);
System.out.println("age is:"+stu.age);
System.out.println("department is:"+stu.department );
}
}
}
class Student implements Serializable{
int id;
String name;
int age;
String department;
public Student(int id, String name, int age, String department) {
this.id = id;
this.name = name;
this.age = age;
this.department = department;
}
}
InputStreamReader 和OutputStreamWriter,这两个类是字节流和字符流之间转换的类 InputStreamReader可以将一个字节流中的字节解码成字符,OuputStreamWriter将写入的字符编码成字节后写入一个字节流。
InputStreamReader 有两个主要的构造函数
InputStreamReader(InputStream in)
//用默认字符集创建一个 InputstreamReader 对象
InputStreamReader(InputStream in,String CharsetName)
/*接受以指定字符集名的字符串,并用该字符集创建对象*/
OutputstreamWriter 也有对应的两个主要的构造函数
OutputStreamWriter(Outputstream in)
//用默认字符集创建一个OutputstreamWriter对象 OutputStreamWriter(OutputStream in,String CharsetName)
/*接受以指定字符集名的字符串,并用该字符集创建OutputStreamwriter 对象*/
构建 BufferedReader 对象时,必须传递一个Reader类型的对象作为参数,而键盘对应的Systemin是一个InputStream类型的对象,解决问题的关键是,我们还需要找到将InputStream类型的流对象包装成 Reader 类型的包装类
public class CharCode {
public static void main(String[] args) throws Exception{
System.getProperties().put("file.encoding","iso8859-1");
System.getProperties().list(System.out);
String strChina="中国";
for (int i = 0; i < strChina.length(); i++) {
System.out.println(Integer.toHexString((int)strChina.charAt(i)));
}
byte[] buf = strChina.getBytes();
for (int i = 0; i < buf.length; i++) {
System.out.println(Integer.toHexString(buf[i]));
}
System.out.println(strChina);
for (int i = 0; i < buf.length; i++) {
System.out.write(buf[i]);
}
System.out.println();
}
}
import java.io.*;
public class CharDecoder {
public static void main(String[] args) throws Exception{
System.getProperties().put("file.encoding","iso8859-1");
System.out.println("please enter a Chinese String:");
byte[] buf = new byte[1024];
int ch=0;
int pos=0;
String strInfo=null;
while (true){
ch=System.in.read();
System.out.println(Integer.toHexString(ch));
switch (ch){
case 'r':
break;
case 'n':
strInfo = new String(buf, 0, pos);
for (int i = 0; i < strInfo.length(); i++) {
System.out.println(Integer.toHexString((int)strInfo.charAt(i)));
}
System.out.println(strInfo);
for (int i = 0; i < pos; i++) {
System.out.write(buf[i]);
System.out.println();
return;
}
default:
buf[pos++]=(byte)ch;
}
}
}
}
字节用于表示计算机内存中最原始的数据,不会涉及到编码问题,只有把字节中的内容当做字符来处理时,才会涉及编码问题,所以 IputStreamReader、InputStreamWriter、PrintStream、String 中都有一个可以指定字符集编码参数的构造函数,而Inputstream的构
造函数中则不存在这样的参数。默认的情况下,如果你构造与流相连的 Reader和 Writer,字节和字符之间的转换规则使用默认的平台字符编码和 Unicode,比如在英语国家字节码是用ISO8859-1,用户也可以指定编码格式,具体的格式参考SUN公司的JDK文档首页中的Interationalization超链接部分,在JDK 文档首页中,你还能看到有关Java 的各种特性讲解的超链接,有空进去看看定会让你受益匪浅
其中Java 2 Platform APISpecification 部分就是JDK 中提供的各种类的帮助文档,Java程序员在实际编码的过程中会经常来查阅这一部分,但作者现在使用 FAllimant 整理成的chm 格式的文档,也就是我们前面多次使用的那个帮助系统,会更方便,更有效。我们再来看看为InputStreamReader 指定其他字符集参数,也就是用ISO8859-1替代默认的 GB2312 字符集
import java.io.*;
public class InputReader {
public static void main(String[] args) throws Exception{
InputStreamReader isr = new InputStreamReader(System.in, "iso8859-1");
BufferedReader br = new BufferedReader(isr);
String strLine = br.readLine();
for (int i = 0; i < strLine.length(); i++) {
System.out.println(Integer.toHexString((int)strLine.charAt(i)));
}
isr.close();
System.out.println(strLine);
}
}