黑马程序员——IO流(其他流)

来源:互联网 发布:stage淘宝官网 编辑:程序博客网 时间:2024/06/06 18:13


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

一、

ObjectInputStream 和 ObjectOutputStream

对象存储在堆内存中,当使用完了就会被当做垃圾回收,对象就不存在了
可以通过流的方式将堆内存对象存储在硬盘上,即使程序结束,
对象也不会消失,想要使用时,直接读取存储对象的文件即可

这种方式被称为对象的序列化

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

ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。
可以使用 ObjectInputStream 读取(重构)对象。

构造方法:ObjectOutputStream(OutputStream out)
          创建写入指定 OutputStream 的 ObjectOutputStream。

方法:writeObject(Object obj) 将指定的对象写入 ObjectOutputStream。
   当要序列化的对象不能实现java.io.Serializable 接口。
   方法抛出NotSerializableException

Serializable:类通过实现 java.io.Serializable 接口以启用其序列化功能。
     未实现此接口的类将无法使其任何状态序列化或反序列化。
     可序列化类的所有子类型本身都是可序列化的。
     序列化接口没有方法或字段,仅用于标识可序列化的语义。

ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。

//将一个对象写入到一个文件中
 public static void writeObj()throws IOException
 {      //所存储对象的文件一般不存成.txt,这里应该存成 Person.Object
  ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));

  oos.writeObject(new Person("lisi",39));//调用写对象方法(已建立好的Person类)
  

  oos.close();
 }

 //将写入文件的对象读出来
 public static void readObj()throws Exception
 {
  ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));

  //调用读对象方法,返回值类型是Object,因为对象是Person,返回值类型可以直接写Person
  Person p = (Person)ois.readObject();//需要强制转换成类类型
  //此方法抛出 ClassNotFoundException(类没有找到异常)
  System.out.println(p);
  ois.close();
 }

二、

DataInputStream 和 DataOutputStream

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

DataOutputStream:
 writeUTF(String str)
          以与机器无关方式使用 UTF-8 修改版编码将一个字符串写入基础输出流。
DataInputStream:
 readUTF(DataInput in)
          从流 in 中读取用 UTF-8 修改版格式编码的 Unicode 字符格式的字符串;然后以 String 形式返回此字符串。
    抛出:EOFException - 如果此输入流在读取所有字节之前到达末尾。


public static void writeUTFDemo()throws IOException
 {
  DataOutputStream dos = new DataOutputStream(new FileOutputStream("utfdata.txt"));
  dos.writeUTF("你好");
  dos.close();
 }

 public static void readUTFDemo()throws IOException
 {
  DataInputStream dis = new DataInputStream(new FileInputStream("utfdata.txt"));
  String s = dis.readUTF();
  System.out.println(s);
  dis.close();
 }
 
 public static void writeData()throws IOException
 {
  DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));

  //直接操作基本数据类型
  dos.writeInt(234);
  dos.writeBoolean(true);
  dos.writeDouble(9887.543);

  dos.close();
 }
 
 public static void readData()throws IOException
 {
  DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
  
  //读取时要按顺序读取
  int i = dis.readInt();
  boolean b = dis.readBoolean();
  double d = dis.readDouble();

  System.out.println("i="+i);
  System.out.println("b="+b);
  System.out.println("d="+d);

  dis.close();
 }

三、

PrintStream 和 PrintWriter

打印流:
该流提供了打印方法,可以将各种数据类型的数据原样打印

字节打印流:
PrintStream
构造函数可以接收的参数类型:
1,file对象。File
2,字符窜路径。String
3,字节输出流。OutputStream

字符打印流:
PrintWriter
1,file对象。File
2,字符窜路径。String
3,字节输出流。OutputStream
4,字符输出流。writer


public static void main(String[] args) throws IOException
 {
  BufferedReader bufr =
   new BufferedReader(new InputStreamReader(System.in));
  //PrintWriter out = new PrintWriter(new FileWriter("a.txt"),true); //写入到文件       
  PrintWriter out = new PrintWriter(System.out,true);//定义一个打印流,输出在控制台上
  String line = null;     //此处用true下面就不用flush刷新,只有println,printf,format刷新缓冲区
  while((line=bufr.readLine())!=null)
  {
   if("over".equals(line))
    break;
   out.println(line.toUpperCase());//打印流的输出方法
   out.flush();
  }
  out.close();
  bufr.close();
 }

四、

PipedInputStream 和 PipedOutputStream

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

通常,数据由某个线程从 PipedInputStream 对象读取,
并由其他线程将其写入到相应的 PipedOutputStream。
不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。

//管道输入流线程
class Read implements Runnable
{
 private PipedInputStream in;
 Read(PipedInputStream in)
 {
  this.in = in;
 }
 public void run()
 {
  try
  {
   //管道流读数据
   byte[] buf = new byte[1024];
   int len = in.read(buf);
   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
  {
   //管道流写数据
   out.write("piped lai la".getBytes());
   out.close();
  }
  catch (IOException e)
  {
   throw new RuntimeException("写出失败!");
  }
 }
}


五、

ByteArrayInputStream 和 ByteArrayOutputStream

操作字节数组的流对象
 ByteArrayInputStream和ByteArrayOutputStream

ByteArrayInputStream:
 ByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。
 关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
 在构造的时候,需要接受数据源,而且数据源是一个字节数组
ByteArrayOutputStream:
 此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray() 和 toString() 获取数据。
 关闭 ByteArrayOutputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
 在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组,这就是数据的目的地

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

在流操作规律时讲解:
源设备:
 键盘 System.in 硬盘 FileStream 内存 ArrayStream
目的设备:
 控制台 System.out 硬盘 FileStream 内存 ArrayStream

用流的读写思想来操作数组

同理:
操作字符数组:
 CharArrayReader  CharArrayWriter
操作字符串:
 StringReader  StringWriter

public static void main(String[] args)
 {
  //数据源          //此处接受的是字节数组
  ByteArrayInputStream bis = new ByteArrayInputStream("abcdefg".getBytes());

  //数据目的
  ByteArrayOutputStream bos = new ByteArrayOutputStream();

  int by = 0;

  while((by=bis.read())!=-1)
  {
   bos.write(by);
  }

  System.out.println(bos.size());//打印目的数组的长度
  System.out.println(bos.toString());
 }

六、

SequenceInputStream

序列流:SequenceInputStream对多个流进行合并

作用:表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,
直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。

就是将多个流对象拼成一个流对象

需求:将三个文件复制到一个文件中
用SequenceInputStream将三个文件合并,再写入另一个文件

public static void main(String[] args) throws IOException
 {
  Vector<FileInputStream> v = new Vector<FileInputStream>();
  //使用集合Vector将三个流对象添加进去
  v.add(new FileInputStream("1.txt"));
  v.add(new FileInputStream("2.txt"));
  v.add(new FileInputStream("3.txt"));
  Enumeration<FileInputStream> en = v.elements();
  //构造函数接受Enumeration,集合中只有Vector使用Enumeration
  SequenceInputStream sis = new SequenceInputStream(en);

  //上面代码既实现了多个流的合并,形成了一个流对象,下面用输出流将其写入文件
  FileOutputStream fos = new FileOutputStream("4.txt");
  byte[] buf = new byte[1024];
  int len = 0;
  while((len = sis.read(buf))!=-1)
  {
   fos.write(buf,0,len);
  }
  fos.close();
  sis.close();
 }

七、

字符编码

字符编码
 字符流的出现为了方便操作字符
 更重要的是加入了编码转换
 通过子类转换流来完成
  InputStreamReader
  OutputStreamWriter
 在两个对象进行构造的时候可以加入字符集
编码表的由来
 计算机只能识别二进制数据,早期由来是电信号
 为了方便应用计算机让它可以识别各个国家的文字
 就将各个国家的文字用数字来表示,并一一对应,形成一张表
 这就是编码表
常见的编码表
 ASCII:美国标准信息交换码
  用一个字节的7位可以表示
 ISO8859-1:拉丁码表,欧洲码表
  用一个字节的8位表示
 GB2312:中国的中文编码表
 GBK:中国的中文编码表升级,融合更多的中文文字符号
 Unicode:国际标准码,融合了多种文字
  所有文字都用两个字节来表示,java语言使用的就是Unicode
 UTF-8:最多用三个字节来表示一个字符

/*
编码:字符串变成字节数组
 String-->byte[]:str.getBytes(charseName);

解码:字节数组变成字符串
 byte[]-->String:new String(byte[].charseName);

*/
import java.util.*;
class  EncodeDemo
{
 public static void main(String[] args) throws Exception
 {  
  demo_2();
  
 }
 public static void demo_1()throws Exception
 {
  String s = "你好";

  //编码。默认情况下是GBK
  byte[] b = s.getBytes("GBK");//UnsupportedEncodingException抛出异常
  System.out.println(Arrays.toString(b));//打印编码对应的数
  //解码,就是将字符数组变成字符串
  String s1 = new String(b,"GBK");
  System.out.println("s="+s);
 }
 public static void demo_2()throws Exception
 {
  String s = "你好";
  //按照GBK进行编码
  byte[] b1 = s.getBytes("GBK");
  System.out.println(Arrays.toString(b1));
  //按照ISO8859-1解码,解码错误,会出现乱码
  String s1 = new String(b1,"ISO8859-1");
  System.out.println("s1="+s1);
  //出现乱码就要解决:将得到的乱码再进行编码
  byte[] b2 = s1.getBytes("ISO8859-1");
  System.out.println(Arrays.toString(b2));
  //然后再按照GBK解码,就能得到原来的数据
  String s2 = new String(b2,"GBK");
  System.out.println("s2="+s2);

//上诉方法对于出现乱码的解决是通用的,但当解码错误时,是按照UTF-8解的码
//就不能使用一上面的方法,因为GBK和UTF-8都识别中文,所以用上诉方法还是会出现乱码
//Tomcat服务器默认使用的是ISO8859-1编码表
 }
}

/*
新建一个记事本文件,写入'联通',保存在打开以后发现出现了乱码,
这是因为解码时出现了问题,当存储的时候是按照GBK来存储的,
再次打开时,记事本软件是按照UTF-8来解码的,所以出现了乱码
那么为什么会出现这种现象呢?

UTF-8的码表从最低一个字节表示一个字符到最高三个字节表示一个字符
当它解码时怎么确定这一个字符是几个字节表示的呢?

怎么确定此时是读一个字节、两个字节还是三个字节,来返回这个字符呢?
GBK用两个字节代表一个字符,而UTF-8最高用三个字节来代表一个字符
当解码时,UTF-8有它自己的编码标志,符合这一标志,记事本就自动按照
UTF-8来解得码,那么UTF-8的编码是什么样的呢?

Utf-8的码表其实对它的字节都加了标识头信息,用此信息就能识别
解码时是读一个、两个还是三个字节

'\u0001' 到 '\u007F' 范围内的所有字符都是用单个字节表示的:

         位值
字节 1   0    位 6-0
 

null 字符 '\u0000' 以及从 '\u0080' 到 '\u07FF' 的范围内的字符用两个字节表示:

         位值
字节 1   1 1 0   位 10-6
 
字节 2   1 0     位 5-0
 


'\u0800' 到 '\uFFFF' 范围内的 char 值用三个字节表示:
         位值
字节 1   1 1 1 0   位 15-12
 
字节 2   1 0       位 11-6
 
字节 3   1 0       位 5-0
 
想要解决这个问题很简单,只要“联通”前面还有别的文字就可以了

*/
class  EncodeDemo2
{
 public static void main(String[] args) throws Exception
 {
  String s = "联通";
  byte[] by = s.getBytes("GBK");//编码
  for(byte b : by)
  {
   //打印编码是得到的每个字节的十进制数
   //System.out.println(b);
   //打印结果:-63 -86 -51 -88
   //用二进制打印一下,保留最低八位,&255
   System.out.println(Integer.toBinaryString(b&255));
            /*打印结果:11000001  打印结果显示“联通”所对应的二进制数
                        10101010  正好和UTF-8的编码标识头对应上了,所以
      11001101  在解码的时候,记事本就按照UTF-8码表了
      10101000*/
  }
 }
}




0 0
原创粉丝点击