Java学习总结之第十二章 Java I/O系统
一、基本使用总结
1. read方法是阻塞方法,也就是如果流对象中无数据可以读取时,则read方法会阻止程序继续向下运行,一直到有数据可以读取为止。
2. 由于‘\’是Java语言中的特殊字符,所以在代码内部书写文件路径时,例如代表“c:\test\java\Hello.java”时,需要书写成“c:\\test\\java\\Hello.java”或“c:/test/java/Hello.java”,这些都需要在代码中注意。
3. File类中包含了大部分和文件操作的功能方法,该类的对象可以代表一个具体的文件或文件夹。
4. 常见的字节输入流:
输入流 | 描述 |
ByteArrayInputStream | 把字节数组转换为输入流 |
FileInputStream | 从文件中读取数据 |
StringBufferInputStream | 把字符串转换为输入流。这个类已被废弃,取而代之的是StringBufferReader |
PipedInputStream | 连接一个PipedOutputStream |
SequenceInputStream | 把几个输入流转换为一个输入流 |
ObjectInputStream | 对象输入流 |
FilterInputStream | 装饰器,具有扩展其他输入流的功能 |
5. 常见的字节输出流:
输出流 | 描述 |
ByteArrayOutputStream | 向字节数组(即内存的缓冲区)中写数据 |
FileOutputStream | 向文件中写数据 |
PipedOutputStream | 向管道中输出数据,与PipedInputStream搭配使用 |
ObjectOutputStream | 对象输出流 |
FilterOutputStream | 装饰器,扩展其他输出流的功能 |
6. 在Java平台中,有以下两种方式能获得本地平台的字符编码类型:
a) System.getProperty(“file.encoding”);
b) Charset cs = Charset.defaultCharset(); 注:Charset类位于java.nio.charset包中。
7. Reader类能够将输入流中采用其他编码类型的字符转换为Unicode字符,然后在内存中为这些Unicode字符分配内存。Writer类能够把内存中的Unicode字符转换为其他编码类型的字符,再写到输出流中。默认情况下,Reader和Writer会在本地平台和字符编码和Unicode字符编码之间进行转换,如图所示:
如果要输入或输出采用特定类型编码的字符串,可以使用InputStreamReader类和OutputStreamWriter类,在它们的构造方法中可以指定输入流或输出流的字符编码。如图下示:
8. 常见的Reader类:
Reader类型 | 描述 |
CharArrayReader | 适配器,把字符数组转换为Reader,从字符数组中读取字符 |
BufferedReader | 装饰器,为其他Reader提供读缓冲区,此外,它的readLine()方法能够读入一行字符串 |
LineNumberReader | 装饰器,为其他Reader提供读缓冲区,并且可以跟踪字符串输入流中的符号 |
StringReader | 适配器,把字符串转换为Reader,从字符串中读取字符 |
PipedReader | 连接一个PipedWriter |
FilterReader | 装饰器,扩展其他Reader的功能 |
PushBackReader | 装饰器,能够把读到的一个字符压回到缓冲区中。通常用做编译器的扫描器,在程序中一般很少使用它 |
InputStreamReader | 适配器,把InputStream转换为Reader,可以指定数据源的字符编码 |
FileReader | 从文件中读取字符 |
9. 常见的Writer类:
Writer类型 | 描述 |
CharArrayWriter | 适配器,把字符数组转换为Writer,向字符数组中写字符 |
BufferedWriter | 装饰器,为其他Writer提供写缓冲区 |
StringWriter | 适配器,把StringBuffer转换为Writer,向StringBuffer中写字符 |
PipedWriter | 连接一个PipedReader |
FilterWriter | 装饰器,扩展其他Writer的功能 |
PrintWriter | 装饰器,输出格式化数据 |
OutputStreamWriter | 适配器,把OutputStream转换为Writer,可以指定数据源的字符编码 |
FileWriter | 向文件中写字符 |
二、装饰流的使用
1. 比较常用的装饰流有DataInputStream/DataOutputStream和BufferedReader/BufferedWriter。装饰流不可以单独使用,必须本命实体流或装饰流进行使用。
2. 只有使用DataOutputStream流格式写入的数据,才可以使用DataInputStream进行读取,以下是DataOutputStream/DataInputStream的使用实例:
//将一定格式的数据写入test.my文件中。 //MyData.java /** * 模拟要存储到文件中的数据 * 该类中保存4种类型的数据 */ public class MyData { boolean b; int n; String s; short sh[]; public MyData(){} public MyData(boolean b,int n,String s,short sh[]){ this.b = b; this.n = n; this.s = s; this.sh = sh; } } //WriteFileUseDataStream.java import java.io.DataOutputStream; import java.io.FileOutputStream; import java.io.IOException; /** * 使用DataOutputStream书写具有一定格式的文件 */ public class WriteFileUseDataStream { public static void main(String[] args){ short[] sh = {1,3,134,12}; MyData data = new MyData(true,100,"Java语言",sh); //写入文件 writeFile(data); } /** * 将MyData对象按照一定格式写入文件中 * @param data 数据对象 */ public static void writeFile(MyData data){ FileOutputStream fos = null; DataOutputStream dos = null; try{ //建立文件流 fos = new FileOutputStream("test.my"); //建立数据输出流,流的嵌套 dos = new DataOutputStream(fos); //依次写入数据 dos.writeBoolean(data.b); dos.writeInt(data.n); dos.writeUTF(data.s); //写入数组 int len = data.sh.length; dos.writeInt(len); //数组长度 //依次写入每个数组元素 for(int i=0;i<len;++i) dos.writeShort(data.sh[i]); }catch(Exception e){ e.printStackTrace(); }finally{ try { dos.close(); fos.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } //ReadFileUseDataStream.java import java.io.DataInputStream; import java.io.FileInputStream; /** * 使用DataInputStream读取自定义格式的文件 */ public class ReadFileUseDataStream { public static void main(String[] args) { MyData data = readFile(); System.out.println(data.b); System.out.println(data.n); System.out.println(data.s); int len = data.sh.length; for(int i=0;i<len;++i) System.out.println(data.sh[i]); } /** * 从文件test.my中读取数据,并使用读取到的数据初始化data对象 * @return 读取到的对象内容 */ public static MyData readFile(){ MyData data = new MyData(); FileInputStream fis = null; DataInputStream dis = null; try{ //建立文件流 fis = new FileInputStream("test.my"); //建立数据输入流,流的嵌套 dis = new DataInputStream(fis); //依次读取数据,并赋值给data对象 data.b = dis.readBoolean(); data.n = dis.readInt(); data.s = dis.readUTF(); int len = dis.readInt(); data.sh = new short[len]; for(int i=0;i<len;++i) data.sh[i] = dis.readShort(); }catch(Exception e){ e.printStackTrace(); }finally{ try{ dis.close(); fis.close(); }catch(Exception e){ e.printStackTrace(); } } return data; } } |
3. IO类的最主要的桥接流有两个:InputStreamReader和OutputStreamWriter。前者实现将InputStream及其子类的对象转换为Reader体系类的对象,实现将字节输入流转换为字符输入流;后者实现将OutputStream及其子类的对象转换为Writer体系类的对象,实现将字节输入流转换为字符输入流。需要注意的是,字符流无法转换为字节流。
4. IO类中有一组提高 读写效率的类,包括BufferedInputStream/BufferedOutputStream、BufferedReader/BufferedWriter。使用示例如下,该代码实现的功能是如果回显用户输入,当用户输入quit时程序退出。:
import java.io.BufferedReader; import java.io.InputStreamReader; public class ReadConsoleWithBuffer { public static void main(String[] args) { BufferedReader br = null; String s = null; try{ //使用流的嵌套构造缓冲流 br = new BufferedReader(new InputStreamReader(System.in)); do{ //输出提示信息 System.out.println("请输入:"); //按行读取输入 s = br.readLine(); //输出用户输入 System.out.println(s); }while(!s.equals("quit")); }catch(Exception e){ e.printStackTrace(); }finally{ try{ br.close(); }catch(Exception e){ e.printStackTrace(); } } } } |
三、标准I/O
1. java.lang.System类提供了三个静态变量:
a) System.in:为InputStream类型,代表标准输入流,默认的数据源为键盘。
b) System.out:为PrintStream类型,代表标准输出流,默认的数据源为控制台。
c) System.err:为PrintStream类型,代表标准错误输出流,默认的数据源是控制台。
2. System类提供了一些用于重定向流的静态方法:
a) setIn(InputStream in):对标准输入流重定向。
b) setout(PrintStream out):对标准输出流重定向。
c) setErr(PrintStream out):对标准错误输出流重定向。
四、对象的序列化和反序列化
1. 只有实现了java.io.Serializable接口的类的对象才能被序列化和反序列化。JDK类库中的有些类(比如String类、包装类和Date类等)都实现了Serializable接口。
2. 对象的序列化包括以下步骤:
a) 创建一个对象输出流,它可以包装一个其他类型的输出流。
b) 通过对象输出流的writeObject()方法写对象。
3. 对象的反序列化包括以下步骤:
a) 创建一个对象输入流,它可以包装一个其他类型的输入流。
b) 通过对象输入流的readObject()方法读取对象。
4. 必须保证向对象输出流写对象的顺序与从对象输入流读对象的顺序一致。
5. 通过,对象中的所有属性都会被序列化,对于一些比较敏感的信息(比如用户的口令),应该禁止对这种属性进行序列化。解决的办法是把这些属性用transient修饰,这样它就不会参与序列化及反序列化过程。
6. 如果希望进一步控制序列化及反序列化的方式,可以在类中提供一个readObject()和writeObject()方法,当ObjectOutputStream/ObjectInputStream对一个对象进行序列化和反序列化时,如果该对象具有writeObject()或readObject()方法时,就会执行这一方法,否则就按默认方式序列化和反序列化。在writeObject()方法中可以调用ObjectOutputStream的defaultWriteObject()方法,使得对象输出流执行默认序列化操作;在readObject()方法中可以调用ObjectInputStream的defaultReadObject()方法,使得对象输入流执行默认反序列化操作。以下是示例代码:
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class User implements Serializable{ private String name; private transient String password; public User(String name,String password){ this.name = name; this.password = password; } public String toString(){ return name+" "+password; } /** * 加密数组,将buff数组中的每个字节的每一位取反 * 例如13的二进制形式为00001101,取反后为11110010 */ private byte[] change(byte[] buff){ for(int i=0;i<buff.length;++i){ int b = 0; for(int j=0;j<8;++j){ int bit = (buff[i]>>j & 1) == 0 ? 1 : 0; b += (1<<j)*bit; } buff[i] = (byte)b; } return buff; } private void writeObject(ObjectOutputStream stream) throws IOException{ stream.defaultWriteObject(); //先按默认方式序列化 stream.writeObject(change(password.getBytes())); } private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException{ stream.defaultReadObject(); //先按默认方式反序列化 byte[] buff = (byte[])stream.readObject(); password = new String(change(buff)); } public static void main(String[] args) throws Exception { User user = new User("Tom","123456"); System.out.println("Before Serializable:"+user); ByteArrayOutputStream buf = new ByteArrayOutputStream(); //把User对象序列化到一个字节缓存中 ObjectOutputStream o = new ObjectOutputStream(buf); o.writeObject(user); //从字节缓存中反序列化User对象 ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray())); user = (User)in.readObject(); System.out.println("After Serializable:"+user); } } |