黑马程序员_java基础视频第21天_JavaIO续集3

 
 
 
 

 ---------------------- android培训、java培训、期待与您交流! ----------------------

 
 
      ---------------------------------------- 对象的序列化:-------------------------------------------
 
对象序列化操作需要使用到ObjectOutputStream和ObjectInputStream两个对象
ObjectOutputStream ( OutputStream  out)
创建一个ObjectOutputStream 把对象往OutputStream输出流写入
ObjectOutputStream也是字节输出流的子类:
java.lang.Object
  java.io.OutputStream
      java.io.ObjectOutputStream
ObjectInputStream是字节输入流的子类:
 
   
java.lang.Object
  java.io.InputStream
      java.io.ObjectInputStream
ObjectOutputStream通过writeObject(Object obj) 
ObjectInputStream通过readObject() 
通过一个简单的程序测试:

publicstaticvoid objetcSeri()throws Exception {

    ObjectOutputStream oos =new ObjectOutputStream(
    new FileOutputStream("person.object"));
    //写入person对象
    oos.writeObject(new Person("johnny",21,"CHINA"));
    oos.close();

}

 

控制台 出现如下错误:
 
  
Exception in thread "main" java.io.NotSerializableException: com.huaxia.day21.Person
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
at java.io.ObjectOutputStream.writeObject(Unknown Source)
at com.huaxia.day21.ObjectSerializable.objetcSeri(ObjectSerializable.java:15)
at com.huaxia.day21.ObjectSerializable.main(ObjectSerializable.java:9)
说明Person这个类没有实现Serializable接口
更正错误后发现硬盘中多了一个person.obejct文件
其内容为一些我们看不懂的字符:

 

那么我们是否通过ObjectInputStream读取到文件的内容呢?

public static void readObject() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.object"));
Person person = (Person)ois.readObject();
System.out.println(person);
} 
控制台输出结果:name:johnny age:21 country:CHINA 
我们来看一下Serializable接口的API

 

 

这意思是说:如果实现了序列化接口的类没有显示声明serialVersionUID变量,序列化运行时将计算一个默认的serialVersionUID的值为这个类基于类的变量方面的值
.我们强烈建议所有实现序列化接口的类都要显示的声明serialVersionUID变量,因为默认的serialVersionUID变量值对于类的修改是非常敏感的,因为他的值就是根据
类成员的签名而生成的而不是系统随机生成的,假设我们对类A进行序列化,在一般情况下我们可以反序列化得到类A的信息,如果我们一旦修改了类A,
那么我们再次反序列化就会出现java.io.InvalidClassException 异常的,因为第一次编译类A的时候他的id是一个值,
你修改类A后在再次编译id的值已经变了.因此为了保证在不同的编译器serialVersionUID变量的值一致性,
所以建议把该变量定义成一个private的常量.
下面来模拟这种情况:现在我修改Person类:如下

 

 

 重新反序列化一次:
 
public static void readObject() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.object"));
Person person = (Person)ois.readObject();
System.out.println(person);
}
 控制台打印店异常:Exception in thread "main" java.io.InvalidClassException: com.huaxia.day21.Person; local class incompatible: 
stream classdesc serialVersionUID = -379036032685453711, 
local class serialVersionUID = 1208026685571330753 
 
用来将文件或者文件夹封装成对象

   ---------------------------------------- 管道流:-------------------------------------------

Java中,可以使用管道流进行线程之间的通信,输入流和输出流必须相连接,这样的通信有别于一般的Shared Data通信,其不需要一个共享的数据空间。

管道流有两个类:
PipedOutputStream管道输出流
java.lang.Object  java.io.OutputStream      java.io.PipedOutputStream
PipedInputStream管道输入流
java.lang.Object java.io.InputStream java.io.PipedInputStream
 
输入输出可以直接连接,通过结合多线程技术 ,如果使用单线程技术,很容易就阻塞了,因为当一个管道读取流去读取数据的时候,如果没有数据那么他就
会一直阻塞在了,我在以前的程序中就已经看到了,那么管道写入流,就没有办法写数据了.所以通过多线程就不会有影响了.
现在我们来实现一个多线程的管道通信流:

package com.huaxia.day21;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
class Read implements Runnable {

	private PipedInputStream is;

	public Read(PipedInputStream is) {
		this.is = is;
	}

	public void run() {
		int len=0;
		byte[] buffer = new byte[1024];
		try {
			//如果不使用循环,如果写入的数据超过1024,那么读的就不完整了
			while((len=is.read(buffer))!=-1){
				System.out.println(new String(buffer,0,len));
			}
			is.close();
		} catch (IOException e) {
			throw new RuntimeException("管道流读取失败");
		}finally{
			if(is!=null)
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
	}

}

class Write implements Runnable {
	private PipedOutputStream os;

	public Write(PipedOutputStream os) {
		this.os = os;
	}

	public void run() {
		try {
			//向管道输出流写入数据
			os.write("hello world!".getBytes());
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(os!=null)
				try {
					os.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
	}

}

public class PipedStreamTest {

	public static void main(String[] args) throws IOException {
		PipedInputStream pis= new PipedInputStream();
		PipedOutputStream pos = new PipedOutputStream();
		//连接管道流,使之能够通信
		pis.connect(pos);
		//启动写入线程
		new Thread(new Write(pos)).start();
		//启动读取线程
		new Thread(new Read(pis)).start();
	}
}
---------------------------------------- 随机访问文件:RandomAccessFile-------------------------------------------
 
首先看一下该类的体系结构:
java.lang.Object
  java.io.RandomAccessFile
从中可以看出该类不是Io体系中的一员而是直接继承Object类的.
该类的实例同时支持随机的读写文件,随机访问文件的行为就像一个大的字节数组存储在文件系统中,在这个数组中有指针或者下标样的东西,
我们可以通过 getFilePointer()方法获取文件当前的指针,并且可以通过 seek(long pos) 方法设置指针的位置.
RandomAccessFile( String name, String mode)
RandomAccessFile( File file, String mode)
通过构造方法可以看出:该只能操作文件,操作文件的模式mode可以为以下的值:
r rw rws rwd
如果操作文件的模式是只读(r),不会创建文件,回去尝试读取指定的文件,如果文件不存在,则会出现异常
如过操作文件的模式是读写(rw),如果文件不存在,就会创建文件,如果文件存在,则不会覆盖文件.
write(int b) 写一个字节,如果这个字节超过8bit,那么写入最低8bit
writeInt(int v) 把这个整型当作4个字节写入文件,最高8位先写
skipBytes(int n) 跳过n个字节,不能往回跳
seek(long pos) 设置下标的位置,下标是从0开始
getFilePointer() 获取当前的指针位置

 
对于 RandomAccessFile 要重点掌握一下几点
1, write(int b)与 writeInt(int v)的区别
2,学会使用 seek(long pos) 实现随机读取和随机写入
3,还要注意数据写入要具有规律,以便更好的使用seek方法实现方便的功能.
 
下面就对于上面的要求来学习一下?
首先比较一下 write (int b)与 writeInt (int v)的区别:
RandomAccessFile raf =new RandomAccessFile("random.txt","rw");
raf.write(97);
raf.close();
发现文件如下所示:


 

我写入97,那么为什么在txt文件中会显示一个a呢?因为 write(int b)把指定的一个字节(4bit)写入到文件中,那么要在文本中显示,显示的肯定是字符而不是
二进制代码,所以它会根据编码表去查发现这个二进制代码所对应的字符是a,所以显示的是a.
我们把上面的 程序中的write方法的参数改为258,如:
RandomAccessFile raf =new RandomAccessFile("random.txt","rw");
raf.write(258);
raf.close();
发现文件如下所示:

 

因为通过验证我们知道258所对应的二进制是 100000010 我们发现这是9位,超过了一个字节8位,而write(int)方法又是写一个字节的,
所以会出现数据丢失的情况.
那我们可以验证一下,第一个程序使用 RandomAccessFile读取出来的数据是97,那么说明在文件文件中显示a,是正确的,
第二个程序也使用RandomAccessFile读取文件,如果读取出来的数据是257,那也说明程序是正确的,反之为错误!
验证第一个程序:
public static void readFile() throws IOException{
		RandomAccessFile raf = new RandomAccessFile("random.txt","r");
		System.out.println(raf.readInt());
	}
发现控制台输出的是97,说第一个程序是正确的,
验证第二程序,把参数改成258,重新写一遍,然后在读取 发现控制台输出为2,为什么写入的是257,因为
258所对应的二进制是100000010共9位 而write(int)方法又是写一个字节,最低8位,所以前面的1没有写入,所以最终写入的
00000010,而00000010对应的十进制又是2,所以输出2了.
那么我们怎么解决呢?我们发现API中是这样表述 writeInt(int v)方法的
writeInt(int v)
          Writes an int to the file as four bytes, high byte first.
说明该方法写入的是4个字节,而不是1个字节,所以适合使用这个:
RandomAccessFile raf = new RandomAccessFile("random.txt","rw");
        raf.writeInt(258);
        raf.close();
对应的读取的方法也要改变:
RandomAccessFile raf =new RandomAccessFile("random.txt","r");
System.out.println(raf.readInt());
输出的结果是258,说明是正确的,所以当写入int的时候应该使用writeInt(int),对应的也要使用readInt()读取.
 
现在使用一下 seek (long pos) 方法实现随机写入和随机读取.
写入数据
	public static void writeFile() throws IOException{
		RandomAccessFile raf = new RandomAccessFile("random.txt","rw");
		//下面写入两个人名和其年龄
		raf.write("张三".getBytes());
		raf.writeInt(23);
		raf.write("李四".getBytes());
		raf.writeInt(35);
		raf.close();
	}


然后读取
	public static void readFile() throws IOException{
		RandomAccessFile raf = new RandomAccessFile("random.txt","r");
		byte[] buffer = new byte[4];
		raf.read(buffer);//读取4个字节
		int age = raf.readInt();//再读取4个字节
		System.out.println("name="+new String(buffer)+"\tage="+age);
	}
控制台输出: name=张三 age=23
现在我想读取第二个人,怎么读取呢?
	public static void readFile_2() throws IOException{
		RandomAccessFile raf = new RandomAccessFile("random.txt","r");
		raf.seek(8*1);
		byte[] buffer = new byte[4];
		raf.read(buffer);//读取4个字节
		int age = raf.readInt();//再读取4个字节
		System.out.println("name="+new String(buffer)+"\tage="+age);
	}
控制台输出:
name=李四 age=35
原理图:



 

如果现在想在第4个人的位置加一个人?原理也还是一样的设置了下标就可以了
	public static void writeFile_2() throws IOException{
		RandomAccessFile raf = new RandomAccessFile("random.txt","rw");
		raf.seek(8*3);
		//下面写入两个人名和其年龄
		raf.write("王五".getBytes());
		raf.writeInt(99);
		raf.close();
	}

在读取的时候设置一下下标.控制台输出正常: name=王五 age=99
 
 
-------------------------------操作基本数据类型的流对象-----------------------------------
操作基本数据类型的流对象包括:
java.lang.Object
  java.io.InputStream
      java.io.FilterInputStream
          java.io.DataInputStream
java.lang.Object
  java.io.OutputStream
      java.io.FilterOutputStream
          java.io.DataOutputStream
一个数据输出流(data out stream)让应用程序把基本的数据类型写入一个输出流当中,可以通过data input stream 读取数据
它操作基本数据类型非常方便,如果使用其他的流对象,来获取基本数据类型,必须要进行类型转换,而在使用data stream则不用如此麻烦,但是要注意
读取的时候要严格按照顺序读取.因为各个基本数据类型的字节数不是相同的,如果不按照顺序怎会出现乱码的情况.
只有一个构造方法 DataOutputStream( OutputStream out) DataInputStream( InputStream in)
例如:
	public static void writeData() throws IOException {
		DataOutputStream dos = new DataOutputStream(new FileOutputStream(
				"dataType.txt"));
		dos.writeInt(109);
		dos.writeDouble(123.089);
		dos.writeBoolean(true);
		dos.close();
	}
	
	public static void readData() throws IOException {
		DataInputStream dis = new DataInputStream(new FileInputStream(
				"dataType.txt"));
		int num = dis.readInt();
		double d = dis.readDouble();
		boolean b = dis.readBoolean();
		System.out.println("int:"+num+"\tdouble:"+d+"\tboolean:"+b);
	}

输出结果:int:109 double:123.089 boolean:true

 
接下来看一下 writeUTF( String str) 方法:
如果你用该方法往某个文件写入字符串,那么你不能用其他的方式读取只能通过 readUTF( DataInput in)
就算使用转换流,使用UTF-8字符集读取这个文件也不行,因为 writeUTF( String str)使用UTF修改版字符集往文件中写数据的.
我们来看一下UTF-8和UFT修改版的区别:
	public static void writeData_2() throws IOException {
		DataOutputStream dos = new DataOutputStream(new FileOutputStream(
		"utfdata.txt"));
		dos.writeUTF("你好中国");
		dos.close();
	}
发现文件内容如下所示:

文件大小为:

 

 现在我们使用换行流往文件中写入相同的数据:

	public static void writeData_3() throws IOException {
		OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("uft-8data.txt"),"utf-8");
		osw.write("你好中国");
		osw.close();
	}

发现utf-8与utf修改版不管是内容还是大小都是不同的.
现在我们使用转换流,字符集为utf-8可不可以读取用UTF修改版的字符集写入的数据呢?
	public static void readData_2() throws IOException {
		InputStreamReader isr = new InputStreamReader(new FileInputStream("utfdata.txt"),"utf-8");
		BufferedReader bw =new BufferedReader(isr);
		//因为是测试,数据只有一行 就不循环
		System.out.println(bw.readLine());
	}


 

	public static void readData_3() throws IOException {
		DataInputStream dis = new DataInputStream(new FileInputStream("utfdata.txt"));
		System.out.println(dis.readUTF());
	}


 

 

-------------------------操作字节数组的流-----------------------------------
 
java.lang.Object
  java.io.InputStream
      java.io.ByteArrayInputStream
ByteArrayInputStream(byte[] buf) 
ByteArrayInputStream(byte[] buf, int offset, int length) 
java.lang.Object
  java.io.OutputStream
      java.io.ByteArrayOutputStream
ByteArrayOutputStream() 
ByteArrayOutputStream(int size) 
我们知道在字节流中本来就封装了字节数组,那为什么还要出现这操作字节数组的流呢?
ByteArrayInputStream包含一个内部缓冲区,该缓冲区包含从流中读取的字节,内部计数器跟踪read方法
要提供的下一个字节,关闭 ByteArrayInputStream无效,此类的方法在关闭流 后任然可以被调用,不会产生IO异常
为什么关闭了流对象还可以使用该类里面的方法呢?
我们查看该类的源代码:
publicvoid close()throws IOException {
}
发现该方法只是一个空实现,为什么?因为该类操作的只是字节数组,没有和底层的资源相关联.所以只是一个空实现.
 

 
ByteArrayOutputStream此类实现了一个输出流,数据被写入一个字节数组中,缓冲区会随着数据的不断写入而自动增长,
可以使用toByteArray()和toString()方法获取数据.使用该流的时候不用指定目的地,因为在内部它有一个可变长度的字节数组,
这就是它的目的地.也可把数据写到一个输出流 writeTo( OutputStream out)
现在可以总结一下:
源:键盘(System.in) 硬盘(FileStream即能够操作文件的流) 内存(ArrayStream即操作数组的流)
目的:控制台(System.out) 硬盘(FileStream即能够操作文件的流) 内存(ArrayStream即操作数组的流)
 
这是字节数组流,如果我们操作的是字符呢?使用它的话就不方便了,使用字符数组流就方便了 CharArrayReader和CharArrayWriter.还有直接操作字符串的StringReader和StringWriter
它们和字节数组流很相似,这里就不多介绍了.
 
 
----------------------------字符编码-----------------------------------------
 
字符编码牵涉到两个概念:编码和解码;那么什么时候会出现编码什么时候出现解码呢?
当把字符写入文件中,也就是字符向字节数组转换需要编码;
当把文件打开显示给用户看,也就是字节数组向字符转换需要解码;
 
例如:
	public static void main(String[] args) throws UnsupportedEncodingException {
		String value = "你好中国";
		byte[] b = value.getBytes();// 默认按gbk编码
		System.out.println(Arrays.toString(b));
		System.out.println(new String(b));
	}

输出结果:


因为都是相同的字符编码所以输出正常;
假设我们在解码的时候使用 iso8859-1进行解码,情况会怎么样呢?
把上面的
String decode =new String(b);//默认按gbk解码
改为: String decode =new String(b,"iso8859-1");//默认按gbk解码,因为你的字符编码可能写错,所以会抛出UnsupportedEncodingException

发现出现了乱码,因为你使用的是,gbk进行编码的,当使用UTF-8进行解码的时候,在UTF-8编码表里面查找 [-60, -29, -70, -61, -42, -48, -71, -6]
所对应的字符,而这些数字对应的字符又是我们看不懂的字符,所以会出现乱码情况!
 
难道我们使用了错误的字符集进行解码后,就没有办法使数据恢复吗?
当然有!
	public static void main(String[] args) throws UnsupportedEncodingException {
		String value = "你好中国";
		byte[] b = value.getBytes();// 默认按gbk编码
		System.out.println(Arrays.toString(b));
		String decode = new String(b, "iso8859-1");// 默认按gbk解码
		System.out.println(decode);

		byte[] b2 = decode.getBytes("iso8859-1");
		System.out.println(Arrays.toString(b2));
		System.out.println(new String(b,"gbk"));

	}
这个程序就可以解决这个问题,输出结果为:

那么它的原理是什么呢?我们画一个图来解释:

 

以前在学习JSP的时候,发现通过get方法提交的表单,如果在输入框里填写中文,当在服务器端获取该数据的时候就会出现乱码,自己在网上找到了
解决办法,
String name = request.getParameter("username");
name = new String(name.getBytes("iso8859-1"),"gbk");
可是但是并不知道为什么要这样,现在听了老师的讲解,懂了!
 
 关于"联通"的字符编码:
 我们做一个小实验,新建一个记事本,在里面写入两个字"联通",保存,然后关闭.再打开,发现文件里面的数据乱码!这是为什么?

	public static void main(String[] args) {
		String value = "联通";
		byte[] b = value.getBytes();
		for(byte by : b){
			//打印它的二进制形式
			System.out.println(Integer.toBinaryString(by&255));
		}
	}


 

输出结果为
11111111111111111111111111000001
11111111111111111111111110101010
11111111111111111111111111001101
11111111111111111111111110101000
获取其最低8bit
System.out.println(Integer.toBinaryString(by&255));
11000001
10101010
11001101
10101000
我们查看UTF的编码描述:
1,所有的字符在'\u0001'到'\u007f'都用一个byte描述,格式如下:

我们知道GBK每两个字节表示一个字符,而UTF-8不是,那么在底层字节流在读取的时候,她怎么知道几个字节表示一个字符呢?

现在通过上面的图发现,它是有自己的规则.
 
我们发现"联通"的二进制代码和上面的的UTF描述的第2条很像,每个字符的第一个字节都是110开头,每个字符的第二个字节都是10开头,
所以我们在上面的试验中才会出现那样的错误,当我们保存"联通"后,记事本发现它很像UTF的特征,所以它用UTF-8去编码!
解决办法: 
在联通前面随便加几个汉字即可,因为记事本一开始发现不像UTF-8的编码特征,所以就使用ANSI去编码 那么 什么是ANSI编码呢?
这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。
在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文 操作系统 下,ANSI 编码代表 JIS 编码。

 

 

 ---------------------- android培训、java培训、期待与您交流! ----------------------

你可能感兴趣的:(黑马程序员,java,string,exception,byte,buffer,stream)