Java---IO---深入IO
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:aaa@qq.com">宋进宇</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:aaa@qq.com">宋进宇</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:aaa@qq.com">宋进宇</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:aaa@qq.com">宋进宇</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:aaa@qq.com">宋进宇</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:aaa@qq.com">宋进宇</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流体系
使用要点:看顶层(父类共性功能),用底层(子类具体对象)。
命名规律:
每个子类的后缀名都是所属体系的父类的名称,很容易区分所属的体系。
而且每一个子类前缀名都是该子类对象的功能体现。
IO流的操作规律
下面对3、4点进行详细说明
上一篇: Android亮度调节的几种实现方法