Java笔记(二十八)……IO流下 IO包中其他常用类以及编码表问题

PrintWriter打印流

Writer的子类,既可以接收字符流,也可以接收字节流,还可以接收文件名或者文件对象,非常方便

同时,还可以设置自动刷新以及保持原有格式写入各种文本类型的print方法

PrintWriter的小例子:打印字符录入的大写

   1: //读取键盘录入,打印大写
   2: private static void printWriterMethod() throws IOException
   3: {
   4:     BufferedReader bufr =
   5:         new BufferedReader(new InputStreamReader(System.in));
   6:  
   7:     PrintWriter out = new PrintWriter(System.out,true);
   8:  
   9:     String line = null;
  10:  
  11:     while( (line = bufr.readLine()) != null)
  12:     {
  13:         if("over".equals(line))
  14:             break;
  15:         //只需一条语句,便可打印一行数据,非常方便
  16:         out.println(line.toUpperCase());
  17:     }
  18:     
  19:     out.close();
  20:     bufr.close();
  21: }

SequenceInputStream合并流

序列流,可将多个流合并成一个流,按序列进行读取

可手动指定各个流创建对象,也可将多个流存入集合,利用枚举Enumeration来创建对象

合并和分割流的小例子:

   1: import java.io.*;
   2: import java.util.*;
   3:  
   4: class SequenceInputStreamDemo 
   5: {
   6:     public static void main(String[] args) throws IOException
   7:     {
   8:  
   9:         int num = splitFile(new File("pic.jpg"));
  10:  
  11:         /*
  12:         合并分割后的流
  13:         */
  14:         
  15:         //定义Vector集合存储所有part文件的字节输入流
  16:         Vector<FileInputStream> v = new Vector<FileInputStream>();
  17:  
  18:         for(int i = 1 ; i <= num ; i ++ )
  19:         {
  20:             v.add(new FileInputStream(i+".part"));
  21:         }
  22:  
  23:         Enumeration<FileInputStream> en = v.elements();
  24:  
  25:         FileOutputStream fos = new FileOutputStream("pic1.jpg");
  26:         
  27:         //定义序列流,通过枚举合并所有的输入流
  28:         SequenceInputStream sis = new SequenceInputStream(en);
  29:  
  30:         byte[] buf = new byte[1024];
  31:  
  32:         int len = -1;
  33:  
  34:         while( (len = sis.read(buf)) != -1)
  35:         {
  36:             //将合并后的流写入一个文件
  37:             fos.write(buf,0,len);
  38:         }
  39:  
  40:         fos.close();
  41:         sis.close();    
  42:     }
  43:     
  44:     //分割流
  45:     private static int splitFile(File f) throws IOException
  46:     {
  47:         FileInputStream fis = new FileInputStream(f);
  48:  
  49:         long size = f.length();
  50:  
  51:         byte[] buf = null;
  52:         
  53:         //选择缓冲区大小
  54:         if(size > 1024*1024*5)
  55:             buf = new byte[1024*1024];
  56:         else
  57:             buf = new byte[(int)size/5];
  58:  
  59:         int len = -1;
  60:         int count = 1;
  61:  
  62:         while( (len = fis.read(buf)) != -1)
  63:         {
  64:             //每个缓冲区的内容分别写入不同的part文件
  65:             FileOutputStream fos = new FileOutputStream((count++)+".part");
  66:             fos.write(buf,0,len);
  67:             fos.close();
  68:         }
  69:  
  70:         fis.close();
  71:  
  72:         return count-1;
  73:     }
  74: }

对象的序列化

ObjectInputStream,ObjectOutputStream

将对象存取在硬盘上,叫做对象的持久化存储(存储的是对象的属性值,而不是方法)

想要对对象进行序列化,该对象必须实现Serializable接口,Serializable接口没有方法,称为标记接口,实现过程只是给实现者加入一个序列化的ID:ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L; 其实就是序列号,这个序列号是由变量的声明语句自动生成的,我们也可以自己定义类的序列号

对象序列化的小例子

   1: import java.io.*;
   2:  
   3: class Person implements Serializable
   4: {
   5:     //序列号,保证类型一致
   6:     static final long serialVersionUID = 42L;
   7:  
   8:     //静态变量以及transient修饰的变量不会被序列化
   9:     static String country = "cn";
  10:     transient int grade;
  11:     private String name;
  12:     private int age;
  13:  
  14:     Person(String name,int age,int grade,String country)
  15:     {
  16:         this.name = name;
  17:         this.age = age;
  18:         this.grade = grade;
  19:         this.country = country;
  20:     }
  21:  
  22:     public String toString()
  23:     {
  24:         return name+"::"+age+"::"+grade+"::"+country;
  25:     }
  26: }
  27:  
  28: class ObjectStreamDemo 
  29: {
  30:     public static void main(String[] args) throws Exception
  31:     {
  32:  
  33:         //writeObj();
  34:         readObj();
  35:     }
  36:  
  37:     //将对象写入流中
  38:     private static void writeObj() throws IOException
  39:     {
  40:         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
  41:  
  42:         oos.writeObject(new Person("Shawn",30,3,"en"));
  43:         oos.writeObject(new Person("feng",23,6,"usa"));
  44:  
  45:         oos.close();
  46:     }
  47:     
  48:     //将对象从流中读出并打印
  49:     private static void readObj() throws Exception
  50:     {
  51:         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
  52:  
  53:         Person p1 = (Person)ois.readObject();
  54:         Person p2 = (Person)ois.readObject();
  55:  
  56:         System.out.println("p1 --- "+p1);
  57:         System.out.println("p2 --- "+p2);
  58:  
  59:         ois.close();
  60:     }
  61: }

io8

我们可以看到,静态变量和transient修饰的变量是不会被序列化到硬盘上的

管道流

PipedInputStream,PipedOutputStream

管道Demo,一个线程写,一个线程读

   1: import java.io.*;
   2:  
   3: //读管道流线程
   4: class Read implements Runnable
   5: {
   6:     private PipedInputStream pis;
   7:  
   8:     Read(PipedInputStream pis)
   9:     {
  10:         this.pis = pis;
  11:     }
  12:  
  13:     public void run()
  14:     {
  15:         try
  16:         {
  17:             byte[] buf = new byte[1024];
  18:         
  19:             int len = -1;
  20:             
  21:             //阻塞方法,读不到数据会等待
  22:             len = pis.read(buf);
  23:  
  24:             System.out.println(new String(buf,0,len));
  25:  
  26:             pis.close();
  27:         }
  28:         catch (IOException e)
  29:         {
  30:             System.out.println("pipe read error!");
  31:         }
  32:         
  33:     }
  34: }
  35:  
  36: //写管道流线程
  37: class Write implements Runnable
  38: {
  39:     private PipedOutputStream pos;
  40:  
  41:     Write(PipedOutputStream pos)
  42:     {
  43:         this.pos = pos;
  44:     }
  45:  
  46:     public void run()
  47:     {
  48:         try
  49:         {
  50:             pos.write("pipe is coming!".getBytes());
  51:  
  52:             pos.close();
  53:         }
  54:         catch (IOException e)
  55:         {
  56:             System.out.println("pipe write error!");
  57:         }
  58:  
  59:     }
  60: }
  61:  
  62: class PipedStreamDemo 
  63: {
  64:     public static void main(String[] args) throws IOException
  65:     {
  66:         PipedInputStream pis = new PipedInputStream();
  67:         PipedOutputStream pos = new PipedOutputStream();
  68:         
  69:         //链接读写管道
  70:         pis.connect(pos);
  71:  
  72:         new Thread(new Read(pis)).start();
  73:  
  74:         new Thread(new Write(pos)).start();
  75:     }
  76: }

随机访问文件流

RandomAccessFile

直接继承Object类,内部封装了字节输入输出流,同时封装了文件的指针,可对基本数据类型进行直接读写,最大的好处是可以实现数据的分段写入,通过seek方法。

用随机访问实现的多线程复制文件(后期会改进代码,完成多线程下载)

   1: import java.io.*;
   2:  
   3: //下载线程
   4: class DownLoadThread implements Runnable
   5: {
   6:     private RandomAccessFile in;
   7:     private RandomAccessFile out;
   8:     private int offset;//偏移量
   9:     private int buf_size;//分配数据量
  10:     private int block_size;//缓冲区大小
  11:  
  12:     //初始化
  13:     DownLoadThread(RandomAccessFile in,RandomAccessFile out,int offset,int buf_size)
  14:     {
  15:         this.in = in;
  16:         this.out = out;
  17:         this.offset = offset;
  18:         this.buf_size = buf_size;
  19:         
  20:         block_size = 1024*512;
  21:         if(buf_size < block_size)
  22:             block_size = buf_size;
  23:  
  24:     }
  25:  
  26:     public void run()
  27:     {
  28:         try
  29:         {        
  30:             System.out.println(Thread.currentThread().getName()+"开始下载...");
  31:             
  32:             //读写流都偏移到指定位置
  33:             in.seek(offset);
  34:             out.seek(offset);
  35:  
  36:             byte[] buf = new byte[block_size];
  37:                 
  38:             int len = -1;
  39:  
  40:             int lastSize = buf_size;
  41:             
  42:             //读取信息并写入到目的地
  43:             while( (len = in.read(buf)) != -1)
  44:             {
  45:                 out.write(buf,0,len);
  46:  
  47:                 lastSize -= len;
  48:                 
  49:                 //分配数据量完成,结束线程
  50:                 if(lastSize == 0)
  51:                     break;
  52:                 if(lastSize < block_size)
  53:                 {
  54:                     block_size = lastSize;
  55:                     buf = new byte[block_size];
  56:                 }
  57:             }
  58:             
  59:             System.out.println(Thread.currentThread().getName()+"下载完成!");
  60:  
  61:             in.close();
  62:             out.close();
  63:             
  64:         }
  65:         catch (IOException e)
  66:         {
  67:             throw new RuntimeException(e);
  68:         }
  69:         
  70:     }
  71: }
  72:  
  73: class MutiDownLoadDemo 
  74: {
  75:     public static void main(String[] args) throws Exception
  76:     {
  77:         //确定源文件和目的文件
  78:         File fin = new File("1.avi");
  79:         File fout = new File("5.avi");
  80:         
  81:  
  82:         multiDownload(fin,fout,12);
  83:  
  84:  
  85:     }
  86:     
  87:     //多线程下载 thread_num为线程数
  88:     private static void multiDownload(File fin,File fout,int thread_num) throws Exception
  89:     {
  90:         RandomAccessFile in = new RandomAccessFile(fin,"r");
  91:  
  92:         RandomAccessFile out = new RandomAccessFile(fout,"rwd");
  93:  
  94:         int len = (int)fin.length();
  95:         
  96:         //确定目的文件大小
  97:         out.setLength(len);
  98:         
  99:         in.close();
 100:         out.close();
 101:  
 102:         System.out.println("-----------File size : "+(len>>20)+" MB--------------");
 103:         System.out.println("-----------Thread num: "+thread_num+"---------");
 104:         
 105:         //确定每个线程分配的数据量
 106:         int buf_size = len/thread_num;
 107:  
 108:         System.out.println("-----------buffer size: "+(buf_size>>20)+" MB-----------");
 109:  
 110:         //开启每个线程
 111:         for(int i = 0 ; i < thread_num ; i ++)
 112:         {
 113:             //"rwd"模式代表可读可写并且线程安全
 114:             new Thread(
 115:                 new DownLoadThread(new RandomAccessFile(fin,"r"),new RandomAccessFile(fout,"rwd"),i*buf_size,buf_size)
 116:                 ).start();
 117:         }
 118:     }
 119: }

基本数据类型流对象

DataInputStream,DataOutputStream

   1: public static void main(String[] args) throws IOException
   2: {
   3:     DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
   4:  
   5:     dos.writeInt(123);
   6:  
   7:     dos.writeDouble(123.45);
   8:  
   9:     dos.writeBoolean(true);
  10:  
  11:     DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
  12:  
  13:     System.out.println(dis.readInt());
  14:     System.out.println(dis.readDouble());
  15:     System.out.println(dis.readBoolean());
  16: }

内存作为源和目的的流对象

操作字节数组

ByteArrayInputStreamByteArrayOutputStream

操作字符数组

CharArrayReaderCharArrayWrite

操作字符串

StringReaderStringWriter

字符编码

字符流出现是为了更方便的操作字符,通过InputStreamReaderOutputStreamWriter可以任意指定编码表进行解码转换

编码表

计算机开始只能识别二进制数据,为了更方便的表示各个国家的文字,就将各个国家的文字与二进制数据进行一一对应,形成了一张表,即为编码表

常见的编码表

ASCII:美国标准信息交换码。

用一个字节的7位可以表示。

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

用一个字节的8位表示。

GB2312:中国的中文编码表。

GBK:中国的中文编码表升级,融合了更多的中文文字符号。

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

所有文字都用两个字节来表示,Java语言使用的就是unicode

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

......

编码规则

只有GBK和UTF-8识别中文,GBK向下兼容GB2312

GBK两个字节表示一个字符,UTF-8是1-3个字节表示一个字符,每个字节前面1-3位作为标识头

GBK和UTF-8都兼容ASCII码表

模拟编解码过程代码

   1: public static void main(String[] args) throws Exception
   2: {
   3:     //字符串
   4:     String s = "你好";
   5:     
   6:     //用UTF-8编码表编码s字符串
   7:     byte[] b = s.getBytes("UTF-8");
   8:     
   9:  
  10:     System.out.println(Arrays.toString(b));
  11:     
  12:     //用GBK编码表解码
  13:     String s1 = new String(b,"GBK");
  14:     
  15:     //获取之后发现不是原来的字符串
  16:     System.out.println(s1);
  17:     
  18:     //用GBK重新编码回去
  19:     byte[] b1 = s1.getBytes("GBK");
  20:     
  21:     //再用UTF-8解码
  22:     String s2 = new String(b1,"UTF-8");
  23:  
  24:     System.out.println(s2);
  25:     
  26:     
  27: }

这样做存在一个问题,由于GBK与UTF-8都支持中文,所以UTF-8编解码时有可能会去内部的相似码表去查找,这样编码出来的字符就会与原字符不符,所以一般使用ISO8859-1与中文码表互相编解码转换

一个有趣的小例子

新建一个文本文档,写入“联通”两个字,保存,关闭,再打开,发现变成了一个奇怪的字符,这是为什么呢?

首先windows默认的是ANSI编码,而UTF-8编码的标识头规则如下图

Java笔记(二十八)……IO流下 IO包中其他常用类以及编码表问题_第1张图片

由于记事本是由编码本身的规律判断选取哪个编码表的

所以答案是,“联通”这两个字由ANSI编码之后的码流符合UTF-8的规则,则记事本自动识别是UTF-8的字符,而去查了UTF-8的码表

解决方法,我们只要在联通前面加上任意字符,记事本就不会误判为UTF-8解码了

你可能感兴趣的:(java)