黑马程序员技术交流社区

标题: “第六贴”之合并流、管道流、RundomAccessFile、编码的练习 [打印本页]

作者: 曹睿翔    时间: 2013-5-14 22:10
标题: “第六贴”之合并流、管道流、RundomAccessFile、编码的练习
本帖最后由 曹睿翔 于 2013-5-14 22:07 编辑

合并流
SequenceInputStream是能对多个流进行合并成一个读取流,它在构造时需要传入Enumeration,而这个只用Vector中有,所以这个多个读取流要加入Vector集合中。
注意:它只是对读取流进行合并。
它使用步骤:
1.      创建Vector<InputStream>
2.      将要合并的InputStream加入Vector
3.      通过Vector获取Enumeration
4.      创建SequenceInputStream,将Enumeration作为参数传入。
代码:
  1. public class SequenceDemo {

  2.         /**
  3.          * @param args
  4.          * @throws Exception
  5.          */
  6.         public static void main(String[] args) throws Exception {
  7.                 // TODO Auto-generated method stub
  8.                 File file1 = new File("D:\\abc.txt");
  9.                 File file2 = new File("D:\\def.txt");
  10.                 File file3 = new File("d:\\java.txt");
  11.                         
  12.                 //将三个文件封装到vector集合中
  13.                 Vector<FileInputStream> vec = new Vector<FileInputStream>();
  14.                
  15.                 vec.add(new FileInputStream(file1));
  16.                 vec.add(new FileInputStream(file2));
  17.                 vec.add(new FileInputStream(file3));
  18.                
  19.                 //返回Enumeration
  20.                 Enumeration<FileInputStream> enu = vec.elements();
  21.                 SequenceInputStream seq = new SequenceInputStream(enu);
  22.                 PrintStream ps = new PrintStream("d:\\合并.txt");
  23.                
  24.                 byte[] ch = new byte[1024];
  25.                 int len = 0;
  26.                 while((len = seq.read(ch))!= -1){
  27.                         ps.write(ch, 0, len);
  28.                 }
  29.                 seq.close();
  30.                 ps.close();
  31.         }
  32. }
复制代码
管道流
管道流分为字节管道流(PipedInputStreamPipedOutputStream)和字符管道流(PipedReaderPipedWriter):它是IO技术和多线程技术的结合。在一条线程上写入的数据可以在另外一条线程上读取,它们是一对对配合使用的。如果在一条线程上使用管道读取和写入流会发生死锁的情况。
其使用步骤:
1.   分别定义写入和读取的Runnable接口子类,把相应的管道流作为构造参数传入给定义的私有管道流成员。
2.   将配对的管道流通过connect()方法连接起来。
3.    启动线程
  1. 示例代码:
  2.   import java.io.*;
  3.   class Read implements Runnable{
  4.           private PipedInputStream in;
  5.           Read(PipedInputStream in)
  6.           {
  7.                   this.in = in;
  8.           }
  9.           public void run(){
  10.                   try
  11.                   {
  12.                           byte[] buf = new byte[1024];
  13.                           System.out.println("读取前。。没有数据阻塞");
  14.                           int len = in.read(buf);
  15.                           System.out.println("读到数据。。阻塞结束");
  16.                           String s= new String(buf,0,len);
  17.                           System.out.println(s);
  18.   
  19.                           in.close();
  20.   
  21.                   }
  22.                   catch (IOException e)
  23.                   {
  24.                           throw new RuntimeException("管道读取流失败");
  25.                   }
  26.           }
  27.   }
  28.   
  29.   class Write implements Runnable
  30.   {
  31.           private PipedOutputStream out;
  32.           Write(PipedOutputStream out)
  33.           {
  34.                   this.out = out;
  35.           }
  36.           public void run()
  37.           {
  38.                   try
  39.                   {
  40.                           System.out.println("开始写入数据,等待6秒后。");
  41.                           Thread.sleep(6000);
  42.                           out.write("piped lai la".getBytes());
  43.                           out.close();
  44.                   }
  45.                   catch (Exception e)
  46.                   {
  47.                           throw new RuntimeException("管道输出流失败");
  48.                   }
  49.           }
  50.   }
  51.   
  52.   class  PipedStreamDemo
  53.   {
  54.           public static void main(String[] args) throws IOException
  55.           {
  56.   
  57.                   PipedInputStream in = new PipedInputStream();
  58.                   PipedOutputStream out = new PipedOutputStream();
  59.                   in.connect(out);
  60.   
  61.                   Read r = new Read(in);
  62.                   Write w = new Write(out);
  63.                   new Thread(r).start();
  64.                   new Thread(w).start();
  65.           }
  66.   }
复制代码
RundomAccessFile(重点,即可读又可写)      此类的实例支持对随机访问文件的读取和写入。 该类不是算是IO体系中子类。而是直接继承自Object。 但是它是IO包中成员。因为它具备读和写功能。内部封装了一个数组,而且通过指针对数组的元素进行操作。可以通过getFilePointer获取指针位置,同时可以通过seek改变指针的位置。 其实完成读写的原理就是内部封装了字节输入流和输出流。
通过构造函数可以看出,该类只能操作文件。而且操作文件还有模式:只读r,,读写rw等。 如果模式为只读 r。不会创建文件。会去读取一个已存在文件,如果该文件不存在,则会出现异常。如果模式rw。操作的文件不存在,会自动创建。如果存则不会覆盖。
  1. import java.io.*;
  2. class RandomAccessFileDemo
  3. {
  4. public static void main(String[] args) throws IOException
  5. {
  6. //writeFile_2();
  7. //writeFile();
  8. //readFile();
  9. RandomAccessFile raf = new RandomAccessFile("raf1.txt","rw");
  10. raf.write("hha".getBytes());

  11. }
  12. //读取,模式设置为“r”
  13. public static void readFile() throws IOException
  14. {
  15. RandomAccessFile raf = new RandomAccessFile("raf.txt","r");
  16. //调整对象中的指针,seek前后都能设置,所以比skipBytes使用范围广。
  17. //raf.seek(8*0);//里边存入的数据都是8个字节为一组,如果没有规律,读取就困难了

  18. //跳过指定的字节数,只能往后走,不能往回走。
  19. raf.skipBytes(8);

  20. byte [] buf = new byte[4];
  21. raf.read(buf);
  22. String name =new String(buf);
  23. int age = raf.readInt();
  24. System.out.println("name="+name);
  25. System.out.println("age="+age);
  26. raf.close();
  27. }

  28. public static void writeFile_2() throws IOException
  29. {
  30. RandomAccessFile raf = new RandomAccessFile("raf.txt","rw");
  31. raf.seek(8*0);//修改数据,网络分段下载原理,要重点掌握。
  32. raf.write("周期".getBytes());
  33. raf.writeInt(103);
  34. raf.close();
  35. }
  36. //写入,模式设置为“rw”
  37. public static void writeFile() throws IOException
  38. {
  39. RandomAccessFile raf = new RandomAccessFile("raf.txt","rw");
  40. raf.write("李四".getBytes());
  41. // raf.write(97);write(int x)方法只写入低8位。如果写入的数字在byte取值范围内,那么可以read()正常读取,如果超出,读取时就会出现数据错乱。
  42. raf.writeInt(97);//要把四个字节都写入,所以用writeInt
  43. raf.write("王五".getBytes());
  44. raf.writeInt(99);
  45. raf.close();
  46. }
  47. }
复制代码

作者: 曹睿翔    时间: 2013-5-14 22:10

编码的练习及总结: 字符流的出现是为了方便操作字符数据,其方法操作的原因是因为内部加入了编码表。Java中能够实现字节根据指定编码表转成字符的,有四个类:InputStreamReader和OutputStreamWriter,PrintStream和PrintWriter。它们都能够加构造时,指定编码表;但后两个是打印流,只能用于打印,使用有局限,所以相对而言,还是前两个转换流使用多一些。编码表的由来计算机只能识别二进制数据,早期是电信号。为了应用计算机方便,让它可以识别各个国家的文字,就将各个国家的文字用数字来表示,并将文字与二进制数字一一对应,形成了一张表,这个表就是编码表。常见的编码表地域码表1.      ASCII:美国码表,息交换码,用一个字节的7位表示。2.      ISO8859-1:欧洲码表,拉丁码表,用一个字节的8位表示,最高位13.      GB2312:中国中文编码表,它用两个字节表示,为兼容ASCII,它的两个字节的高位都是1,也即是两个负数;但与ISO8859-1冲突。大概有六七千个字。4.      GBK:中国的中文编码表的升级版,扩容到2万多字。通用码表:1.      Unicode:国际标准码,融合多种语言文字。所有的文字都用两个字节表示,Java默认使用的就是Unicode。2.      UTF-8:UnicodeTransform Format -8。Unicode码把用一个字节能装下的文字,也用两个字节表示,有些浪费空间,对之进行优化的结果就是UTF-8。UTF-8编码表,一个文字最用一个字节表示,最多用3个字节表示,并且每个字节开始都有标识头,所以很容易于其他编码表区分出来。     代码示例:
  1. class EncodeStream   
  2. {  
  3.     public static void main(String[] args)  throws Exception  
  4.     {  
  5.         //writeText();  
  6.         readText();  
  7.     }  
  8.     //按照指定的码表读取数据  
  9.     public static void readText() throws Exception  
  10.     {  
  11.         InputStreamReader isr = new InputStreamReader(new FileInputStream("gbk.txt"),"UTF-8");  
  12.         char [] buf = new char[10];  
  13.         int len = isr.read(buf);  
  14.         System.out.println(new String(buf,0,len));  
  15.         isr.close();  
  16.     }  
  17.     //按照指定的码表写入数据  
  18.     public static void writeText() throws Exception  
  19.     {  
  20.         OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"GBK");  
  21.         osw.write("你好");  
  22.         osw.close();  
  23.     }  
  24. }  
复制代码
编码问题的产生与解决从上边的那些编码表可以看出,GBK和Unicode都能识别中文,那么当一台电脑使用GBK,而另一台电脑使用Unicode时,虽然在各自的电脑上都能识别中文,但他们其中一方向另一方发送中文文字时,另一方却不能识别,出现了乱码。这是一万年GBK和Unicode虽然都能识别中文,但对同一个中文文字,他们在两个编码表对应的编码值不同。这时,在解读别人传来的中文数据时,就需要指定解析中文使用的编码表了。而转换流就能指定编码表,它的应用可以分为:1.      可以将字符以指定的编码格式存储。2.      可以对文本数据以指定的编码格式进行解读。它们指定编码表的动作是由构造函数完成的。编码:字符串变成字节数组,Stringàbyte[],使用str.getBytes(charsetName);解码:字节数组变成字符串,byte[]àString,使用new String(byte[] b, charsetName);编码编错:是指你对一个文字进行编码时,使用了不识别该文字的编码表,比如你编码一个汉字,却使用了ISO8859-1这个拉丁码表,ISO8859-1根本就不识别汉字。编码编错时,你用任何方式对编码后的数据进行处理,都不可能再拿到这个汉字了。解码解错:是指你对一个文字进行编码事,使用了正确的码表,编码正确,但在解码时使用了错误的码表,那么你还有可能拿到这个文字。这分为两种情况:第一种情况:你使用的是GBK编码,解码时用的是ISO8859-1,因为GBK编译一个汉字,使用两个字节,ISO8859-1解码时是一个字节一个字节读取,虽然解码出现了乱码,但是这个汉字的二进制数据没有变化,那么你可以通过再次编译获取其原来的二进制数据,然后再次使用GBK编码,解码成功。第二种情况:你使用的是GBK编码,解码时用的却是UTF-8,因为这两个码表都识别汉字,那么你再次使用UTF-8编码时,就有可能把一个汉字的2个字节,变成3个,这时再用GBK解码时,得到的仍然是乱码,解码仍然失败。
  1. class  EncodeDemo  
  2. {  
  3.     //解决编码问题示例  
  4.     public static void main(String[] args) throws Exception  
  5.     {  
  6.         String s ="黑马训练营";  
  7.         byte[] b1= s.getBytes("GBK");
  8.         System.out.println(Arrays.toString(b1));  
  9.   
  10.         //String s1 = new String(b1,"ISO8859-1");  
  11.         String s1 = new String(b1,"UTF-8");  
  12.         System.out.println("s1="+s1);  
  13.   
  14.         //对s1进行编码  
  15.         //byte [] b2 = s1.getBytes("ISO8859-1");  
  16.         byte [] b2 = s1.getBytes("UTF-8");  
  17.         System.out.println(Arrays.toString(b2));  
  18.         String s2 = new String(b2,"GBK");  
  19.         System.out.println("s2="+s2);  
  20.   
  21.   
  22.     }  
  23. }  

  24. “联通”的编码问题(联系也一样)
  25.          问题描述:打开记事本仅写入“联通”两个汉字,关闭后,再次打开会出现乱码。

  26. class EncodeDemo2   
  27. {  
  28.     public static void main(String[] args) throws Exception  
  29.     {  
  30.         String s = "联通";  
  31.         byte [] by = s.getBytes("GBK");  
  32.         for(byte b:by)  
  33.         {  
  34.             System.out.println(Integer.toBinaryString(b&255));  
  35.         /*   
  36.          * “联通”编码问题的原因:
  37.          //boBinaryString(int)它接受的是int,byte类型的b参与运算时会类型提升为int,我们需要的是提升后的低8位,所以&255
  38.             
  39.              对联通的结果进行GBK编码时,其二进制码为:
  40.             11000001
  41.             10101010
  42.             11001101
  43.             10101000  
  44.              编码的结果符合UTF-8的格式,所以再次打开记事本时,它会把它按照UTF-8的格式进行解码,结果就是两个乱码。
  45.         */  
  46.         }  
  47.     }  
  48. }  
复制代码

作者: 曹睿翔    时间: 2013-5-14 22:10

IO综合练习:录入学生成绩并将信息存储在硬盘文件中。
多了点注释
  1. /*
  2. 有5个学生,每个学生有三门课的成绩。
  3. 从键盘输入以上数据(包括姓名,三门课成绩);
  4. 输入的格式:如zhangsan,30,40,60计算出总成绩。
  5. 并把学生的信息和计算出的总分数,按由高到低顺序存在在磁盘文件stud.txt中。

  6. 1.描述学生对象。
  7. 2.定义一个学生对象的工具类

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

  13. */  
  14. import java.io.*;  
  15. import java.util.*;  
  16. //使用TreeSet需要将其中的元素实现Comparable接口  
  17. class Student implements Comparable<Student>  
  18. {  
  19.     private String name;  
  20.     private int ma,cn,en;  
  21.     private int sum;  
  22.     Student(String name,int ma,int cn,int en)  
  23.     {  
  24.         this.name = name;  
  25.         this.ma = ma;  
  26.         this.cn = cn;  
  27.         this.en = en;  
  28.         sum =ma+cn+en;  
  29.   
  30.     }  
  31.     //Comparable接口要实现compareTo方法。  
  32.     public int compareTo(Student s )  
  33.     {  
  34.         //注意,一般自然信息定义的都是升序,即成绩从低到高的顺序  
  35.         int num =new Integer(this.sum).compareTo(new Integer(s.sum));  
  36.         if(sum==0)  
  37.             return this.name.compareTo(s.name);  
  38.         return num;  
  39.     }  
  40.     public String getName()  
  41.     {  
  42.         return name;  
  43.     }  
  44.     public int getSum()  
  45.     {  
  46.         return sum;  
  47.     }  
  48.     //学生类也有可能存入HashSet中,所以要复写hashCode和equals方法。  
  49.     public int hashCode()  
  50.     {  
  51.         return name.hashCode()+sum*78;  
  52.     }  
  53.     public boolean equals (Object obj)  
  54.     {  
  55.         if(!(obj instanceof Student))  
  56.             throw new ClassCastException("类型不匹配");  
  57.         Student s = (Student)obj;  
  58.         return this.name.equals(s.name)&&this.sum==s.sum;  
  59.     }  
  60.     //复写toString方法,提供学生类特有的字符串表现形式。  
  61.     public String toString()  
  62.     {  
  63.         return "Student["+name+","+ma+","+cn+","+en+"]";  
  64.     }  
  65. }  
  66. //定义学生信息录入和存储工具类  
  67. class StudentInfoTool  
  68. {  
  69.     //函数重载,提供一个默认的方法,对学生对象按照定义的自然顺序进行排序  
  70.     public static Set<Student> getStudents()  throws IOException  
  71.     {  
  72.         return getStudents(null);  
  73.     }  
  74.       
  75.     //定义录入工具,并将键盘录入的结果存入Set集合中,因为要排序,所以要用TreeSet。  
  76.     //这里加入比较器作为参数,是为了让集合可以按照不同的要求进行排序,比如按照某一单科成绩或总分从高到底  
  77.     public static Set<Student> getStudents(Comparator<Student> cmp)  throws IOException  
  78.     {  
  79.         //定义缓冲区,包装键盘录入流对象  
  80.         BufferedReader bufr =   
  81.             new BufferedReader(new InputStreamReader(System.in));  
  82.         String line =null;  
  83.         //定义要集合,用于存储录入的学生信息。  
  84.         Set  <Student>stus = null;  
  85.         if(cmp==null)  
  86.             stus = new TreeSet<Student>();  
  87.         else  
  88.             stus = new TreeSet<Student>(cmp);  
  89.         //也可以用三元运算符进行优化  
  90. //      Set<Student>stus=(cmp==null)?(new TreeSet<Student>()):(new TreeSet<Student>(cmp));  
  91.          
  92. //      循环读取录入的信息,注意定义结束标记。  
  93.         while((line =bufr.readLine())!=null)  
  94.         {  
  95.             if("over".equals(line))  
  96.                 break;  
  97.             //用","进行切割---其实这里可以将录入信息line用正则表达式过滤一下,对于非法的信息不写入,并进行提示,防止录入非法的数据。  
  98.               
  99.             String [] info = line.split(",");  
  100.             Student stu =new Student(info[0],Integer.parseInt(info[1]),  
  101.                         Integer.parseInt(info[2]),  Integer.parseInt(info[3]));  
  102.             stus.add(stu);  
  103.         }  
  104.         //关闭流资源  
  105.         bufr.close();  
  106.         return stus;  
  107.     }  
  108.     //将学生信息存入磁盘文件中,也可以将要写入的文件已参数形式传入  
  109.     public static void write2File(Set<Student> stus) throws IOException  
  110.     {  
  111.         BufferedWriter bufw = new BufferedWriter(new FileWriter("stuinfo.txt"));  
  112.         for(Student stu: stus)  
  113.         {  
  114.             bufw.write(stu.toString()+"\t");  
  115.             //这里写入的数据是int类型的值,并且write会截取其低8位,所以要把它转成字符串,否则会出现乱码  
  116.             bufw.write(stu.getSum()+"");  
  117.             bufw.newLine();//写入跨平台的换行符  
  118.             bufw.flush();//字符缓冲区一定要记得刷新动作  
  119.         }  
  120.         //关闭资源。  
  121.         bufw.close();  
  122.     }  
  123. }  
  124.   
  125. class  StudentInfoTest  
  126. {  
  127.     public static void main(String[] args)  throws IOException  
  128.     {  
  129.         //通过Collections集合工具类的反转命名方法,获得一个逆序比较器  
  130.         Comparator<Student> cmp = Collections.reverseOrder();  
  131.         //如果没有传入逆序比较器,学生会按照自然顺,即总成绩从低到高的排序,这与我们的现实生活习惯不符合。  
  132.         Set <Student> stus = StudentInfoTool.getStudents(cmp);  
  133.         //将集合中的学生信息写入文件。  
  134.         StudentInfoTool.write2File(stus);  
  135.   
  136.     }  
  137. }  
复制代码

作者: 神之梦    时间: 2013-5-14 22:56
程序看得我这初学者{:soso_e134:}
作者: chouwayメ    时间: 2013-5-14 23:01
本帖最后由 chouwayメ 于 2013-10-8 22:40 编辑

{:soso_e100:}我保存网页到手机 上班的时候慢慢看哈
作者: Just_Only    时间: 2013-5-15 09:04
相当的牛逼啊  这代码写起来真心不容易




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2