黑马程序员——>第二十一天<io流(对象序列化-管道流-字符编码)>

来源:互联网 发布:车工钳工 知乎 编辑:程序博客网 时间:2024/05/05 08:48

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

01IO流(对象的序列化)

对象的序列化:  把对象存放在硬盘上,这叫做对象的持久化存储(找一个能长期保存数据的介质),  也叫对象的序列化
   能操作对象的流   ObjectOutputStream 将java对象的基本数据和图形写入OutputStream,可以使用ObjectInputStream读取(重构)对象      通过在流中使用文件可以实现对象的持久存储

import java.io.*;class ObjectStreamDemo { public static void main(String[] args) throws Exception {  //writeObj();  写的时候开这个  readObj();   //  读的时候开这个 } public static void readObj()throws Exception  // 读取 {  ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt")); // 和下面相对应           一般不存成.txt (因为里边的东西看不懂,打开没意义)   存成 person.object  Person p = (Person)ois.readObject();  System.out.println(p);  ois.close(); } public static void writeObj()throws IOException {  ObjectOutputStream oos =      // 创建对象   将对象文件里边去  所以用 FileOutputStream   new ObjectOutputStream(new FileOutputStream("obj.txt"));  括号里边传字节流//你要写的是一个对象,能直接操作对象的那个文件 Object   这些类出现都是在对更细节类进行操作。  oos.writeObject(new Person("lisi0",399,"kr"));  //调用写入对象    存储到了硬盘上      //  这个person在另一个类中有单独的定义  oos.close(); }}import java.io.*;class Person implements Serializable  //  如果不实现这个接口  会导致  上面的编译失败{                                                  //给类定义标记用的     这就是实现Serializable接口 public static final long serialVersionUID = 42L;   //这个接口没有方法 (称为   标记接口)                                   //上面的uid标示是给编译器使用的         给一个固定标示  就是为了序列化方便(新的类还能去操作曾经被序列化的对象)                                     //  uid   其实是根据你类中的那些成员算出来的。你类中  无论成员变量还是成员函数都具备一个数字标示(数字签名)                                                                                             //   根据这些数字算出来这么一个号   来标示这个类 private String name;                 // 若这里的private改变或不写  那个标记接口就起作用了 transient int age;                //若不想将非静态的成员(age)序列化 这时加一个关键字修饰一下就可以了transient                                   //这时  age即使在堆内存当中  也不能被序列化     (保证其值在堆内存中存在而不在文本文件中存在) static String country = "cn";  // 静态时不能被序列化的   读不出来                                    //静态在方法去里  而其他的在堆内存中(能把堆里边的序列化,却不能将其他地方的序列化) Person(String name,int age,String country) {  this.name = name;  this.age = age;  this.country = country; } public String toString() {  return name+":"+age+":"+country; }}

02IO流(管道流)

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

读取流和写入流通常之间没有关系,中间有中转站时才能具备一定关系
  读取流  将数据存到数组中    写入流通过数组对数据进行操作

管道流  可以让两个接上        可以对接到一起的流对象
  就一根管子   这边写  那边读  到底哪边先执行呢

管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从PipedInputStream对象读取,并由其他线程将其写入到相应的PipedOutputStream。不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程                怎样将两个连接起来呢
  这个是涉及到多线程技术的io流对象                            
                                                                                      
集合中涉及到io流的是Properties                                   
io流涉及到多线程的就是管道流PipedInputStream        

PipedInputStream(PipedOutputStream src)   直接在构造函数时就把对方扔进来  
这是读取流,你new对象时,把输出流传进来就可以了

若是空参数PipedInputStream() 得有相对应的connect(PipedOutputStream src)方法

 

 

一个读  一个写   得有两个方法

import java.io.*;class Read implements Runnable  { private PipedInputStream in;                         //希望这个类一初始化就有一个管道进来 Read(PipedInputStream in)                            //一初始化就进来   构造函数 {  this.in = in; } public void run()                                           // 覆盖run方法 {  try                                                                //覆盖run方法  不能抛异常  {   byte[] buf = new byte[1024];                                        //读数据   System.out.println("读取前。。没有数据阻塞");   int len = in.read(buf);                                                // 往buf里读数据     System.out.println("读到数据。。阻塞结束");    String s= new String(buf,0,len);                                //管道流里边读到的数据   System.out.println(s);   in.close();  }  catch (IOException e)  {   throw new RuntimeException("管道读取流失败");  } }}class Write implements Runnable{ private PipedOutputStream out; Write(PipedOutputStream out) {  this.out = out; } public void run() {  try  {   System.out.println("开始写入数据,等待6秒后。");     Thread.sleep(6000);   out.write("piped lai la".getBytes());                                  // 写数据   out.close();  }  catch (Exception e)  {   throw new RuntimeException("管道输出流失败");  } }}class  PipedStreamDemo{ public static void main(String[] args) throws IOException {  PipedInputStream in = new PipedInputStream();                             // 创建管道流对象  PipedOutputStream out = new PipedOutputStream();  in.connect(out);                                                                //两个流要接上  Read r = new Read(in);      Write w = new Write(out);                                              //将in和out传进来  new Thread(r).start();                                                   // 新的线程  new Thread(w).start();                                               // 又一个新的线程 }}


03IO流(RandomAccessFile) 

 结尾处没有父类名  自成一派   直接继承  object

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

  此类的实例支持对随机访问文件的读取和写入(另类中的另类 即能读  又能写 )。随机访问文件的行为类似存储在文件系统中的一个大型byte数组。存在指向该隐含数组的光标或索引,称为文件指针(这个对象内部封装一个数组而且封装指针,可以把数据仍在数组里边来,扔完后对数据进行操作,就是给数组元素设置 获取数组元素(读和写),);输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针   那么他通过文件指针可以对数据进行操作   该文件指针可以通过getFilePointer 方法读取,并通过seek方法设置

RandomAccessFile

该类不算是IO体系中子类。
而是直接继承自Object。

但是它是IO包中成员。因为它具备读和写功能。
内部封装了一个数组,而且通过指针对数组的元素进行操作。
可以通过getFilePointer获取指针位置,
同时可以通过seek改变指针的位置。

因为存储的是一个byte数组  所以是字节流  不是字符流
其实完成读写的原理就是内部封装了字节输入流和输出流。   操作数据必然是流

通过构造函数可以看出,该类只能操作文件
而且操作文件还有模式:只读r,,读写rw等。

如果模式为只读 r。不会创建文件。会去读取一个已存在文件,如果该文件不存在,则会出现异常。
如果模式rw。操作的文件不存在,会自动创建。如果存在则不会覆盖。

      和曾经学过的输出流不一样的地方  输出流  一new对象就要覆盖文件,

 public static void readFile()throws IOException {  RandomAccessFile raf = new RandomAccessFile("ran.txt","r");  //建立对象  这里r只读 屏蔽了写动作    faf.write("haha".getBytes());  这个不行  你在调用写的方法// 文件中的数据都在数组当中,可以调整指针来取数据  //调整对象中指针。  //raf.seek(8*1);将指针放在角标为8的位置上  可以通过指针的偏移取到这个数组当中任意一段数据   前提  保证这个数据是有规律的   没规律取的时候就费尽了  *1是取第二个人的数据。。。   seek前后都能指  所以用的比skipBytes大的多  //跳过指定的字节数  raf.skipBytes(8);   //尝试跳过输入的n个字节以丢弃跳过的字节  很遗憾  不能往回跳  byte[] buf = new byte[4];  raf.read(buf);  //往buf里读一次   装四个字节  String name = new String(buf);   //把数组变成字符串  int age = raf.readInt();  // 从此文件读取一个有符号的32位整数  四个字节  System.out.println("name="+name);  System.out.println("age="+age);  raf.close(); }


 

流在操作数据时在按照顺序写  按照顺序读  而这个可以随机写和读   不仅可以读写 还能进行修改

 public static void writeFile_2()throws IOException {  RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");  raf.seek(8*3);    //若*0  就会覆盖原数据    不仅可以读写 还能进行修改  raf.write("周期".getBytes());  raf.writeInt(103);  raf.close(); } public static void writeFile()throws IOException {  //读写都一样  注意它自己的自身特点  RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");  //创建对象  raf.write("李四".getBytes());  raf.writeInt(97);  raf.write("王五".getBytes());  raf.writeInt(99);   //按四个字节将int写入该文件,先写高字节  raf.close(); }  //write方法只写int类型的最低八位}raf.write(258) // 输出结果不是258而是一些字符:   258  100000010  而只取最低八位 00000010 少个1 数据丢失//要保证数据原样性:  1。我写数字输出一堆字符 查表了  2.  丢失数据raf.writeInt()


作用:
  可以实现数据的分段写入      下载软件的原理   实现了多线程的下载
用一般流就挂了   一般流是从头往后写  会导致这个数据虽然都存完了,可是读出来是错误的,不是一个完整的文件,解码会出现错误


04IO流(操作基本数据类型的流对象DataStream)


操作基本数据类型
  DataInputStream   DataOutputStream

 object里边也有这些方法 但是  object更倾向于操作对象   而  datainputstream更倾向于操作这些数据类型

所以你仅为操作基本数据类型的话 用这个对象时非常方便的

操作字节数组
  ByteArrayInputStream   ByteArrayOutStream
操作字符数组
  CharArrayReader   CharArrayWrite
操作字符串
  StringReader    StringWriter
在进行构造时  
DataOutputStream(OutputStream out)   就是让流和数据相结合的对象
本身又结合数据的功能   要传一个流进来


特殊方法:
  writeUTF(String str)   以与机器无关方式使用UTF-8修改版 编码将一个字符串写入基础输出流

如果要用这种方式写入字符串,只能用它对应的方式读出来,用转换流读不出来

/*
DataInputStream与DataOutputStream

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

05IO流(ByteArrayStream)
操作字节数组
   可以操作字节数组中数据的流对象
  ByteArrayInputStream  
 包含了一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪read方法要提供的下一个字节。  关闭ByteArrayInputStream 无效(这个流对象  没有调用底层资源)。此类中的方法在关闭此流后仍可被调用,而不会产生任何IOException(里边的缓冲区 将源数据都存放到数组里边去了  根本就没有和键盘 硬盘打交道,不涉及到底层资源调用问题)

  读取时一建立对象初始化时就必须要有数据源存在
  ByteArrayOutStream

此类实现了一个输出流,其中的数据被写入一个byte数组。缓冲区会随着数据的不断写入而自动增长(可变长度)。可使用toByteArray() 和toString()获取数据(不用flush)
 关闭ByteArrayInputStream 无效(都存数组里边去了,也不涉及底层资源操作)。此类中的方法在关闭此流后仍可被调用,而不会产生任何IOException


这个目的已经被封装到了对象内部
 这个对象里边封装的就是一个可变长度的字节数组(目的),  源和目的一样有

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

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

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

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


在流操作规律讲解时:

源设备,
 键盘 System.in,硬盘 FileStream,内存 ArrayStream。  数组流
目的设备:
 控制台 System.out,硬盘FileStream,内存 ArrayStream。


用流的读写思想来操作数据。

06IO流(转换流的字符编码)

字符编码 

 字符流的出现为了 方便 操作字符
 更重要的是加入了编码转换
 通过子类转换流来完成                                 还有两个
 InputStreamReader     加入编码表的流对象     PrintStream
 OutputStreamWriter        PrintWriter   但是  只能打印   不能读取 

所以 还是用 转换流比较好


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

表码表的由来
  计算机只能识别二进制数据,早期由来是电信号
  为了方便应用计算机,让它可以识别各个国家的文字
  就将各个国家的文字用数字来表示,并一一对应,形成一张表
  这既是表码表


常见的表码表

  ASCII:美国标准信息交换码
      用一个字节的7位可以表示
  ISO8859-1:拉丁码表。   欧洲码表
      用一个字节的8位表示
  GB2312:中国的中文编码表    俩字节   高位都是1 为了和美国的区别开   要兼容  美国的 因为中国的 有abc
  GBK:中国的中文编码表升级,融合了更多的中文字符号。
  Unicode:国际标准码,融合了多种文字
      所有文字都用两个字节来表示,Java语言使用的就是Unicode
  UTF-8:最多用三个字节来表示一个字符

 
 

import java.io.*;class EncodeStream { public static void main(String[] args) throws IOException  {   //writeText();   readText(); } public static void readText()throws IOException  // 转换流  读 {  InputStreamReader isr = new InputStreamReader(new FileInputStream("utf.txt"),"gbk");  char[] buf = new char[10];  int len = isr.read(buf);  String str = new String(buf,0,len);  System.out.println(str);  isr.close(); } public static void writeText()throws IOException  {  OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utf.txt"),"UTF-8");   // 转换流    写  osw.write("你好");  osw.close(); }}


 

??  用  gbk   编码    用utf-8解码
浣犲  用  utf-8 编码    用gbk解码

07字符编码



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

String-->byte[];  str.getBytes(charsetName);


byte[] -->String: new String(byte[],charsetName);


 

import java.util.*;class  EncodeDemo{ public static void main(String[] args)throws Exception  {  String s = "哈哈";  byte[] b1 = s.getBytes("GBK");  //变成数组就是在编码  System.out.println(Arrays.toString(b1));  // 把数组变成字符串  String s1 = new String(b1,"utf-8");   //字节变字符   解码   utf 这里变成iso  System.out.println("s1="+s1);//存硬盘上变成了字节        编码,  //用记事本打开  变成了 字符   解码   //对s1进行iso8859-1编码。   编译失败  因为  这个表 不识别中文  找的全都是废码  ??  byte[] b2 = s1.getBytes("utf-8");  //  对上面的 进行iso编码  System.out.println(Arrays.toString(b2));  // 产生一个新数组  String s2 = new String(b2,"gbk");  //对产生的新数组 进行解码  用gbk  System.out.println("s2="+s2);                   }                                          }    

                                              
  若  你好   编码为gbk    解码为iso8859-1    怎么解决乱码

           而编码为gbk    解码为 utf-8     因为两个都识别中文   所以  用上面那种方法得出的结果还是乱码

 08字符编码- 联通

联通两个字打入到记事本中  中保存   然后打开就成��ͨ
当你在双击打开文本的时候,启动了记事本应用程序, 解码出的问题。

class EncodeDemo2 { public static void main(String[] args) throws Exception {  String s = "联通";  //将联通进行拆解  byte[] by = s.getBytes("gbk");  // 用gbk表码表对其进行编码  for(byte b : by)   {   System.out.println(Integer.toBinaryString(b&255));//变成二进制   &225   取后八位  }  System.out.println("Hello World!"); }}

gbk   两个字节代表一个字符  而  utf-8是三个字节代表一个字符    都是负数 凭什么要读两个 读三个  怎么确定(涉及到转换流式怎么操作的)

gbk表里 中文表示都是负数

FileReader
InputStreamReader(new FileInputStream(""),"gbk") ;     而 字节流式一个字节一个字节的读

一次先读两个字节  然后去查表   查表一次   read()-->返回一个中文   这就是字符流的特点

可是这个是utf-8编的码,要是两个两个读 就挂了。  应该三个三个的读 去查utf-8的编码表  (u-8里  有可能两个字节表示一个文字,有可能三个字节表示一个文字,到底读两个  读三个不确定)  其实 u-8编码表对这个字节都加了一个表示头信息   这个表示头信息就可以确定  该一次读一个字节还是两个字节 或者是三个字节

               一个字节表示      两个字节表示       三个字节表示

 头位              0                         110                  1110

第二个                                        10                    10

第三个                                                                 10
 


用这个来判断  是读一个还是读两个  还是读三个


联通的二进制位表现
   用gbk编码         产生的二进制形式跟utf-8一致
11000001        110
10101010        10
11001101        110
10101000        10

因为当记事本进行解码时   看到  第一个是110  有可能是gbk编码  再看第二个字节 10   直接就去查utf-8编码表

   明明是用gbk编码  但是解码时去查utf-8   那就挂了

  因为联通的二进制编码形式整好符合utf-8的格式

解决:

  在记事本中联通前随便写入一个汉字  就可以让记事本识别   应该用哪个编码表来解码了

这个是gbk和utf-8的重合点


09练习
/*
有五个学生,每个学生有3门课的成绩,
从键盘输入以上数据(包括姓名,三门课成绩),
输入的格式:如:zhagnsan,30,40,60计算出总成绩,
并把学生的信息和计算出的总分数高低顺序存放在磁盘文件"stud.txt"中。

1,描述学生对象。
2,定义一个可操作学生对象的工具类。

思想:
1,通过获取键盘录入一行数据,并将该行中的信息取出封装成学生对象。
2,因为学生有很多,那么就需要存储,使用到集合。因为要对学生的总分排序。
 所以可以使用TreeSet。
3,将集合的信息写入到一个文件中。


*/

import java.io.*;import java.util.*;class Student implements Comparable<Student>   //实现 一个比较接口{ private String name; private int ma,cn,en; private int sum;        Student(String name,int ma,int cn,int en) //  学生一初始化就都有了 {  this.name = name;  this.ma = ma;  this.cn = cn;  this.en = en;  sum = ma + cn + en; } public int compareTo(Student s) {  int num = new Integer(this.sum).compareTo(new Integer(s.sum)); // 总分进行比较  if(num==0)   return this.name.compareTo(s.name);  return num; }     //结果会是自然排序  从低到高   这时要  强行逆转比较器  public String getName()   //获取 名字 {  return name; } public int getSum()  // 获取总分 {  return sum; } public int hashCode()     //覆盖hashCode()   你不确定就有可能存到hashCode里边去 {  return name.hashCode()+sum*78; } public boolean equals(Object obj)  // 两个学生进行比较 {  if(!(obj instanceof Student))   throw new ClassCastException("类型不匹配");  Student s = (Student)obj;  return this.name.equals(s.name) && this.sum==s.sum; } public String toString() {  return "student["+name+", "+ma+", "+cn+", "+en+"]"; }}class StudentInfoTool      // 学生工具类{ public static Set<Student> getStudents()throws IOException      //没有比较器的方法    {  return getStudents(null);   //这个不需要比较器 传null就行   默认排序 }                                           // 定义的方法  需要比较器 往这里传 public static Set<Student> getStudents(Comparator<Student> cmp)throws IOException  //创建集合类 {   //有比较器的方法  BufferedReader bufr =         // 读取键盘   new BufferedReader(new InputStreamReader(System.in));  String line = null;  // 循环读  //Set<Student> stus = new TreeSet<Student>();  //创建一个学生集合  Set<Student> stus  = null;  if(cmp==null)   stus = new TreeSet<Student>();  //那就new一个不带比较器的集合  else   stus = new TreeSet<Student>(cmp);  / 带比较器的集合  while((line=bufr.readLine())!=null)   //循环读  {   if("over".equals(line))    break;      String[] info = line.split(",");   //如果不是就 用,进行切割      Student stu = new Student(info[0],Integer.parseInt(info[1]),  // 把姓名封装成对象          Integer.parseInt(info[2]),          Integer.parseInt(info[3]));               //因为成绩是整数   应该把整数转换一下Integer.parseInt   stus.add(stu);   // 将学生对象放到集合当中  }  bufr.close();  //关闭集合  return stus; }                                        // 集合传进来 public static void write2File(Set<Student> stus)throws IOException   //集合有了  接下来就把集合中的信息写到文件当中去 {  BufferedWriter bufw = new BufferedWriter(new FileWriter("stuinfo.txt"));                         //先有流   目的     目的是个文件 需要缓冲区    定义一个数据存储位置  for(Student stu : stus)   //遍历  {   bufw.write(stu.toString()+"\t");  // 将学生姓名写出去   bufw.write(stu.getSum()+"");            // 总分写出去   bufw.newLine();  //换行     bufw.flush();    //带缓冲区 一定要  flush  刷新  } // 循环当中把数据都写到 你定义的存储位置上去  bufw.close();  //最后关闭 }}class StudentInfoTest { public static void main(String[] args) throws IOException {  Comparator<Student> cmp = Collections.reverseOrder();  // 强行逆转比较器   传比较器reverseOrder() // 翻转比较器  Set<Student> stus = StudentInfoTool.getStudents(cmp); //调用getStudents  返回来一个集合  StudentInfoTool.write2File(stus); }}



 

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

原创粉丝点击