黑马程序员——java之IO体系中File类、编码问题、对象序列化

时间:2023-02-18 15:44:25

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


知识点总结

1、File类的由来、三种常见的构造方法。

文件也有自己对应的属性信息,比如所在目录、文件类型、大小、建立修改日期。Java通过面向对象的方法对其进行封装,方便对其进行操作。

File(String pathname)

File(File parent ,String child)

File(String parent,String child)

2、File的几种操作方法:创建、判断、删除、获取信息。

1,创建。

boolean createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false。和输出流不一样,输出流对象一建立创建文件。而且文件已经存在,会覆盖。

boolean mkdir():创建文件夹。

boolean mkdirs():创建多级文件夹。

2,删除。

boolean delete():删除失败返回false。如果文件正在被使用,则删除不了返回falsel

void deleteOnExit();在程序退出时删除指定文件。解决了在占用文件时删除不  了的问题。

3,判断。

boolean exists() :文件是否存在.非常常用。

isFile():【注意】:在判断文件对象是否是文件或者目录时,

           亦一般要先通过exists先判断文件对象封装内容是否存在。

isDirectory();

isHidden();

isAbsolute();

4,获取信息。

getName():

getPath():

getParent()://只要没有明确指定父目录,返回为空;

                否则返回文件的父目录(上级目录

String getAbsolutePath();//返回绝对路径,字符串类型  

    File getAbosoluteFile();返回对象 

long lastModified() //返回最近一次修改时间

long length() //返回该文件的长度

5.设置

public boolean renameTo(File dest)  

重新命名此抽象路径名表示的文件。

可以将文件从一个目录转移到另一个目录。  

但是,重命名操作无法将一个文件从一个文件系统移动到另一个文件系统,  

该操作不是不可分的,如果已经存在具有目标抽象路径名的文件,那么该操作可  能无法获得成功。应该始终检查返回值,以确保重命名操作成功。

6、列出内容:public String[]list();  只传入实现FileNameFilter接口的对象

   列出内容:public File[]listFiles(),既可以传入FileNameFilter,又可以传FileFilter

    FileNameFilter  中只有 booleanaccept(File dir, String name) 方法

              测试指定文件是否应该包含在某一文件列表中。

FileFilter       中只有    booleanaccept(File pathname) 

        测试指定抽象路径名是否应该包含在某个路径名列表中。

 

[java]  view plain copy print ?
  1. 示例代码:  
  2.     public class FileDemo2 {  
  3.   
  4.     /** 
  5.      * @param args 
  6.      */  
  7.     public static void main(String[] args) {  
  8.         // TODO Auto-generated method stub  
  9.           
  10.         listDemo();  
  11.         listDemo1();  
  12.         listDemo_2();  
  13.     }  
  14.     public static void listDemo()  
  15.     {  
  16.         File f = new File("D:\\JAVA\\code");  
  17.   
  18.         String[] names = f.list();//调用list方法的file对象必须是封装了一个目录。该目录还必须存在。  
  19.         for(String name : names)  
  20.         {  
  21.             System.out.println(name);  
  22.         }  
  23.     }  
  24.     public static void listDemo_2()  
  25.     {  
  26.         File dir = new File("d:\\java\\code");  
  27.   
  28.         String[] arr = dir.list(new FilenameFilter()  
  29.         {  
  30.             public boolean accept(File dir,String name)  
  31.             {  
  32.                 return name.endsWith(".java");  
  33.   
  34.             }  
  35.         });  
  36.           
  37.         System.out.println("len:"+arr.length);  
  38.         for(String name : arr)  
  39.         {  
  40.             System.out.println(name);  
  41.         }  
  42.     }  
  43.   
  44.     public static void listDemo1(){  
  45.         File dir = new File("d:\\java\\code");  
  46.         File [] arr = dir.listFiles(new FilenameFilter(){  
  47.             public boolean accept(File dir,String name){  
  48.                 return name.endsWith(".java");  
  49.             }  
  50.         });  
  51.         for(File f :arr){  
  52.               
  53.             Print.sop(f);  
  54.               
  55.         }  
  56.     }   
  57.   
  58. }  


 

3、列出指定目录下文件或者文件夹,包含子目录中的内容。

也就是列出指定目录下所有内容。

因为目录中还有目录,只要使用同一个列出目录功能的函数完成即可。

在列出过程中出现的还是目录的话,还可以再次调用本功能。

也就是函数自身调用自身。

这种表现形式,或者编程手法,称为递归。

递归要注意:

1,限定条件。

2,要注意递归的次数。尽量避免内存溢出。

 

[java]  view plain copy print ?
  1. public class ListFileDemo {  
  2.   
  3.     /** 
  4.      * @param args 
  5.      */  
  6.     public static void main(String[] args) {  
  7.         // TODO Auto-generated method stub  
  8.         File f = new File("d:"+File.separator+"java");  
  9.         getFile(f,0);  
  10.     }  
  11.       
  12.     public static void getFile(File f,int level){  
  13.         if(!f.exists())//先判断文件或目录是否存在  
  14.             new IOException("找不到文件");  
  15.         if(f.isDirectory()){//判断是不是目录  
  16.             File[] files = f.listFiles();//是就获取目录下的文件  
  17.             for(File f1 : files){             
  18.                 if(f1.isDirectory()){//在循环目录的过程中判断是否还是目录,是                                      //的话递归调用本方法  
  19.                     level++;  
  20.                     getFile(f1,level);  
  21.                     }     
  22.                System.out.println(getLevel(level)+f1);//不是目录就打印  
  23.                   
  24.             }  
  25.   
  26.         }  
  27.         System.out.println(getLevel(level)+f);//不是目录就打印  
  28.           
  29.     }  
  30.     //获取层级目录,打印出层次效果,毕老师想的,牛!  
  31.     public static String getLevel(int level){  
  32.         StringBuilder sb = new StringBuilder();  
  33.         sb.append("|--");  
  34.          for(int x =0; x<level; x++)    
  35.             {                       
  36.                 sb.insert(0,"|  ");    
  37.             }    
  38.             return sb.toString();    
  39.     }  
  40.   
  41. }  

 

4、删除一个带内容的目录

[java]  view plain copy print ?
  1. /*  
  2. 删除一个带内容的目录。  
  3. 删除原理:  
  4. 在window中,删除目录从里边往外面删除的。  
  5. 既然是从里边往外删除,就需要用到递归。  
  6. */    
  7. import java.io.*;    
  8. class RemoveDir     
  9. {    
  10.     public static void main(String[] args)     
  11.     {    
  12.         File dir =new File("b:\\bxd");    
  13.         removeDir(dir);    
  14.     }    
  15.     //java删除是不走回收站的    
  16.     public static void removeDir(File dir)    
  17.     {    
  18.         File[] files =dir.listFiles();    
  19.         //删除目录中的内容    
  20.         for(int x =0; x<files.length; x++)    
  21.         {    
  22.             //java不能访问隐藏文件,所以遍历的时候最好判断下                     //(!files[x].isHidden())&&files[x].isDirectory()    
  23.             if(files[x].isDirectory())    
  24.                 removeDir(files[x]);    
  25.             else    
  26.                 sop(files[x]+":-file-:"+files[x].delete());    
  27.     
  28.         }     
  29.         //删除目录    
  30.         sop(dir+"::del::"+dir.delete());    
  31.     }    
  32.     public static void sop(Object obj)    
  33.     {    
  34.         System.out.println(obj);    
  35.     }    
  36. }    

5、打印流:

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

字节打印流:

PrintStream

构造函数可以接收的参数类型:

1,file对象。File

2,字符串路径。String

3,字节输出流。OutputStream

字符打印流:

PrintWriter

构造函数可以接收的参数类型:

1,file对象。File

2,字符串路径。String

3,字节输出流。OutputStream

4,字符输出流,Writer

 

打印流有自己的PrintWriter.println();等方法。

把标准输入流写到文件中

 

[java]  view plain copy print ?
  1. 示例代码:  
  2. class  PrintStreamDemo    
  3. {    
  4.     public static void main(String[] args) throws IOException    
  5.     {    
  6.         BufferedReader bufr =     
  7.             new BufferedReader(new InputStreamReader(System.in));    
  8.         PrintWriter out = new PrintWriter(new FileWriter("a.txt"),true); //不用使用转换流  
  9.             
  10.         String line =null;    
  11.         while((line=bufr.readLine())!=null)    
  12.         {    
  13.             if("over".equals(line))    
  14.                 break;    
  15.             out.println(line.toUpperCase());    
  16.             //out.flush();    
  17.         }    
  18.         out.close();    
  19.         bufr.close();    
  20.     }    
  21. }    

6、Properties是hashtable的子类。

也就是说它具备map集合的特点。而且它里面存储的键值对都是字符串。

是集合中和IO技术相结合的集合容器。

该对象的特点:可以用于键值对形式的配置文件。

那么在加载数据时,需要数据有固定格式:键=值。

常用方法:

Object setProperty(String key,String value):

        调用Hashtableput方法,设置键值对

String getProperty(String key):

Set<String> stringPropertyNames:

       获取集合中所有的键

void load(InputStream in): 

      从输入流中读取属性列表(键和元素对)。

void  load(Reader reader): 

        按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。

           void list(PrintStream out)  

       将属性列表输出到指定的输出流。

 voidlist(PrintWriter out)   

        将属性列表输出到指定的输出流。

Store(OutputStream out,String comments):

Store(Writer writer,String comments)

 

 

[java]  view plain copy print ?
  1.   练习代码:  
  2. import java.io.File;  
  3. import java.io.FileNotFoundException;  
  4. import java.io.FileReader;  
  5. import java.io.IOException;  
  6. import java.io.PrintWriter;  
  7. import java.util.Properties;  
  8.   
  9. public class PropertiesDemo1 {  
  10.     /** 
  11.      * @param args 
  12.      */  
  13.     public static void main(String[] args) {  
  14.         // TODO Auto-generated method stub  
  15.         //getSystemInfoToFile();  
  16.         getPropertiseAndChange();  
  17.           
  18.     }  
  19.     public static void getPropertiseAndChange(){//测试加载流方法load  
  20.         File sysInfo = new File("D:"+File.separator+"Systeminfo.txt");   
  21.         Properties prop = new Properties();  
  22.         PrintWriter pw= null;  
  23.         //prop.setProperty("userName", "caoRuiXiang");  
  24.         try {  
  25.             prop.load(new FileReader(sysInfo));  
  26.             prop.setProperty("userName""caoRuiXiang");//添加一个键值对  
  27.             System.out.println(prop);  
  28.         pw= new PrintWriter(new File("d:"+File.separator+"sys.txt"));  
  29.             prop.store(pw, null);  
  30.             /*从上面的例子中可以看出,setProperty方法只是修改了内存中的值,而硬盘中的值并没有改变, 
  31.              * 所以Properties集合中海油一种方法,store,这种方法存到流中,进而存到硬盘中去, 
  32.              * */  
  33.         } catch (FileNotFoundException e) {  
  34.             // TODO Auto-generated catch block  
  35.             e.printStackTrace();  
  36.         } catch (IOException e) {  
  37.             // TODO Auto-generated catch block  
  38.             e.printStackTrace();  
  39.         }finally{  
  40.             pw.close();  
  41.         }  
  42.           
  43.     }  
  44.     public static void getSystemInfoToFile() {  //测试list方法  
  45.         Properties prop = System.getProperties();  
  46.         PrintWriter pw = null;  
  47.         try {  
  48.             pw = New PrintWriter(newFile("D:\\Systeminfo.txt"));  
  49.             prop.list(pw);  
  50.             pw.flush();  
  51.         } catch (FileNotFoundException e) {  
  52.             // TODO Auto-generated catch block  
  53.             e.printStackTrace();  
  54.         }finally{  
  55.             if(pw!=null)  
  56.                 pw.close();  
  57.         }  
  58.     }  
  59. }  

7、练习:限制程序运行次数。当运行次数到达5次时,给出,请您注册的提示。并不再让该程序执行。

[java]  view plain copy print ?
  1. /*用于记录应用程序运行次数。 
  2. 如果使用次数已到,那么给出注册提示。 
  3. 很容易想到的是:计数器。 
  4. 可是该计数器定义在程序中,随着程序的运行而在内存中存在,并进行自增。 
  5. 可是随着该应用程序的退出,该计数器也在内存中消失了。 
  6. 下一次在启动该程序,又重新开始从0计数。这样不是我们想要的。程序即使结束,该计数器的值也存在。下次程序启动在会先加载该计数器的值并加1后在重新存储起来。 
  7. 所以要建立一个配置文件。用于记录该软件的使用次数。该配置文件使用键值对的形式。 
  8. 这样便于阅读数据,并操作数据。键值对数据是map集合。 
  9. 数据是以文件形式存储,使用io技术。 
  10. 那么map+io -->properties. 
  11. 配置文件可以实现应用程序数据的共享。*/  
  12.   
  13. import java.util.*;    
  14. import java.io.*;    
  15.     
  16. class  RunCount    
  17. {    
  18.     public static void main(String[] args)  throws IOException    
  19.     {    
  20.         Properties prop = new Properties();    
  21.         File file = new File("count.ini");    
  22.         if(!file.exists())    
  23.             file.createNewFile();    
  24.         FileInputStream fis = new FileInputStream(file);    
  25.         prop.load(fis);    
  26.     
  27.         int count = 0;     
  28.         String value = prop.getProperty("time");    
  29.         if(value!=null)    
  30.         {    
  31.             count = Integer.parseInt(value);    
  32.             if(count>=5)    
  33.             {    
  34.                 sop("您好,使用次数已到,拿钱!");    
  35.                 return ;    
  36.             }    
  37.         }    
  38.         count++;    
  39.     
  40.         prop.setProperty("time",count+"");    
  41.         FileOutputStream fos = new FileOutputStream(file);    
  42.         prop.store(fos,"");    
  43.         fos.close();    
  44.         fis.close();    
  45.     
  46.     }    
  47.     public static void sop(Object obj)    
  48.     {    
  49.         System.out.println(obj);    
  50.     }    
  51. }    
8、合并流

SequenceInputStream是能对多个流进行合并成一个读取流,它在构造时需要传入Enumeration,而这个只用Vector中有,所以这个多个读取流要加入Vector集合中。

 注意:它只是对读取流进行合并。

它使用步骤:

1.      创建Vector<InputStream>

2.      将要合并的InputStream加入Vector

3.      通过Vector获取Enumeration

4.      创建SequenceInputStream,将Enumeration作为参数传入。

代码:

 


[java]  view plain copy print ?
  1. public class SequenceDemo {  
  2.   
  3.     /** 
  4.      * @param args 
  5.      * @throws Exception  
  6.      */  
  7.     public static void main(String[] args) throws Exception {  
  8.         // TODO Auto-generated method stub  
  9.         File file1 = new File("D:\\abc.txt");  
  10.         File file2 = new File("D:\\def.txt");  
  11.         File file3 = new File("d:\\java.txt");  
  12.               
  13.         //将三个文件封装到vector集合中  
  14.         Vector<FileInputStream> vec = new Vector<FileInputStream>();  
  15.           
  16.         vec.add(new FileInputStream(file1));  
  17.         vec.add(new FileInputStream(file2));  
  18.         vec.add(new FileInputStream(file3));  
  19.           
  20.         //返回Enumeration  
  21.         Enumeration<FileInputStream> enu = vec.elements();  
  22.         SequenceInputStream seq = new SequenceInputStream(enu);  
  23.         PrintStream ps = new PrintStream("d:\\合并.txt");  
  24.           
  25.         byte[] ch = new byte[1024];  
  26.         int len = 0;  
  27.         while((len = seq.read(ch))!= -1){  
  28.             ps.write(ch, 0, len);  
  29.         }  
  30.         seq.close();  
  31.         ps.close();  
  32.     }  
  33. }  
9、练习:文件切割与合并
[java]  view plain copy print ?
  1.   public class SplitAndMergeFile {  
  2.   
  3.     /** 
  4.      * @param args 
  5.      * @throws IOException  
  6.      */  
  7.     public static void main(String[] args) throws IOException {  
  8.         // TODO Auto-generated method stub  
  9. //      File pic = new File("d:\\pic.jpg");  
  10. //      splitFile(pic);  
  11.         mergeFile();  
  12.     }  
  13.       
  14.     public static void mergeFile() throws IOException{  
  15.         //Vector效率低,可以改用ArrayList  
  16.         ArrayList<FileInputStream> arr = new ArrayList<FileInputStream>();  
  17.         for(int x = 1;x<6;x++){  
  18.             arr.add(new FileInputStream("d:\\pic"+(x++)+".part"));    
  19.         }  
  20.         //因为it要被Enumeration的匿名内部类对象使用,所以要加final    
  21.          final Iterator<FileInputStream> it = arr.iterator();  
  22.         //定义Enumeration子类对象进行,使用ArrayList的迭代器复写其方法,其实与ArrayList关联    
  23.         Enumeration<FileInputStream> en =   
  24.   new Enumeration<FileInputStream>(){  
  25.             public boolean hasMoreElements(){  
  26.                 return it.hasNext();  
  27.             }  
  28.             public FileInputStream  nextElement(){  
  29.                 return it.next();  
  30.             }  
  31.           };  
  32.           //定义序列流,合并分割后的文件关联的流对象   
  33.           SequenceInputStream sis = new SequenceInputStream(en);  
  34.           
  35.           byte [] buf = new byte[1024];  
  36.           int len = 0;  
  37.           PrintStream ps = new PrintStream("d:\\合并得到的.jpg");  
  38.           while((len = sis.read(buf))!=-1){  
  39.               ps.write(buf, 0, len);  
  40.           }  
  41.             
  42.           sis.close();  
  43.           ps.close();  
  44.     }  
  45.       
  46.     public static void splitFile(File fNeedS) throws IOException{    //切割文件   
  47.         FileInputStream fis = new FileInputStream(fNeedS);  
  48.         PrintStream ps = null;    
  49.         //指定切割后每个文件的大小或者缓冲区的大小  
  50.         byte[] buf = new byte[1024*512];  
  51.         int len = 0;  
  52.         int count = 1;  
  53.         while((len = fis.read(buf))!=-1){  
  54.                //创建每个分割文件。  
  55.             ps = new PrintStream("d:\\pic"+(count++)+".part");  
  56.             ps.write(buf, 0, len);  
  57.             ps.close();  
  58.         }  
  59.         fis.close();  
  60.           
  61.     }   
  62. }  

10、

对象序列化

数据可以封装成对象,对象运行时是在堆内存中的,如果对象的数据需要存储在硬盘上,那么就要用到对象的序列化流。对象序列化(也叫对象的可串行性)其实就是对象持久化,把内存中的对象,变成硬盘上的文件内容。IO*对象序列化的流对象为ObjectInputStreamObjectOutputStream

注意:

1.    用ObjectOutputStream写入的的文件,只能用ObjectInputStream来重构读取。

2.    被序列化的对象必须实现Serializable接口。

3.    对象的静态成员和被transient关键字修饰的成员不能被序列化。(当对象在堆内存的私有对象不希望被序列化时,可以使用transient关键字)。

此外,序列化的文件一般以.ojbect作为类型后缀名,一个文件中可以存放多个不同类型的序列化对象。

Serializable接口

在对对象进行序列化时,必须实行Serializable接口,否则使用ObjectOutputStream写入时,会出现NotSerializableException异常。

Serializable接口并没必须要实现的方法,类定义时仅标示一下实现即可。实现Serializable的类,都有serialVersionUID,如果你没有在类中显式定义一个serialVersionUID,那么编译器会根据该类中的成员生成一个具有唯一性的serialVersionUID

显式定义serialVersionUID的好处:如果你在对类对象进行了序列化之后,又修改了这个类,那么再次读取修改前序列化的对象时,编译器可以识别;如果没有显式定义,你修改后的类经过编译器编译后会生成一个新的serialVersionUID,这个serialVersionUID跟修改前类的serialVersionUID不同,当你再次读取时,编译器会报出InvalidClassException异常。所以,如果类对象需要序列化,建议显式定义serialVersionUID


[java]  view plain copy print ?
  1. 代码示例:(借鉴优秀的博客思路,自己敲了一遍,掌握相关知识点)  
  2. import java.io.FileInputStream;  
  3. import java.io.FileOutputStream;  
  4. import java.io.IOException;  
  5. import java.io.ObjectInputStream;  
  6. import java.io.ObjectOutputStream;  
  7. import java.io.Serializable;  
  8.   
  9. public class ObjectStreamDemo {  
  10.   
  11.     /** 
  12.      * @param args 
  13.      * @throws Exception  
  14.      */  
  15.     public static void main(String[] args) throws Exception {  
  16.         // TODO Auto-generated method stub  
  17. //      WriteObject();  
  18.         ReadObject();  
  19.     }  
  20.       
  21.     public static void ReadObject() throws  Exception{  
  22.         //通过ObjectInputStream读取序列化后的对象   
  23.         ObjectInputStream ois =   
  24.                 new ObjectInputStream(new FileInputStream("D:\\caoRuiXiang.object"));  
  25.         Person cao = (Person) ois.readObject();  
  26.           
  27.         System.out.println(cao.getInfo());  
  28.     }  
  29.       
  30.     public static void WriteObject() throws IOException{  
  31.         //通过ObjectOutputStream将对象序列化  
  32.         ObjectOutputStream oop =   
  33.                   new ObjectOutputStream(new FileOutputStream("D:\\caoRuiXiang.object"));  
  34.           oop.writeObject(new Person("曹睿翔",23,"hongkang"));//country为静态,不能序列化,所以,写入文件中的不是“HongKang”,而是CN  
  35.           oop.close();  
  36.     }  
  37.   
  38. }  
  39.   
  40. class Person implements Serializable{  
  41.     public static final  long serialVersionUID = 12L;  
  42.     private String name;  
  43.     private int age; //age如果不想序列化,可以在前边加 transient 关键字,保证其值在堆内存中存在而不在文本文件中存在。   
  44.     static String county = "cn";  
  45.     public Person(String aName,int aAge,String cCounty){  
  46.         this.name = aName;  
  47.         this.age = aAge;  
  48.         this.county = cCounty;  
  49.     }  
  50.       
  51.     public String getInfo(){  
  52.         return this.name  +  this.age + county;  
  53.     }  
  54.       
  55. }  
  56. //Serializable serializable Serializable Serializable Serializable Serializable   
  57. //serialVersionUID serialVersionUID serialversionUID  

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

 

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

 

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

这就是数据目的地。

 

注意:

1.   因为这两个流对象都操作的是数组,并没有使用系统资源,所以,不用进行close关闭,即使你关闭了,它的其他方法还可以使用,而不会抛出IOException

2.   使用这对对象操作时,它的源和目的都是内存。

用途:

     这两个对象是在用流的思想来操作数组,当我们需要把一个文件中的数据加入内存中的数组时,就可以考虑用这个两个对象。此外,它还有writeToOutputStream os)可以把ByteArrayOutputStream对象内部定义的缓冲区内容,一次性写入os中。

操作字符数组、字符串的流对象类型与之相似,可以参与它们的使用方法。

在流操作规律讲解时:

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

 

 

[java]  view plain copy print ?
  1. import java.io.*;    
  2. class ByteArrayStream  {    
  3.     public static void main(String[] args)   {    
  4.         //数据源---字节数组,在内存中    
  5.         ByteArrayInputStream bis = new ByteArrayInputStream("ABCDEFG".getBytes());    
  6.     
  7.         //数据目的--bos内部封装的数组,在内存中    
  8.         ByteArrayOutputStream bos =new ByteArrayOutputStream();    
  9.     
  10.         int by = 0;    
  11.         while((by =bis.read())!=-1)  {    
  12.             bos.write(by);    
  13.         }    
  14.     
  15.         System.out.println(bos.size());//返回缓冲区大小    
  16.         System.out.println(bos.toString());//把缓冲区中的字节按照默认的编码转成为字符串返回。    
  17.     
  18.         //bos.writeTo(new FileOutputStream("a.txt"));//把bos内部的byte数组内容一次性写入字节输出流对象中。    
  19.     }    
  20. }    

12、管道流

管道流分为字节管道流(PipedInputStreamPipedOutputStream)和字符管道流(PipedReaderPipedWriter):它是IO技术和多线程技术的结合。在一条线程上写入的数据可以在另外一条线程上读取,它们是一对对配合使用的。如果在一条线程上使用管道读取和写入流会发生死锁的情况。

其使用步骤:

1.   分别定义写入和读取的Runnable接口子类,把相应的管道流作为构造参数传入给定义的私有管道流成员。

2.   将配对的管道流通过connect()方法连接起来。

3.    启动线程

[java]  view plain copy print ?
  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.   }  

13、RundomAccessFile(重点,即可读又可写)

      此类的实例支持对随机访问文件的读取和写入。

 

该类不是算是IO体系中子类。

而是直接继承自Object

 

但是它是IO包中成员。因为它具备读和写功能。

内部封装了一个数组,而且通过指针对数组的元素进行操作。

可以通过getFilePointer获取指针位置,

同时可以通过seek改变指针的位置。

 

 

其实完成读写的原理就是内部封装了字节输入流和输出流。

 

通过构造函数可以看出,该类只能操作文件。

而且操作文件还有模式:只读r,,读写rw等。

 

如果模式为只读 r。不会创建文件。会去读取一个已存在文件,如果该文件不存在,则会出现异常。

如果模式rw。操作的文件不存在,会自动创建。如果存则不会覆盖。

 

[java]  view plain copy print ?
  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.     }    
  13.     //读取,模式设置为“r”    
  14.     public static void readFile() throws IOException    
  15.     {    
  16.         RandomAccessFile raf = new RandomAccessFile("raf.txt","r");    
  17.         //调整对象中的指针,seek前后都能设置,所以比skipBytes使用范围广。    
  18.         //raf.seek(8*0);//里边存入的数据都是8个字节为一组,如果没有规律,读取就困难了    
  19.             
  20.         //跳过指定的字节数,只能往后走,不能往回走。    
  21.         raf.skipBytes(8);    
  22.     
  23.         byte [] buf = new byte[4];    
  24.         raf.read(buf);    
  25.         String name =new String(buf);    
  26.         int age = raf.readInt();    
  27.         System.out.println("name="+name);    
  28.         System.out.println("age="+age);    
  29.         raf.close();    
  30.     }    
  31.     
  32.     public static void writeFile_2()  throws IOException    
  33.     {    
  34.         RandomAccessFile raf = new RandomAccessFile("raf.txt","rw");    
  35.         raf.seek(8*0);//修改数据,网络分段下载原理,要重点掌握。    
  36.         raf.write("周期".getBytes());    
  37.         raf.writeInt(103);          
  38.         raf.close();    
  39.     }    
  40.     //写入,模式设置为“rw”    
  41.     public static void writeFile()  throws IOException    
  42.     {    
  43.         RandomAccessFile raf = new RandomAccessFile("raf.txt","rw");    
  44.         raf.write("李四".getBytes());    
  45. //      raf.write(97);write(int x)方法只写入低8位。如果写入的数字在byte取值范围内,那么可以read()正常读取,如果超出,读取时就会出现数据错乱。    
  46.         raf.writeInt(97);//要把四个字节都写入,所以用writeInt    
  47.         raf.write("王五".getBytes());    
  48.         raf.writeInt(99);    
  49.         raf.close();    
  50.     }    
  51. }    

编码的练习及总结:

 

字符流的出现是为了方便操作字符数据,其方法操作的原因是因为内部加入了编码表。Java中能够实现字节根据指定编码表转成字符的,有四个类:InputStreamReaderOutputStreamWriterPrintStreamPrintWriter。它们都能够加构造时,指定编码表;但后两个是打印流,只能用于打印,使用有局限,所以相对而言,还是前两个转换流使用多一些。

编码表的由来

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

常见的编码表

地域码表

1.      ASCII:美国码表,息交换码,用一个字节的7位表示。

2.      ISO8859-1:欧洲码表,拉丁码表,用一个字节的8位表示,最高位1

3.      GB2312:中国中文编码表,它用两个字节表示,为兼容ASCII,它的两个字节的高位都是1,也即是两个负数;但与ISO8859-1冲突。大概有六七千个字。

4.      GBK:中国的中文编码表的升级版,扩容到2万多字。

通用码表:

1.      Unicode:国际标准码,融合多种语言文字。所有的文字都用两个字节表示,Java默认使用的就是Unicode

2.      UTF-8:UnicodeTransform Format -8Unicode码把用一个字节能装下的文字,也用两个字节表示,有些浪费空间,对之进行优化的结果就是UTF-8UTF-8编码表,一个文字最用一个字节表示,最多用3个字节表示,并且每个字节开始都有标识头,所以很容易于其他编码表区分出来。

     代码示例: 

[java]  view plain copy print ?
  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. }    

编码问题的产生与解决

从上边的那些编码表可以看出,GBKUnicode都能识别中文,那么当一台电脑使用GBK,而另一台电脑使用Unicode时,虽然在各自的电脑上都能识别中文,但他们其中一方向另一方发送中文文字时,另一方却不能识别,出现了乱码。这是一万年GBKUnicode虽然都能识别中文,但对同一个中文文字,他们在两个编码表对应的编码值不同。这时,在解读别人传来的中文数据时,就需要指定解析中文使用的编码表了。

而转换流就能指定编码表,它的应用可以分为:

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解码时,得到的仍然是乱码,解码仍然失败。


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


IO综合练习:录入学生成绩并将信息存储在硬盘文件中。

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