欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java---IO---深入IO

程序员文章站 2024-03-03 19:21:04
...

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流的操作规律    

Java---IO---深入IO

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

Java---IO---深入IO







相关标签: Java IO 深入Io