RandomAccessFile(随机访问文件)
1.随机访问文件,自身具备读写的方法。
new RandomAccessFile()之后,若文件不存在会自动创建,存在则不创建。——该类其实内部既封装了字节输入流,又封装了字节输出流。
该类若用write()方法写整数,每次只写它的最后一个字节。而采用writeInt()方法,则可把一个整数完整地写入。
2.通过skipBytes(int x),seek(int x)来达到随机访问。
通过seek方法设置数据的指针就可以实现对文件数据的随机读写。InputStream中的skip()方法只能从头往后跳,不能反向;而seek()方法可双向随便定位。
3.数据修改方面的特点
用RandomAccessFile类可以实现数据的修改,当然文件中的数据一般要有规律,以方便在编程时能够进行定位,让数据写对地方。
而用“流”实现数据修改时,则通常需要把数据从流读到数组当中,在数组中进行数据修改,然后再把修改后的数组再重新写到流中。
代码演示:
import java.io.IOException; import java.io.RandomAccessFile; import org.junit.Test; /** * 2018年5月3日 下午3:54:52 * @author <a href="mailto:[email protected]">宋进宇</a> * 演示 随机访问文件 * 数据流可以理解成静态摊在地上, * 由我们根据游标(第一次开时从0开始)在指定位置更更改内容(以byte为单位), * 如果位置计算不准确,那么会把旧数据破坏了。 * 读的时候也要精确计算出从什么位置开始读,读什么类型的数据(多长), * 否则数据读出来也是错误的 */ public class RandomAccessFileDemo { @Test public void t1() throws IOException { RandomAccessFile raf = new RandomAccessFile( "testIO_Files/testRaf.txt", "rw" ); raf.writeInt(100); raf.seek(0); raf.write(97); //查看文件显示内容为 a搀 //搀对应的int值是25600 正好 是100*256 //可以得出显示时 a 是一个字节 这时还有三个字节 显示时后面补了一个字节 再显示出 搀 raf.close(); } //经过测试 可以得出: //RandomAccessFile 应该用来存储有序的数据, //可以推测 数据库 应该就是采用这种方式存储数据 @Test public void t2() throws IOException { RandomAccessFile raf = new RandomAccessFile( "testIO_Files/testRaf.txt", "rw" ); raf.writeInt( 100 ); raf.writeInt( 99 ); raf.write( 97 ); raf.writeUTF("湖南城市学院"); //下面会出异常,因为RandomAccessFile指针是往后走的,这是指先文件末尾 //如果这是进行readInt() 会出现EOFException // int d = raf.readInt(); // System.out.println(d); //应该按下面方式来读 raf.seek(0);//设置从0位置开始读 int d = raf.readInt(); System.out.println(d); //如果不按写入的顺序读取会出现显示的数据是混乱的,虽然文件里面的数据没被改变。 // int a = raf.read(); // System.out.println(a); // int c = raf.readInt(); // System.out.println(c); int c = raf.readInt(); System.out.println(c); // int a = raf.read(); // System.out.println(a); raf.skipBytes(1);//跳过一个字节 System.out.println(raf.readUTF()); raf.close(); } }
序列化
1.序列化
将一个对象存放到某种类型的永久存储器上称为保持。如果一个对象可以被存放到磁盘或磁带上,或者可以发送到另外一台机器并存放到存储器或磁盘上,那么这个对象就被称为可保持的。(在Java中,序列化、持久化、串行化是一个概念。)
java.io.Serializable接口没有任何方法,它只作为一个“标记者”,用来表明实现了这个接口的类可以考虑串行化。类中没有实现Serializable的对象不能保存或恢复它们的状态。
2.对象图
当一个对象被串行化时,只有对象的数据被保存;方法和构造函数不属于串行化流。如果一个数据变量是一个对象,那么这个对象的数据成员也会被串行化。树或者对象数据的结构,包括这些子对象,构成了对象图。
3.瞬时 transient
防止对象的属性被序列化。
代码演示:
Person类(实现序列化接口):
package cn.hncu.obj.ioReinforce.serializable; import java.io.Serializable; public class Person implements Serializable{ private static final long serialVersionUID = 1L; private String name; private int age; //测试发现 通过对象流无法存储瞬时数据也就是 关键字 transient 修饰的变量 private transient String tel; //测试发现 通过对象流无法存储静态的数据 public static int count; //记录new了多少个Person public Person() { this( null, 0, null );//调用带参构造方法,为了统一记录 } public Person(String name, int age, String tel) { super(); this.name = name; this.age = age; this.tel = tel; count++; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getTel() { return tel; } public void setTel(String tel) { this.tel = tel; } @Override public String toString() { return name + ", " + age + ", " + tel + ", " + count; } }
主类:
package cn.hncu.obj.ioReinforce.serializable; import java.io.EOFException; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import org.junit.Test; /** * 2018年5月3日 下午6:17:23 * @author <a href="mailto:[email protected]">宋进宇</a> * 演示序列化: * 被序列化的对象必须要实现Serializable接口 * 序列化时,非静态变量都会存入对象图,静态变量和函数都是不会存入对象图的。 * 如果某个非静态变量不想存入对象图,则可以把它声明成瞬时变量(transient) */ public class SerializableDemo { // 需注意:通过对象流写到文件的对象需要实现序列化接口,否则出异常 @Test public void write() throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testIO_Files/obj.txt")); Person p1 = new Person("Jack", 18, "123456" ); System.out.println( p1 ); Person p2 = new Person("Tom", 20, "7845116" ); System.out.println( p2 ); Person p3 = new Person("张三", 19, "6666666" ); System.out.println( p3 ); oos.writeObject(p1); oos.writeObject(p2); oos.writeObject(p3); oos.close(); } // 注意:通过对象流读取对象时,可以通过捕捉 EOFException 来判断是否读取完毕 @Test public void read() throws IOException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testIO_Files/obj.txt")); while (true) { try { Person obj = (Person) ois.readObject(); System.out.println(obj); } catch (EOFException e) {// 出现这个异常说明文件读取完毕 System.out.println("文件读取完毕"); break; } catch (ClassNotFoundException e) { e.printStackTrace(); } } ois.close(); } }
转换流(InputStreamReader和OutputStreamWriter)
1.转换流功能1:充当字节流与字符流之间的桥梁
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; /** * 2018年5月3日 下午6:57:06 * @author <a href="mailto:[email protected]">宋进宇</a> * 演示把字节流转换成字符流 */ public class TransformIODemo1 { public static void main(String[] args) throws IOException { /* 需求:模拟英文聊天程序,要求: (1) 从键盘录入英文字符,每录一行就把它转成大写输出到控制台; (2) 保存聊天记录到字节流文件。 */ // //1.获取控制台输入流 // InputStream in = System.in; // //2.把字节流转换成字符流 // InputStreamReader isr = new InputStreamReader( in ); // //3.需要拥有一下读取一行的能力,采用套接一层BufferedReader // BufferedReader br = new BufferedReader( isr ); //一气呵成 BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( "testIO_Files/chat.txt" ) ) ); String mes = null; while ( ( mes = br.readLine() ) != null ) { System.out.println( mes.toUpperCase() ); bw.write(mes); //因为要保存成一行一行的形式,需要newLine,不然挤在一起 //可以通过bw.write(mes+"\r\n"); 来换行,但是跨平台性不好。 //比如Window系统和Linux系统,两个系统是不一样的换行风格 bw.newLine(); //因为有缓冲流缘故,为了数据实时性更新,需要刷一下 bw.flush(); } br.close(); bw.close(); } }
2.转换流功能2:字符编码转换
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.Scanner; import org.junit.Test; /** * 2018年5月3日 下午7:14:12 * @author <a href="mailto:[email protected]">宋进宇</a> * 演示字符编码转换 */ public class TransformIODemo2 { //我用的MyEclipse的编码为UTF-8,所以先写一点数据到文件中,保存为GBK编码 @Test public void write() throws IOException { ////////////////编码////////////////////// BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( "testIO_Files/gbk.txt" ), "GBK" ) ); Scanner sc = new Scanner( System.in ); while( sc.hasNext() ) { String str = sc.nextLine(); bw.write( str ); bw.newLine(); bw.flush(); } sc.close(); bw.close(); } //测试在UTF-8编码环境下,读取GBK编码的文件 @Test public void read() throws IOException { BufferedReader br = new BufferedReader( new InputStreamReader( new FileInputStream( "testIO_Files/gbk.txt" ) /*, "GBK" */ ) ); String str = null; while ( ( str = br.readLine() ) != null ) { //可以发现中文的内容是乱码。 System.out.println(str); /* * 经测试 下面这中转换是不行的 因为在br.readLine()这一句时返回的String * 是通过 UTF-8 解码转换成的,如果想要通过下面这种"形式"转换就得通过字节流, * 字符流是无法完成的,因为在br.readLine()这里解码时生成的字节码已经跟存储的字节码值不一样了,所有转换不过来 */ System.out.println(new String( str.getBytes( "UTF-8" ), "GBK" ) ); } br.close(); //////////////演示new String( str.getBytes( "UTF-8" ), "GBK" )形式解码/////////////// BufferedInputStream bis = new BufferedInputStream( new FileInputStream( "testIO_Files/gbk.txt" ) ); byte[] buf = new byte[8]; int len = -1; while ( ( len = bis.read( buf ) ) != -1 ){ //这里之所以能成功是因为buf里面的值跟文件中是一致的,然后通过"GBK"进行解码是可以成功的 //但是需注意:这种解码是不稳定的,截取字节数据时,把一个完整的汉字拆开,会出现某些字段是乱码 System.out.println( new String( buf, 0, len, "GBK" )); } bis.close(); //////////////最好的解码方式,为了加快速度 可以套一层Buffered/////////////// //对比第一个读取文件的 构造 观察不同 InputStreamReader fr = new InputStreamReader( new FileInputStream( "testIO_Files/gbk.txt" ), "GBK" ); char[] cbuf = new char[8]; len = -1; while ( ( len = fr.read(cbuf) ) != -1 ) { System.out.println( String.valueOf( cbuf, 0, len ) ); } fr.close(); } }
打印流(PrintStream和PrintWriter)
1.打印流的特点:
1)只有输出没有输入。PrintStream是字节打印流,PrintWriter是字符打印流。
2)能够方便地打印各种数据“值表示形式”,提供了一系列的打印功能(只有它有,其它流都没有。)
3)和其他输出流不同,它永远不会抛出IOException异常(构造方法除外),异常内部解决且设置了内部标志。
4)可创建具有自动刷新的功能,可使用带换行符的println()方法。
5)(在构造方法中)可以指定字符集编码的。
2.关于打印流的自动刷新
autoFlush - boolean 变量;如果为 true,则 println、printf 或 format 方法将刷新输出缓冲区。---其实是因为这几个方法中帮我们调用了out.flush()。
3.代码演示:
import java.io.FileNotFoundException; import java.io.PrintStream; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import org.junit.Test; /** * 2018年5月5日 下午2:01:33 * @author <a href="mailto:[email protected]">宋进宇</a> * 演示打印字节流和打印字符流 * PrintStream、PrintWriter中的输出有两个方法: * write() ----和字节输出流一样,以字节为单位输出,是数据的原样输出--值--面向计算机 * print() ----把数据转换成字符串输出,不是数据的原样输出--值的表现形式---面向用户的 * 打印流在构造时可以指定字符编码 * 在创建时可以指定为自动刷缓存, 只对println、printf 或 format方法有效 */ public class PrintOutDemo { //演示普通的构造方法,以及常用函数 @Test public void t1() { PrintStream ps = new PrintStream( System.out ); ps.print( "你好" ); ps.println( "Holle" ); ps.write( 97 );//这里打印输出的是'a' 并不像上面print函数那样原样输出, //由此可知:write打印的是真正的数据,面向计算机,通常用来数据传输 //print打印的是数据的表示形式,面向用户,通常用来传递信息 //ps.flush();//这里如果不flush() 则打印不出来'a',由此可知:打印字节流带缓冲 } // 演示带编码转换的构造函数 @Test public void t2(){ PrintStream ps = null; try { //我的MyEclipse设置的工作环境是UTF-8编码的,通过PrintStream //可以实现编码转换。 ps = new PrintStream( "testIO_Files\\print.txt", "GBK" ); ps.print( "中文" ); ps.println( "湖南城市学院" ); ps.write( 353 );//同时这里可以发现结果显示是 'a' 由此可知 字节流只打印 一个字节, //若超过了一个字节只打印最后面的一个字节数据 } catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } finally { if ( ps != null ) { ps.close(); } } } //通过 演示 打印字符流来测试 自动行刷新 即 autoFlush == true @Test public void t3(){ PrintWriter pw = new PrintWriter( System.out , true ); //我们可以发现 PrintWriter 和 PrintStream 在打印时 是不抛异常的 //如果是别的系列的流是会抛异常的。 pw.print( "中国" ); pw.print( "湖南\n" );//对于print函数即使加 '\n' 换行也无法触发 自动行刷新 //观察控制台 明明程序都执行完毕了,而且我们还设置了 自动行刷新 可是为什么控制台没有打印? //pw.println();//加上这一句试试,观察发现 控制台打印出来了 //由此可知 在 用到 自动行刷新 这个属性时只有 println、printf 或 format方法有效 } }
IO包中的其他流
内存(数组)流
用于操作字节数组的流对象,其实它们就是对应设备为内存的流对象。
该流的关闭是无效的,因为没有调用过系统资源。
按照流的读写思想操作数组中元素。
1.字节数组流
ByteArrayInputStream与ByteArrayOutputStream
2.字符数组流
CharArrayReader与CharArrayWriter
3.字符串流
StringReader 与 StringWriter
序列流SequenceInputStream ——对多个流进行合并
将多个流进行逻辑串联(合并变成一个流,操作起来很方便,因为多个源变成了一个源)
代码演示:
import java.io.FileInputStream; import java.io.IOException; import java.io.SequenceInputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; /** * 2018年5月5日 下午2:55:48 * @author <a href="mailto:[email protected]">宋进宇</a> * 演示采用 SequenceInputStream 把 多个流合并 */ public class SequenceIO_Demo { public static void main(String[] args) { ArrayList<FileInputStream> list = new ArrayList<FileInputStream>(); SequenceInputStream sis = null; try { //先在3个文件中自己手写些数据,做下区别以便观察 list.add( new FileInputStream( "testIO_Files\\1.txt" ) ); list.add( new FileInputStream( "testIO_Files\\2.txt" ) ); list.add( new FileInputStream( "testIO_Files\\3.txt" ) ); //采用 Collections 集合的工具类 把 集合 转换成 Enumeration 类型 Enumeration<FileInputStream> en = Collections.enumeration( list ); //在使用 SequenceInputStream 把3个文件输出流 合并成一个输出出口 sis = new SequenceInputStream( en ); //直接打印在控制台 byte[] buf = new byte[128]; int len = -1; while ( ( len = sis.read( buf ) ) != -1 ) { //这里需注意:因为3个文件中是自己手写的一些数据 ,所以是GBK的编码, //即这里需要通过GBK进行解码,但是需注意这种形式解码有风险的。 System.out.println( new String( buf, 0, len, "GBK" ) ); } } catch (IOException e) { e.printStackTrace(); } finally { if ( sis != null) { try { sis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
IO流知识点小结
流是用来传输数据的。
传输数据时,一定要先明确数据源与数据目的地(数据汇)。
数据源可以是文件、键盘或者其他流。
数据目的地可以是文件、显示器或者其他流。
流只是在帮助数据进行传输,并对传输的数据进行处理,比如过滤处理、转换处理等。
IO流体系
使用要点:看顶层(父类共性功能),用底层(子类具体对象)。
命名规律:
每个子类的后缀名都是所属体系的父类的名称,很容易区分所属的体系。
而且每一个子类前缀名都是该子类对象的功能体现。