黑马程序员——Java基础——对象序列化、管道流、RandomAccessFile类、操作基本数据类型的流对象、字符编码

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ------

一、IO流——对象序列化

1、概述

   IO流中的其他成员

   ObjectInputStream 和 ObjectOutputStream

  被操作的对象需要实现Serializable(标记接口)

从后缀可以看出,这个是个字节流,从前缀可以看出,是可以直接操作对象的流。

这个对象有什么意义呢?我们知道,流是用来操作数据的,现在数据被封装成对象了。对象本身存在于堆内存中,我们知道,使用完,对象变为垃圾,在堆内存中就被释放了。我们可以通过这个流对象,把它存放到硬盘上。

堆内存中的对象,我们知道是封装有数据的。这些数据也随着对象存在硬盘上,即使程序结束了,这个数据还是存放在硬盘上,当再次使用程序,需要使用前面所产生的数据,只要把存放数据的文件拿出来读一下,就可以了。

把对象存放到硬盘上,这叫做对象的持久化存储,或者叫做对象的序列化。

 ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。

2、方法

      1) 构造方法

         ObjectOutputStream(OutputStream out)  创建写入指定 OutputStream 的 ObjectOutputStream

          构造的时候,就要明确目的。 它可以操作基本的数据类型。

     2write(int)和 writeInt(int)的区别

     Write(int)是只写入int类型的最低八位,是字节流的共性方法

      writeInt(int)写入的是32位的int值。

   3、练习

需求:把数据存放到文件中。

 

 

注意事项:

  1)需要序列化的对象必须实现java.io.Serializable接口

    这种接口称之为标记接口。就是给这个类盖个章原理是:这个接口是给类加上一个serialVersionUID:

  ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

 每个类如果被序列化的话,都有一个ID标识,通常是给编译器所使用的。因为类被持久化以后,这个类可以被改变。再重新编译的话,会产生一个新的序列号。新的序列号和旧的序列号不匹配。它就用ID号去判断。

UID不是随机的,是根据类中的成员算出来的,类中的成员,无论是成员变量还是成员函数,都有一个数字标识,它就是根据这些标识算出的ID号。

2)静态是不能被序列化的。因为静态是在方法区里面的,它只能给堆内存里面的数据序列化。

3)如果对于非静态数据也不想对其序列化的话,可以加上一个关键字修饰下。transient  int age;

二、IO流——管道流

     1、概述

      PipedInputStream 和 PipedOutputStream

     输入和输出可以直接进行连接,通过结合线程使用。

    通常我们流的读写操作都需要数组等中转站,才能使用,但是管道流的读写可以在一起操作。

   但是管道流这种方式,有一种问题:到底谁先执行呢?读写是需要顺序的。

       public class PipedInputStream  extends  InputStream

管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。

IO流与其他技术相结合的技术

集合中涉及到IO流的,是Properties

IO流中涉及到多线程的,就是管道流

2、方法 

构造函数

PipedInputStream(PipedOutputStream src) 

          创建 PipedInputStream,使其连接到管道输出流 src。  

如果创建一个空连接的对象时,需要调用对象中的void connect(PipedOutputStream src) 方法去连接。

演示:

 

 

 

 

谁先执行并不重要,重要的是read()是阻塞式方法,读不到数据就会等。

三、  IO流——RandomAccessFile

      1、概述 

       RandomAccessFile     随机访问文件,自身具备读写的方法通过skipBytes(int x),seek(int x)来达到随机访问。

      该类不算是IO体系中的子类,而是直接继承自Object,但是它是IO包中的成员,因为它具备读和写功能。内部封装了一个byte数组,而且通过指针对数组的元素进行操作。

可以通过getFilePointer获取指针位置,同时可以通过seek改变指针的位置。

      其实,完成读写的原理就是内部封装了字节输入流和输出流。

局限性:通过构造函数,可以看出该类只能操作文件。

而且操作文件还有模式。只读:读写:rw

     如果模式为只读r,不会创建文件,会去读取一个已存在文件,如果该文件不存在,会出现异常,如果模式为rw,该对象的构造函数要操作的文件不存在,会自动创建,如果存在,则不会覆盖。(和流不同)

  2、方法

   

 

读取

 

需求: 第四个位置写入周七

 

3、RandomAccessFile的应用:它可以实现数据的分段写入。

    把要写入的数据分段以后,一个线程负责一段,不会发生冲突。这就是下载软件的实现原理。

如果用一般的流就挂了,因为一般流都是从头往后写。

三、IO流——操作基本数据类型的流对象

   1、概述

   操作基本数据类型

   DataInputStream和 DataOutputStream

   操作字节数组

   ByteArrayInputStream 和 ByteArrayOutputStream

   操作字符数组

   CharArrayReader与 CharArrayWriter

   操作字符串

   StringReader 与 StringWriter

 2DataInputStream    DataOutputStream

    可以用于操作基本数据类型的流对象

  2.1特殊方法:

   void writeUTF(String str) 

          以与机器无关方式使用 UTF-8 修改版编码将一个字符串写入基础输出流。 

       也就是说,用UTF-8写进去的,只能用这个方法读出来,不然读不出来。

 

 

 

 

3、ByteArrayStream

   1)概述

   用于操作字节数组的流对象。

     操作字节数组

   ByteArrayInputStream 和 ByteArrayOutputStream

       public class ByteArrayInputStream  extends InputStream  

    ByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。 

 也就是说,这个对象一建立,一定要有数据源存在。数据的读写都是这样。比如操作文件的流FileInputStream一建立,就必须传一个文件进来

       public class  ByteArrayOutputStream  extends OutputStream

         此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray() 和 toString() 获取数据。 

  2)方法

   构造函数

   ByteArrayInputStream(byte[] buf) 

          创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组。 

    ByteArrayInputStream(byte[] buf, int offset, int length) 

          创建 ByteArrayInputStream,使用 buf 作为其缓冲区数组。 

   这个流对象,不像操作文件的流,它并不调用底层资源,所以,文档中提到,关闭流对象是无效的。

而且,此类中的方法在关闭流后,仍然可以调用此方法,不会产生异常。  

ByteArrayOutputStream() 

          创建一个新的 byte 数组输出流。 

ByteArrayOutputStream(int size) 

          创建一个新的 byte 数组输出流,它具有指定大小的缓冲区容量(以字节为单位)。 

发现,这个对象建立的时候,不需要目的,其实它有目的,目的被封装在内部,它的内部定义了一个字节数组作为缓冲区。

说明:

ByteArrayInputStream:在构造的时候,需要接收数据源,而且数据源是一个字节数组

ByteArrayOutputStrean:在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。这就是数据目的地。

因为这两个流对象都操作的数组,并没有使用系统资源,所以不用进行close

3)和数组的区别

A将操作都封装,方便使用

B更重要的,对数组的操作只有两种:设置值,获取值。反映到IO中就是读和写。这是用流的读写思想在操作数组。更加方便简洁。

4)操作其他基本数据类型的流对象

    操作字符数组

   CharArrayReader与 CharArrayWriter

   操作字符串

   StringReader 与 StringWriter

 

5)流操作规律:

   源设备:键盘 System.in,硬盘FileStream,内存ArrayStream

   目的设备:控制台System.out,硬盘FileStream,内存ArrayStream

四、IO流——转换流的字符编码

   1、概述

   1)字符编码

      字符流的出现为了方便操作字符。

       更重要的是加入了编码转换

        通过子类转换流来完成。

          InputStreamReader

          OutputStreamWriter

      在两个对象进行构造的时候可以加入字符集。

     IO流中,涉及到编码表的流,转换流和PrintStreamPrintWriter

  2)编码表的由来

       计算机只能识别二进制数据,早期由来是电信号。

    为了方便应用计算机,让它可以识别各个国家的文字,就将各个国家的文字用数组来表示并一一对应,形成一张表。这就是编码表。

   常见的编码表

ASCII:美国标准信息交换码

              用一个字节的7位可以标识。

ISO8859-1:拉丁码表。欧洲码表。

          用一个字节的8位标识

GB2312:中国的中文编码表。

GBK:中国的中文表码表升级,融合了更多的中文文字符号。兼容ASCII码。用两个字节来表示。两个字节的高位都是1. 

Unicode:国际标准码,融合了多种文字。

         所有文字都用两个字节来表示Java语言使用的Unicode

 UTF-8:最多用三个字节来表示一个字符。

有两种以上的码表都可以解析中文,就有可能出现乱码。

  2、转换流的编码应用

    1)可以将字符以指定编码格式存储

    2)可以对文本数据指定编码格式来解读

   指定编码表的动作由构造函数完成

3、演示

   

4、字符编码方法

   编码:字符串编程字节数组

  解码:字节数组变成字符串。

  String------Byte[];  str.getBytes();

  Byte[]-------String: new String(byte[])

  String(byte[] bytes, String charsetName) 

          通过使用指定的 charset 解码指定的 byte 数组,构造一个新的 String

  byte[] getBytes(String charsetName) 

          使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。

 

注意:

1ISO8859-1根本不能识别中文。如果编错了,没法解码。

但是如果编码对了,解码错了,

如:  Byte[] b1=s.getBytes(GBK);

String s1= new String(b1,ISO);

2)用ISO8859-1编码,用GBK解码的情况:

 

这种情况,在实际开发中经常遇见。

比如:用户在浏览器中输入网址,提交服务端,服务端装有Tomcat服务器,它默认的解码方式ISO8859-1

现在用户用GBK编码的数据“张三”发到服务端,服务端用ISO解码,解出来为????。

  这时候,在服务端,只能够做的就是,用ISO再编码一次,再进行解码。

但是特殊情况:

如果我们不是用ISO编错了,是用UTF-8编错了呢?

千万注意,这时候,就不能再用UTF-8再编码一次,进行解码了,解出来的是乱码,还是上例,用UTF-8演示

结果:

[-60,-29,-70,-61]

S1=???

[-17,-65,-67,-17,-65,-67,-17,-65,-67]

S2=乱码

没拿着源码。

原因:它拿着字节码到UTF-8中去查,查不着,就拿着这字节码到未知区域去查,就返回乱码。

为什么?因为UTF-8GBK都识别中文。

5、特殊例子:联通

     新建一个文本文件,输入“联通”,保存,再打开,?出现下图现象。

我们存联通的时候,默认是GBK编码,没问题,当我们再打开的时候,是解码出现了问题。

解释 联通的问题

其实,UTF-8的码表给数据都加上头标识信息。它根据标识头信息,就可以确定一次要读一个字节,还是两个字节,还是三个字节。

参看   java.IO.DataInputStream 下的static String readUTF(DataInput in) UTF-8修改版的内容。 

如果是单自己来表示的,话,头位是0

由两个字节来表示的话,第一个字节,头是110 第二个字节,头饰10

 由三个字节来表示的话,第一个字节,头是1110  第二个字节头 是10 第三个字节头是10

演示

 
 
class EncodeDemo2
{
public static void Main(String[]args) throws Exception
{
String s="联通";
byte[] by=s.getBytes(“GBK”);
for(byte b:by)
{
System.out.println(Integer.toBinaryString(b&255));
}
}
}

“联通”产生的二进制数字,和UTF-8的格式一致。直接查UTF-8的码表去了。

怎么解决呢?

联通前面加个汉字就可以。它一判断头信息,不符合UTF-8的格式,就用GBK解码了。

 


------- Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值