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

时间:2024-01-21 14:49:40

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: }

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

我们可以看到,静态变量和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: }

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

操作字节数组

ByteArrayInputStream与ByteArrayOutputStream

操作字符数组

CharArrayReader与CharArrayWrite

操作字符串

StringReader 与 StringWriter

字符编码

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

编码表

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

常见的编码表

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包中其他常用类以及编码表问题

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

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

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