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:[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流体系

    使用要点:看顶层(父类共性功能),用底层(子类具体对象)。

    命名规律:
          每个子类的后缀名都是所属体系的父类的名称,很容易区分所属的体系。
          而且每一个子类前缀名都是该子类对象的功能体现。

IO流的操作规律    

下面对3、4点进行详细说明








猜你喜欢

转载自blog.csdn.net/qq_34928644/article/details/80188463