5.2 IO流(File類,Propertis配置文件,其他類(打印流,序列流,操作對象的流(序列化接口),隨機訪問文件的流,管道流,操作基本數據的流,操作數組的流,操作字符串的流),編碼表)

1.File類

IO流的流對象只能操作設備上的數據。

File類:

1.用來將文件或者文件夾(也稱目錄)封裝成對象。
2.方便對文件和文件夾的屬性信息進行操作。(操作文件夾,文件的屬性(創建時間,修改時間,只讀,只寫等)等)
3.File對象可以作爲參數傳遞給流的構造函數。(這種用的較多)
    如:  FileReader(File file) 
            在給定從中讀取數據的 File 的情況下創建一個新 FileReader。

4.File類的常用方法:
①構造方法:
    1.File(String pathname) 
          通過將給定路徑名字符串轉換爲抽象路徑名來創建一個新 File 實例。

    示例:
    //可以將一個已存在的,或者不存在的文件或者目錄封裝成file對象。
    File file = new File("a.txt");
    File file = new File("C:\\a.txt");

    2.File(String parent, String child) 
          根據 parent 路徑名字符串和 child 路徑名字符串創建一個新 File 實例。

    示例:
    File file = new File("C:\\", "a.txt");

    3.File(File parent, String child) 
          根據 parent 抽象路徑名和 child 路徑名字符串創建一個新 File 實例。

    示例:
    File f = new File("C:\\");
    File f1 = new File(f, "a.txt")


②字段:
    1.separatorChar
        public static final char separatorChar
    與系統有關的默認名稱分隔符。此字段被初始化爲包含系統屬性 file.separator 值的第一個字符。在 UNIX 系統上,此字段的值爲 '/';在 Microsoft Windows 系統上,它爲 '\\'。  

    separator
        public static final String separator
    與系統有關的默認名稱分隔符,爲了方便,它被表示爲一個字符串。此字符串只包含一個字符,即 separatorChar。 

    跨平臺:
    File file = new File("C:"+System.getProperty("file.separator") + "a.txt");
    File file = new File("C:"+ File.separator + "a.txt");

    2.pathSeparatorChar
        public static final char pathSeparatorChar
    與系統有關的路徑分隔符。此字段被初始爲包含系統屬性 path.separator 值的第一個字符。此字符用於分隔以路徑列表 形式給定的文件序列中的文件名。在 UNIX 系統上,此字段爲 ':';在 Microsoft Windows 系統上,它爲 ';'。 

        pathSeparator
            public static final String pathSeparator
        與系統有關的路徑分隔符,爲了方便,它被表示爲一個字符串。此字符串只包含一個字符,即 pathSeparatorChar。 


③常見方法:

/*
1.獲取:
    1.1獲取文件名稱
        String getName() 
                    返回由此抽象路徑名錶示的文件或目錄的名稱。 

    1.2獲取文件路徑
        String getPath()  //給什麼路徑獲得什麼路徑
                將此抽象路徑名轉換爲一個路徑名字符串。 
            String getAbsolutePath() 
                返回此抽象路徑名的絕對路徑名字符串。 
            File getAbsoluteFile()   //獲得絕對路徑的File對象
                返回此抽象路徑名的絕對路徑名形式。 

    1.3獲取文件大小
        long length() 
                返回由此抽象路徑名錶示的文件的長度。 

    1.4獲取文件的修改時間
        long lastModified() 
                返回此抽象路徑名錶示的文件最後一次被修改的時間。 

2.創建與刪除:
        2.1創建
             boolean createNewFile() 
                            當且僅當不存在具有此抽象路徑名指定名稱的文件時,不可分地創建一個新的空文件。 
            static File createTempFile(String prefix, String suffix) 
                            在默認臨時文件目錄中創建一個空文件,使用給定前綴和後綴生成其名稱。 
            static File createTempFile(String prefix, String suffix, File directory) 
                            在指定目錄中創建一個新的空文件,使用給定的前綴和後綴字符串生成其名稱。 

            文件夾的創建:
                boolean mkdir() 
                            創建此抽象路徑名指定的目錄。 
                boolean mkdirs()    //delete刪除時,刪除的是最後一個文件夾
                            創建此抽象路徑名指定的目錄,包括所有必需但不存在的父目錄。 

        2.2刪除
            boolean delete() 
                    刪除此抽象路徑名錶示的文件或目錄。 
                            ①如果文件正在被流操作,文件刪除不了。
                            ②如果目錄內有內容,目錄刪不了。
                            ③刪除了不經過回收站。
                void deleteOnExit()     //告訴虛擬機在退出時刪除
                    在虛擬機終止時,請求刪除此抽象路徑名錶示的文件或目錄。 

    3.判斷:
            存在: boolean exists() 
                                測試此抽象路徑名錶示的文件或目錄是否存在。 
                絕對路徑: boolean isAbsolute() 
                                測試此抽象路徑名是否爲絕對路徑名。 
                目錄:boolean isDirectory() 
                                測試此抽象路徑名錶示的文件是否是一個目錄。 
                文件:boolean isFile() 
                                測試此抽象路徑名錶示的文件是否是一個標準文件。                 
                隱藏:boolean isHidden()   //系統級隱藏文件的判斷方式不一樣,這裏不考慮過濾系統級隱藏文件
                                測試此抽象路徑名指定的文件是否是一個隱藏文件。                 
                可執行:boolean canExecute() 
                                測試應用程序是否可以執行此抽象路徑名錶示的文件。                
                可讀: boolean canRead() 
                                測試應用程序是否可以讀取此抽象路徑名錶示的文件。 
                可寫:boolean canWrite() 
                                測試應用程序是否可以修改此抽象路徑名錶示的文件。 

        4.重命名:boolean renameTo(File dest)    //如果被重命名文件不存在,重命名失敗,返回false
                                    重新命名此抽象路徑名錶示的文件。                                            
                        File dest的作用:確定重命名的名字和重命名後文件的位置。
                                                    如果位置變化了,原來的文件會被刪除,即剪切的意思。

        5.盤空間操作:        
                文件系統根(盤符):static File[]     listRoots()    //因爲盤符名是路徑,所以getName獲取的爲空字符串。
                                                                                            //可以通過getPath獲得抽象路徑。
                                                    列出可用的文件系統根。 
                分區大小:long getTotalSpace() 
                                        返回此抽象路徑名指定的分區大小。
                分區可用空間大小(常用): long getFreeSpace() 
                                        返回此抽象路徑名指定的分區中未分配的字節數。
                分區可用於虛擬機的空間大小:long getUsableSpace() 
                                        返回此抽象路徑名指定的分區上可用於此虛擬機的字節數。 
         6.獲取目錄內容:
            注意:
            ①如果File對象裏的路徑不是目錄或者是系統級目錄(如各盤的隱藏的系統信息卷標目錄等),就是會返回null,然後使用時報空指針異常,數組就沒有創建成功。
            ②如果是空目錄,則數組存在,但是長度爲0,遍歷時輸出的是數組的地址。

                獲取目錄下的文件和目錄(包括隱藏的):String[] list()  
                                                    返回一個字符串數組,這些字符串指定此抽象路徑名錶示的目錄中的文件和目錄。 
                獲取目錄下的文件和目錄 的 File對象:File[] listFiles()  //可以使用getPath獲取抽象路徑,通過getName獲得文件名稱
                                                    返回一個抽象路徑名數組,這些路徑名錶示此抽象路徑名錶示的目錄中的文件。

                帶過濾器:
                String[] list(FilenameFilter filter)   //文件名過濾器。數組中存放的是getName()的文件名。
                                返回一個字符串數組,這些字符串指定此抽象路徑名錶示的目錄中滿足指定過濾器的文件和目錄。 
                File[] listFiles(FileFilter filter)     //文件過濾器 。數組中存放的是getpath()的路徑。
                                返回抽象路徑名數組,這些路徑名錶示此抽象路徑名錶示的目錄中滿足指定過濾器的文件和目錄。 

                File[] listFiles(FilenameFilter filter)  //這個方法可以在文件過濾器中通過getName實現文件名的獲取。用第二個方法代替。
                                返回抽象路徑名數組,這些路徑名錶示此抽象路徑名錶示的目錄中滿足指定過濾器的文件和目錄。 
*/  
常見方法示例:

public class FileMethodDemo {

    public static void main(String[] args) throws IOException {


//      getDemo();
//      createAndDeleteDmoe();
//      isDemo();
//      renameToDemo();
//      listRootsDemo();
//      panDemo();
        listDemo();

    }



//獲取:

        public static void getDemo() throws IOException{
            File file = new File("D:\\Workspaces\\MyEclipse Professional 2014\\IO\\utf8_1.txt");

            String name = file.getName();  //文件名

            String absPath = file.getAbsolutePath(); //獲得所給路徑的 絕對路徑
//          File absPathFile = file.getAbsoluteFile(); //返回絕對路徑的File對象


            String Path = file.getPath();//獲得指定的路徑(相對或者絕對看指定的是什麼路徑)
            String parentPath = file.getParent();  //父目錄,在給出的是絕對路徑時返回父目錄路徑,否者返回null

            long len = file.length();  //文件大小

            long time = file.lastModified();  //修改時間:作用,通過另一個線程監控判斷 修改時間是否改變,從而可以對文件進行自動刷新的操作。

            System.out.println("name:\t\t" + name);
            System.out.println("absPath:\t" + absPath);
            System.out.println("Path:\t\t" + Path);
            System.out.println("parPath:\t" + parentPath);
            System.out.println("len:\t\t" + len);
            System.out.println("time:\t\t" + new SimpleDateFormat("yyyy/MM/dd H:m:s").format(new Date(time)));
        }


//文件和目錄 創建和刪除:      

        public static void createAndDeleteDmoe() throws IOException{
            File file = new File("abc.txt");

//文件的刪除和創建:

            /*
                    和輸出流不一樣,如果文件不存在,則創建,如果文件存在,則不創建。
                    (當然輸出流可以在構造函數中定義追加模式,不覆蓋)            
             */
            boolean b = file.createNewFile();  //如果文件存在,就不創建,返回false
            System.out.println("b = " + b);

            boolean c = file.delete(); //文件不存在時,返回false
            System.out.println("c = " + c);

//文件夾的刪除和創建:

            File file1 = new File("abc");
            boolean d = file1.mkdir();   //文件夾存在時,返回false
            System.out.println("d = " + d);
            file1.delete();

            File file2 = new File("ABC\\abc");  //創建多級目錄
            file2.mkdirs();   //文件目錄存在時,返回false
            file2.delete();    //刪除時,刪除的是最後一個文件夾


        }


//判斷:
        public static void isDemo(){
            File f = new File("demo12.txt");

            boolean b = f.exists();  //是否存在,如果文件不存在,下面的全都是flase。
                                                            //所在執行其他判斷操作時,應先判斷文件是否存在。
            System.out.println("b = "+ b);

            boolean c = f.isAbsolute();  //是否是絕對路徑
            System.out.println("c = "+ c);

            boolean d = f.isDirectory();  //是否是目錄
            System.out.println("d = "+ d);

            boolean e = f.isFile();  //是否是文件
            System.out.println("e = "+ e);

            boolean a = f.isHidden();  //是否隱藏
            System.out.println("a = "+ a);

            boolean  b1= f.canExecute();  //是否能執行
            System.out.println("b1 = "+ b1);

            boolean b2 = f.canRead();  //是否可讀
            System.out.println("b2 = "+ b2);

            boolean b3 = f.canWrite();  //是否可寫
            System.out.println("b3 = "+ b3);    

        }

//重命名:
        public static void renameToDemo() throws IOException{
            File f1 = new File("D:\\demo1.mp3");
//          File f2 = new File("D:\\demo2.mp3");

            boolean b = f1.renameTo(new File("D:\\demo2.mp3"));
            System.out.println("b = " + b);

        }


//列出系統根
        public static void listRootsDemo(){
            File[] fileArr = File.listRoots();

            for(File f :fileArr){
                System.out.println(f); 
/*  顯示:
                C:\
                D:\
                E:\
                H:\
*/
            }
        }


//盤符空間操作:

        private static void panDemo() {
                File f = new File("D:\\");
                //磁盤空間操作:總大小,剩餘可用空間。
                System.out.println("getTotalSpace:"+ new DecimalFormat("0.000").format(f.getTotalSpace()/1024.0/1024.0/1024.0) + "G"); //分區大小
                System.out.println("getFreeSpace:" + new DecimalFormat("0.000").format(f.getFreeSpace()/1024.0/1024.0/1024.0) + "G");//分區可用空間
                System.out.println("getUsableSpace:"+new DecimalFormat("0.000").format(f.getUsableSpace()/1024.0/1024.0/1024.0) + "G");//分區可用於虛擬機的空間大小

        //找出剩餘空間最大的盤:數組法比較合理

                //①數組法:

                File[] fileArr = File.listRoots();
                Long[] longArr = new Long[fileArr.length];

                int a = 0;
                for(File f1:fileArr){
                        longArr[a] = f1.getFreeSpace();
                        a++;
                }
                Long[] longArr_copy = Arrays.copyOf(longArr,longArr.length); //排序前先做個副本,用於後面根據最值查原來的角標

                Arrays.sort(longArr);
                System.out.println("剩餘空間最大的盤是:" + fileArr[Arrays.binarySearch(longArr_copy, longArr[fileArr.length-1])].toString().charAt(0) 
                        + "盤\t剩餘:" + new DecimalFormat("0.000").format(longArr[fileArr.length-1]/1024.0/1024.0/1024.0) + "G");


/*              //②Map集合法:空間容量作爲鍵(雖然做出來了,但容量作爲鍵有些不合理,不能體現唯一性)
                File[] fileArr = File.listRoots();
                Map<Long, File> map = new TreeMap<Long, File>();
                for(File file :fileArr){
                    map.put(file.getFreeSpace(), file);
                }

                Iterator<Long> ite = map.keySet().iterator();
                while(true){
                    Long key =  ite.next();
                if(!ite.hasNext()){
                    String s= new DecimalFormat("0.0").format(key/1024.0/1024.0/1024.0);
                    System.out.println("剩餘空間最大的盤是:"+ map.get(key).toString().charAt(0) + "盤     "+ " 剩餘  " + s + "G");
                    break;
                    }
                }

                //顯示所有的盤符和剩餘空間:             
//              Iterator<Long> it = map.keySet().iterator();
                for(Iterator<Long> it = map.keySet().iterator();it.hasNext();){
                        Long key = it.next();
                        File value = map.get(key);
                        String s= new DecimalFormat("0.0").format(key/1024.0/1024.0/1024.0);
                        System.out.println(value.toString().charAt(0) + " :  剩餘  " + s + "G");
                }*/
            }


//目錄內容獲取和過濾:過濾器


        public static void listDemo(){
            File file = new File("D:\\");
//          for(String s: file.list()){
//              if(s.endsWith(".java"))   //手動過濾
//              System.out.println(s);
//          }


            //過濾器:
                    //需求1:只要D:\中.java文件。
/*          for(String s:file.list(new FilterByJava())){
                System.out.println(s);
            }*/

                //需求2:只要D:\中不是隱藏的.txt文件。
                //改進:因爲常看隱藏屬性,是File對象的功能,所以這裏用listFiles(),返回目錄列表的Files對象形式。
            for(File f:file.listFiles(new FilterByHidden())){
                System.out.println(f.getName());
            }


/*          for(String s:file.list(new SuffixFilter(".txt"))){    //提供構造方法的專屬後綴名過濾器
                System.out.println(s);
            }
            */
        }

}

//過濾器
/*class FilterByJava implements FilenameFilter{  //文件名過濾器

    @Override
    public boolean accept(File dir, String name) {
            return name.endsWith(".java");
    }

}*/

class FilterByHidden implements FileFilter{  //文件過濾器

    @Override
    public boolean accept(File pathname) {
        if(!pathname.isHidden()){    //系統級隱藏文件的判斷方式不一樣,這裏不考慮過濾系統級隱藏文件
            if(pathname.getName().endsWith(".txt"))
                return true;
        }           
        return false;
    }

}


//後綴名過濾器,提供構造函數,免得修改過濾器源碼

class SuffixFilter implements FilenameFilter{

    private String suffix;
    public SuffixFilter(String suffix) {
//      super();
        this.suffix = suffix;
    }

    @Override
    public boolean accept(File dir, String name) {

        return name.endsWith(suffix);
    }

}

2.File練習:
1.深度遍歷文件夾:遞歸

/*
        需求:對指定目錄進行所有內容的列出(包含子目錄中的內容)
        即深度遍歷
*/
public class Demo1 {

    public static void main(String[] args) {
        File file = new File("D:\\A");

        demo1(file,0);
    }

    public static void demo1(File file,int level) {
        System.out.println(getSpace(level) + file.getName());   
        level++;
        for(File f:file.listFiles()){     //默認先讀文件,後讀文件夾
            if(f.isDirectory()){
                    demo1(f,level);
            }
            else
                System.out.println(getSpace(level)+ f.getName());

        }
    }

    public static String getSpace(int level) {
        StringBuilder sb = new StringBuilder();
        sb.append("|--");
        for(int i = 0;i<level;i++)
            sb.insert(0,"|   ");
        return sb.toString();
    }

}

/*

顯示:
|--A
|   |--1.txt
|   |--2.txt
|   |--B
|   |   |--3.txt
|   |   |--4.txt
|   |   |--C
|   |   |   |--5.txt

*/

2.遞歸:

/*
        遞歸:函數自身直接或者間接的調用了自身。

        注意:
        1.遞歸一定要明確條件,否者容易棧溢出。
        2.注意遞歸的次數。



*/
public class DiGuiDemo {

    public static void main(String[] args) {

        toBin(6);  //二進制
        System.out.println(getSum(1500));  //求和

    }


    public static void toBin(int num) {
        if(num>0){
            System.out.println(num%2);  //可以放下面
            toBin(num/2);
        }
    }

    public static int getSum(int num) {
        if(num == 1)
            return 1;

        return num + getSum(num-1);
    }

}

遞歸圖解:
這裏寫圖片描述

這裏寫圖片描述

3.刪除一個帶內容的目錄:

/*
        刪除一個帶內容的目錄。

        原理:必須從最裏面往外刪。
        需要進行深度遍歷。
 */

public class DeleteDemo {

    public static void main(String[] args) {
        File file = new File("D:\\A");

        deleteDemo(file);   //刪除目錄


    }

    public static void deleteDemo(File file) {
        for(File f: file.listFiles()){

            if(f.isDirectory()){   //是目錄
                deleteDemo(f);  //刪目錄下的文件
            }
            else
                f.delete();   //刪目錄下的文件                         
        }
                file.delete(); //從後往前,依次刪除掉所有空文件夾。              
    }

}

4.綜合練習:文件清單列表:

/*
            獲取指定目錄下,指定擴展名的文件(包含子目錄中的)
            這些文件的絕對路徑寫入到一個文本文件中。

            就是建立一個指定擴展名的文件的列表。

        思路:
        1.必須進行深度遍歷
        2.要在遍歷的過程中進行過濾。將符合條件的內容,都存儲到容器中。
        3.對容器中的內容進行遍歷並將絕對路徑寫到文件中。

 */

public class Demo2 {

    public static void main(String[] args) throws IOException {

            File file = new File("D:\\coding");   //被過濾的文件

            FilterByName filter = new FilterByName(".java");  //過濾器

            LinkedList<File> list = new LinkedList<File>();  //過濾的File對象集合

            File f = new File(file,".java.txt");   //被寫入的文件

            getFiles(file,filter,list);         
            writeToFile(list,f);

    }

    public static void writeToFile(LinkedList<File> list, File f)throws IOException{
        BufferedWriter bufw =null;
        try {
            bufw = new BufferedWriter(new FileWriter(f));
            for(File f1:list){
                bufw.write(f1.getPath());
                bufw.newLine(); 
                bufw.flush();
            }

        }/*catch(IOException e){
            throw new RuntimeException("寫入失敗");
        }*/finally{
            if(bufw!=null)
                try {
                    bufw.close();
                } catch (IOException e) {

                    throw new RuntimeException("關閉失敗");
                }
        }


    }

    public static void getFiles(File file, FilterByName filter,LinkedList<File> list) throws IOException {
        for(File f:file.listFiles(filter)){    //使用過濾器代替
            if(f.isDirectory()){
                getFiles(f,filter,list);
            }
/*          else if(f.getName().endsWith(s)){
                bufw.write(f.getPath());
                bufw.newLine();
                bufw.flush();
            }*/
            else{
                list.add(f);
            }
        }

    }

}

class FilterByName implements FilenameFilter{
    private String suffix;

    public FilterByName(String suffix){
        super();
        this.suffix = suffix;
    }

    @Override
    public boolean accept(File dir, String name) {

        return name.endsWith(suffix);
    }

}

3 .配置文件設置:Properties集合
Hashtable:內部機構是哈希表,是同步的。不允許null作爲鍵和值。
子類 Properties:用來存儲鍵值對形式的配置文件的信息。可以和IO技術相結合。

API:
public class Propertiesextends Hashtable<Object,Object>
    Properties 類表示了一個持久的屬性集。Properties 可保存在流中或從流中加載。屬性列表中每個鍵及其對應值都是一個字符串。 


Properties集合:
特點:
1.該集合中的鍵和值都是字符串類型。
2.集合中的數據可以保存到流中,或者從流中獲取。

通過該集合用於操作以鍵值對形式存在的配置文件。 還有一種,xml也是存儲配置文件的。

特有方法:
①存: Object setProperty(String key, String value) 
          調用 Hashtable 的方法 put。 
②取:String getProperty(String key) 
          用指定的鍵在此屬性列表中搜索屬性。 

③全取: Set<String> stringPropertyNames() 
          返回此屬性列表中的鍵集,其中該鍵及其對應值是字符串,如果在主屬性列表中未找到同名的鍵,則還包括默認屬性列表中不同的鍵。 

④與輸出流結合,顯示Properties列表:  void list(PrintStream out) 
                              將屬性列表輸出到指定的輸出流。 
⑤持久化存儲:
    void store(OutputStream out, String comments)   //comments - 屬性列表的描述。 
              以適合使用 load(InputStream) 方法加載到 Properties 表中的格式,將此 Properties 表中的屬性列表(鍵和元素對)寫入輸出流。 
     void store(Writer writer, String comments) 
              以適合使用 load(Reader) 方法的格式,將此 Properties 表中的屬性列表(鍵和元素對)寫入輸出字符。 
⑥讀取:
    void load(InputStream inStream) 
          從輸入流中讀取屬性列表(鍵和元素對)。 
     void load(Reader reader) 
          按簡單的面向行的格式從輸入字符流中讀取屬性列表(鍵和元素對)。 
示例:
public class PropertiesDemo {

    public static void main(String[] args) throws IOException {

//      propertiesDemo();
//      propertiesDemo2();
//      propertiesDemo3();
//      propertiesDemo4();
        loadDemo();
    }



//模擬load()原理:

public static void loadDemo() throws IOException {
        Properties prop = new Properties();
        BufferedReader bufr = new BufferedReader(new FileReader("info.txt"));
        String line = null;
        while((line = bufr.readLine())!= null){
            if(!line.startsWith("#")){   //排除註釋內容
                String[] arr = line.split("=");
                prop.setProperty(arr[0], arr[1]);

//              System.out.println(arr[0]+"::" +arr[1]);
            }
        }
            prop.list(System.out);
    }




//讀取文本  中的配置信息

public static void propertiesDemo4() throws IOException {
    Properties prop = new Properties();
        //集合中的數據來自於一個文件。
        //注意:必須保證該文件中的數據是鍵值對。
            //需要使用讀取流。
        FileInputStream fis = new FileInputStream("info.txt");
        prop.load(fis);  //從流中獲取    
        prop.list(System.out);      

    }






//  持久化存儲元素

public static void propertiesDemo3() throws IOException {
    Properties prop = new Properties();
    //存儲元素
    prop.setProperty("zhang", "30");
    prop.setProperty("wang", "50");
    prop.setProperty("kjd", "34");
    prop.setProperty("sss", "56");

    //持久化存儲,需要關聯到輸出流
    FileOutputStream fos = new FileOutputStream("info.txt");
    prop.store(fos, "name + age");   //鍵值和註釋不要寫中文信息。因爲此方法有自己特定的輸出方式。
    fos.close();

    /*  內容:
    #name + age
    #Wed Nov 01 06:44:02 CST 2017
    kjd=34
    zhang=30
    sew=56
    wang=50
*/
    }





//演示Properties集合和流對象相結合的功能。
    public static void propertiesDemo2(){
        Properties prop = new Properties();
        //存儲元素
//      prop.setProperty("zhang", "30");
//      prop.setProperty("wang", "50");
//      prop.setProperty("kjd", "34");
//      prop.setProperty("sew", "56");

        prop =  System.getProperties();
        prop.list(System.out);  //一般顯示出來,調試用的,並不能操作裏面的值。

    }


    // Properties集合的存和取

    public static void propertiesDemo() {
        Properties prop = new Properties();

        prop.setProperty("zhang", "30");
        prop.setProperty("wang", "50");
        prop.setProperty("kjd", "34");
        prop.setProperty("sew", "56");

        //修改元素
        prop.setProperty("wang", "32");

        //取出所有元素
        Set<String> names = prop.stringPropertyNames();

        for (String name : names) {
            String value = prop.getProperty(name);
            System.out.println(name +": " + value);
        }
    }

}

Properties集合練習:

1.對已有的配置文件中的信息進行修改:

/*
        對已有的配置文件中的信息進行修改。

        讀取這個文件。
        並將這個文件的鍵值數據存儲到集合中。
        再通過集合對數據進行修改。
        再通過流將修改後的集合存儲到文件中。

 */

    public static void test() throws IOException{
        File file = new File("info.txt");
        if(!file.exists()){
            file.createNewFile();
        }
        FileReader fr = new FileReader(file);
        Properties prop = new Properties();
        prop.load(fr);
        prop.setProperty("sss", "16");

        FileWriter fw = new FileWriter(file);  //和文件相關聯,新建一個文件。
                    //必須放在讀取原文件結束之後,或者加true追加模式。
        prop.store(fw, "...");

        prop.list(System.out);  //測試

        fr.close();
        fw.close();

    }

2.定義功能,獲取一個應用程序運行的次數,如果超過5次,給出使用次數已到,請註冊的提示。並不要再運行程序。

/*
        定義定義功能,獲取一個應用程序運行的次數,如果超過5次,給出使用次數已到,請註冊的提示。並不要再運行程序。

        思路:
        1.應該有計數器。
        每次程序啓動都需要計數一次,並且是再原有的次數上進行計數。
        2.計數器就是一個變量。程序啓動時進行計數,計數器必須存在於內存並進行運算。
            可是程序一結束,計數器消失了。那麼再次啓動該程序,計數器又重新被初始化。
            而我們需要多次啓動同一個應用程序,使用的是同一個計數器。
            這就需要計數器的生命週期變長,從內存存儲到硬盤文件中。

        3.如何使用這個計數器。
                首先,程序啓動時,應該先讀取這個用於記錄計數器信息的配置文件。
                獲取上一次計數器次數。並進行使用次數的判斷。
                其次,對該次數進行自增,並把自增後的次數重新存儲到配置文件中。
        4.文件中的信息進行存儲並體現。
                直接存儲次數值可以,但是不明確該數據的含義。所以起名字很重要。
                這就有了名字和值的對應,所以可以使用鍵值對。
                可以使用映射關係map集合搞定,又需要讀取硬盤上的數據,所以map + io = Properties。
                如果是更復雜的數據,用xml。

 */
public class Demo3 {

    public static void main(String[] args) throws IOException {

        int i = 5;   
        String s = "notepad.exe";

        Demo(s,i);
    }

    public static void Demo(String s, int i) throws IOException {
            Process p = Runtime.getRuntime().exec(s);   //啓動程序

            File file = new File("notepad.properties");
            if(!file.exists()){
                file.createNewFile();
            }
            FileReader fr= new FileReader(file);

            Properties prop =new Properties();

            prop.load(fr);      //加載配置文件

            String value = prop.getProperty("times");   //獲取times值

            int count = 0;   //計數器
            if(value!=null) {
                count = Integer.parseInt(value);  //獲取上一次的值                     
            }
            count++;

            prop.setProperty("times", String.valueOf(count)); 

            if(count > i){        //判斷配置文件的times值
                System.out.println("5次免費使用次數已到,請註冊!");
                prop.setProperty("times", String.valueOf(i));
                p.destroy();    //停止程序
            }


            FileWriter fw = new FileWriter(file);    //更新配置文件

            prop.store(fw, "TimeCon");

            fr.close();
            fw.close();

    }

}

xml:使用的是標籤

<Persons>
    <Person id = "001">
        <name>zhansan</name>
        <age>18</age>
    </Person>

    <Person id = "005">
        <name>lisan</name>
        <age>17</age>
    </Person>

</Persons>

4 .IO包中的其他類:

1.打印流:操作目的
①PrintStream

/*
        打印流PrintStream:字節流的擴展形式。print()將字節流封裝成了字符流。
        1.提供了打印方法可以對多種數值類型進行打印。並保持數據的表現形式。
        2.它不拋IOException

        API:
        public class PrintStream
            extends FilterOutputStream
            implements Appendable, Closeable
            1.PrintStream 爲其他輸出流添加了功能,使它們能夠方便地打印各種數據值表示形式。
            2.另外,爲了自動刷新,可以創建一個 PrintStream,這時參數爲true。這意味着可
            在寫入 byte 數組之後自動調用 flush 方法:可調用其中一個 println 方法;
            或寫入一個換行符或字節 ('\n'),然後flush。 
            3.PrintStream 打印的所有字符都使用平臺的默認字符編碼轉換爲字節。
            在需要寫入字符而不是寫入字節的情況下,應該使用 PrintWriter 類。

        構造函數:
        1.(字符串)路徑
                PrintStream(File file) 
                        創建具有指定文件且不帶自動行刷新的新打印流。 
                PrintStream(File file, String csn) 
                        創建具有指定文件名稱和字符集且不帶自動行刷新的新打印流。 

        2.File對象
                PrintStream(String fileName) 
                        創建具有指定文件名稱且不帶自動行刷新的新打印流。 
                PrintStream(String fileName, String csn) 
                        創建具有指定文件名稱和字符集且不帶自動行刷新的新打印流。 

        3.字節輸出流
                PrintStream(OutputStream out) 
                        創建新的打印流。 
                PrintStream(OutputStream out, boolean autoFlush) 
                        創建新的打印流。 
                PrintStream(OutputStream out, boolean autoFlush, String encoding) 
                        創建新的打印流。 

          方法:
          1.void print(boolean b) 
                        打印 boolean 值。 
          2.void write(int b) 
                        將指定的字節寫入此流。 
                void write(byte[] buf, int off, int len) 
                        將 len 字節從指定的初始偏移量爲 off 的 byte 數組寫入此流。 

 */


public class Demo4 {

    public static void main(String[] args) throws IOException {

        PrintStream out = new PrintStream("print.txt");

//      out.write(97);  //只寫入最後的八位,只寫一個字節。a

        out.print(97);  //打印並保持數據的表現形式。將97先變成字符保持原樣將數據打印到目的地

        out.close();
    }

}

②PrintWriter: web開發最常用

/*
        打印流PrintWriter:字符流的擴展形式。此類實現在 PrintStream 中的所有 print 方法。
        1.提供了打印方法可以對多種數值類型進行打印。並保持數據的表現形式。
        2.它不拋IOException

        API:
            public class PrintWriter
        extends Writer
            1.向文本輸出流打印對象的格式化表示形式。
            2.與 PrintStream 類不同,如果啓用了自動刷新,則只有在
            調用 println、printf 或 format 的其中一個方法時纔可能完成此操作,
            而不是每當正好輸出換行符時才完成。這些方法使用平臺自有的行分隔符概念,而不是換行符。


        構造函數:比字節打印流多了一個接收字符流對象
        1.(字符串)路徑
                PrintWriter(File file) 
                        創建具有指定文件且不帶自動行刷新的新打印流。 
                PrintWriter(File file, String csn) 
                        創建具有指定文件名稱和字符集且不帶自動行刷新的新打印流。 

        2.File對象
                PrintWriter(String fileName) 
                        創建具有指定文件名稱且不帶自動行刷新的新打印流。 
                PrintWriter(String fileName, String csn) 
                        創建具有指定文件名稱和字符集且不帶自動行刷新的新打印流。 

        3.字節輸出流
                PrintWriter(OutputStream out) 
                        創建新的打印流。 
                PrintWriter(OutputStream out, boolean autoFlush)  
                 //autoFlush - boolean 變量;如果爲 true,則 println、printf 或 format 方法將刷新輸出緩衝區
                        創建新的打印流。 
        4.字符輸出流:
                PrintWriter(Writer out) 
                        創建不帶自動行刷新的新 PrintWriter。 
                PrintWriter(Writer out, boolean autoFlush) 
                        創建新 PrintWriter。 


          方法:
          1.void print(boolean b) 
                        打印 boolean 值。 
          2.void write(int b) 
                        將指定的字節寫入此流。 
                void write(byte[] buf, int off, int len) 
                        將 len 字節從指定的初始偏移量爲 off 的 byte 數組寫入此流。 

 */


public class Demo4 {

    public static void main(String[] args) throws IOException {

        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter out = new PrintWriter(new FileWriter("out.txt"),true);   //自動刷新開啓

        String line = null;
        while((line = bufr.readLine())!= null){
            if("over".equals(line))
                break;
            out.println(line);
//          out.flush();
        }

        out.close();
        bufr.close();
    }
}

2.序列流 SequenceInputStream:操作源,

API:
public class SequenceInputStream
extends InputStream

        SequenceInputStream 表示其他輸入流的邏輯串聯。它從輸入流的有序集合開始,並從第一個輸入流開始讀取,直到到達文件末尾,接着從第二個輸入流讀取,依次類推,直到到達包含的最後一個輸入流的文件末尾爲止。 


1.構造方法:
    SequenceInputStream(Enumeration<? extends InputStream> e) 
         通過記住參數來初始化新創建的 SequenceInputStream,該參數必須是生成運行時類型爲 InputStream 對象的 Enumeration 型參數。 

    SequenceInputStream(InputStream s1, InputStream s2) 
         通過記住這兩個參數來初始化新創建的 SequenceInputStream(將按順序讀取這兩個參數,先讀取 s1,然後讀取 s2),以提供從此 SequenceInputStream 讀取的字節。 

2.示例:文件合併

//文件合併:
public class Demo5 {

    public static void main(String[] args) throws IOException {

//      Vector<FileInputStream> vec = new Vector<FileInputStream>();    //效率低

        ArrayList<FileInputStream> vec = new ArrayList<FileInputStream>();
        vec.add(new FileInputStream("demo2.txt"));
        vec.add(new FileInputStream("demo5.txt"));
        vec.add(new FileInputStream("out.txt"));

        final Iterator<FileInputStream> it = vec.iterator();  //內部類訪問局部變量時,加final
/*      
        //迭代器轉換枚舉方法:工具類Collections的enumeration(Collection<T> c)方法:  
                    static <T> Enumeration<T> enumeration(Collection<T> c) 
                                            返回一個指定 collection 上的枚舉。 
        */

        Enumeration<FileInputStream> en = Collections.enumeration(vec);

/*  原理:
            Enumeration<FileInputStream> en = new Enumeration(){   //ArrayList不能返回枚舉,自己造

            @Override
            public boolean hasMoreElements() {

                return it.hasNext();
            }

            @Override
            public Object nextElement() {

                return it.next();
            }

        };*/

//      SequenceInputStream sis = new SequenceInputStream(vec.elements());
        SequenceInputStream sis = new SequenceInputStream(en);


        FileOutputStream fos= new FileOutputStream("new.txt");

        byte ch[] = new byte[1024];

        int len = 0;
        while((len = sis.read(ch))!= -1){
            fos.write(ch,0,len);
        }
        fos.close();
        sis.close();

    }

}

文件切割器 ,合併器:

/*
        文件切割與合併:

 */
public class Demo6 {

    private static final int BUFFER_SIZE = 1048576;

    public static void main(String[] args) throws IOException {

        File file = new File("E:\\電影\\經典\\《聲之形》.mp4");  //切割文件目錄
        File file1 = new File("E:\\電影\\經典\\《聲之形》切割碎片"); //切割碎片目錄
        File file2 = new File("E:\\電影\\經典\\《聲之形》合併後目錄"); //碎片合併目錄
        long date = 10485760;       //碎片大小:10M

//      qieGe(file,file1,date);   //切割

        heBin(file1,file2);  //合併

    }


    public static void qieGe(File file,File file1, long date) throws IOException {
        //計劃顯示:
        int num = (int)(file.length()/date)+1;
        System.out.print("計劃切割碎片數量:" +num+"\n是否繼續切割?(Y/N):");
        //判斷:
        BufferedReader bufr =new BufferedReader(new InputStreamReader(System.in));
        String str = null;
        while((str = bufr.readLine())!=null){
            if("y".equals(str)||"Y".equals(str))
                break;
            else if("n".equals(str)||"N".equals(str))
                System.exit(0);
        }
        //開始時間:
        long startTime = System.currentTimeMillis();

        System.out.println("正在切割...");


        //用讀取流關聯文件
        FileInputStream fis = new FileInputStream(file);

        String[] name = file.getName().split("\\.");
        int i = 1 ;

        //定義一個1M緩衝區
        byte []ch = new byte[BUFFER_SIZE];

        //創建目的
        File f = createFile(name,i++,file1);

        //用寫入流關聯目的
        FileOutputStream fos = new FileOutputStream(f);

        //開始讀寫操作,這裏的操作,是默認碎片大小大於緩衝區。
                        //否者可以簡化,不需要if,else判斷,直接每次都新建目錄。


        int len = 0;
        while((len = fis.read(ch))!= -1){
            if(f.length()<date)    //判斷,碎片大小沒有達到指定大小,就繼續寫入·
                fos.write(ch,0,len);   
            else{                    //達到指定大小,新建碎片文件,本次字節寫入新的碎片文件。循環繼續。

                f = createFile(name,i++,file1);     //新建碎片文件
                fos = new FileOutputStream(f);              
                fos.write(ch);     //這裏也得寫一次,否者合併的視頻有的地方會卡,保證數據的完整性
            }           
        }

        fos.close();
        fis.close();

        //結束時間:
        long endTime = System.currentTimeMillis();
        String timecost = new DecimalFormat("0.00").format((endTime - startTime)/1000.0);

        //切割文件時,必須記錄住被切割文件的名稱,以及切割出來的碎片文件的個數。便於合併。

        //寫入配置文件:       
        FileOutputStream fos1 = new FileOutputStream(new File(file1,"info.properties"));
        Properties prop =new Properties();
        prop.setProperty("timecost", timecost+"s");  //分割耗時

        prop.setProperty("filenum", String.valueOf(i-1)); //碎片數量
        prop.setProperty("filename", file.getName());  //文件名稱
        prop.setProperty("prefix", name[0] + "_cut"); //碎片前綴
        prop.setProperty("suffix", "."+name[1]); //碎片後綴

        prop.store(fos1, "");

//      prop.list(System.out);

        System.out.println("切割完畢! 用時" + prop.getProperty("timecost"));

    }

//創建目的  
    public static File createFile(String[] name,int i,File file1) throws IOException{
        String newName = name[0] + "_cut"+i+"."+name[1];

        if(!file1.exists()){
            file1.mkdirs();
        }
        File f = new File(file1,newName);       //File對象在被使用需要確認目錄是否存在。             
        return f;
    }



//合併    
    public static void heBin(File file1,File file2) throws IOException {
        //加載碎片目錄的配置文件:===================================================
        File[] files = file1.listFiles(new SuffFilter(".properties"));
        if(files.length == 0){
            throw new RuntimeException("碎片目錄缺少配置文件,無法合併!");
        }

        //記錄配置文件對象
        File config = files[0];
        FileInputStream fos1 = new FileInputStream(config);
        Properties prop1 =new Properties();
        prop1.load(fos1);

        int num = Integer.parseInt(prop1.getProperty("filenum"));  //碎片數量
        String suffix = prop1.getProperty("suffix");  //碎片後綴名
        String prefix = prop1.getProperty("prefix");  //碎片前綴名
        String filename = prop1.getProperty("filename");  // 源文件名稱

        //判斷碎片數量是否完整:
            File[] file = file1.listFiles(new SuffFilter(suffix));
            if(file.length<num){
                throw new RuntimeException("碎片文件數量小於配置文件默認數量"+num+",無法合併!");
            }


        //計劃顯示:碎片數量:

            System.out.print("計劃合併碎片數量:" + num+"\n是否繼續合併?(Y/N):");

            //判斷:
                BufferedReader bufr =new BufferedReader(new InputStreamReader(System.in));
                String str = null;
                while((str = bufr.readLine())!=null){
                    if("y".equals(str)||"Y".equals(str))
                        break;
                    else if("n".equals(str)||"N".equals(str))
                        System.exit(0);
                }
        //開始時間:
            long startTime = System.currentTimeMillis();

            System.out.println("正在合併...");


        //將碎片文件和流對象關聯,存儲到集合中。===========================
        ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();  //用來存放合併需要的碎片目錄的讀取流的集合

        for(int x= 1; x<= num;x++){
            al.add(new FileInputStream(new File(file1, prefix+x+suffix)));
        }

        //將多個流合併成一個序列流
        SequenceInputStream seq = new SequenceInputStream(Collections.enumeration(al));

        //定義合併文件的名稱
        String newName = filename+ "_merge" +suffix ;

        //用寫入流關聯合並目的
        if(!file2.exists()){
            file2.mkdirs();
        }       
        FileOutputStream fos = new FileOutputStream(new File(file2,newName));  //參數如果是字符串會自己建,如果是File對象則要確定File目錄存在。

        //用緩衝區進行寫入流的操作,效率更高
        byte[] b = new byte[BUFFER_SIZE];

        //開始讀寫操作
        int len = 0;
        while((len = seq.read(b))!= -1){
            fos.write(b,0,len);
        }

        fos.close();
        seq.close();

        //結束時間:
        long endTime = System.currentTimeMillis();
        String timecost = new DecimalFormat("0.00").format((endTime - startTime)/1000.0);

        //寫入配置文件:
        FileOutputStream fos2 = new FileOutputStream(new File(file2,"info.properties"));
        Properties prop2 =new Properties();
        prop2.setProperty("timecost", timecost+"s");
        prop2.store(fos2, "");
//      prop.list(System.out);

        System.out.println("合併完畢! 用時" + prop2.getProperty("timecost"));
    }

}

//過濾器
class SuffFilter implements  FilenameFilter{
    private String suffix;
    public SuffFilter(String suffix){
        super();
        this.suffix = suffix;
    }
    @Override
    public boolean accept(File dir, String name) {

        return name.endsWith(suffix);
    }

}

3.操作對象的字節流。ObjectInputStream, ObjectOutputStream。

ObjectOutputStream:是對象的序列化。(序列流是流的序列化)

ObjectOutputStream:
將 Java 對象的基本數據類型和圖形寫入 OutputStream。
通過在流中使用文件可以實現對象的持久存儲。
對象的默認序列化機制寫入的內容是:對象的類,類簽名,以及非瞬態和非靜態字段的值。

構造方法:
    ObjectOutputStream(OutputStream out) 
       創建寫入指定 OutputStream 的 ObjectOutputStream。

①被操作的對象需要實現Serializable(標記接口)

示例:
public class Demo7 {

    public static void main(String[] args) throws IOException {

        writeObj();
    }

    public static void writeObj() throws IOException, IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.object"));
        //對象的序列化
        oos.writeObject(new Person1("小強",30));  //被操作的對象需要實現Serializable(標記接口)

        oos.close();
    }

}

public class Person1 implements Serializable/*標記接口*/{

    private String name;
    public Person1(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    private int age;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

ObjectInputStream:是對象的反序列化。

ObjectInputStream 對以前使用 ObjectOutputStream 寫入的基本數據和對象進行反序列化。 

示例:
public class Demo7 {

    public static void main(String[] args) throws IOException, ClassNotFoundException {

        writeObj();
        readObj();
    }

    public static void readObj() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.object"));

        //對象的反序列化
        Person1 p = (Person1)ois.readObject();
        System.out.println(p.getName() + ":" + p.getAge());

        ois.close();
    }

    public static void writeObj() throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.object"));

        //對象的序列化
        oos.writeObject(new Person1("小強",30));  //被操作的對象需要實現Serializable(標記接口)

        oos.close();


    }

}

public class Person1 implements Serializable/*標記接口*/{

    private String name;
    public Person1(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    private int age;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

4.序列化接口 Serializable :

關鍵字 瞬態關鍵字transient : 非靜態數據不想被序列化可以使用這個關鍵字修飾。

作用:給類加id
①序列化運行時使用一個稱爲 serialVersionUID 的版本號與每個可序列化類相關聯,該序列號在反序列化過程中用於驗證序列化對象的發送者和接收者是否爲該對象加載了與序列化兼容的類。如果接收者加載的該對象的類的 serialVersionUID 與對應的發送者的類的版本號不同,則反序列化將會導致 InvalidClassException。
②如果可序列化類未顯式聲明 serialVersionUID,則序列化運行時將基於該類的各個方面計算該類的默認 serialVersionUID 值,如“Java(TM) 對象序列化規範”中所述。不過,強烈建議 所有可序列化類都顯式聲明 serialVersionUID 值,原因是計算默認的 serialVersionUID 對類的詳細信息具有較高的敏感性,根據編譯器實現的不同可能千差萬別,這樣在反序列化過程中可能會導致意外的 InvalidClassException。
例:
 ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

①如果序列化的類改變了,id號會變。反序列化會發生異常。
②所以安全起見,自己顯示聲明id號。這樣,如果序列化的類改變了,id號不會變。


public class Person1 implements Serializable/*標記接口*/{

    /**
     * 
     */
    private static final long serialVersionUID = 9527L;

    private transient String name;  //如果想要非靜態的值不被序列化對象存儲,加瞬態關鍵字transient
    private static int age;  //靜態的值序列化不會存儲
    public Person1(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }


    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

5.RandomAccessFile : 隨機訪問文件對象(可用於多線程寫入)

/*
        RandomAccessFile (多線程寫入)
        不是io體系中的子類。

        構造方法:
        RandomAccessFile(File file, String mode) 
                創建從中讀取和向其中寫入(可選)的隨機訪問文件流,該文件由 File 參數指定。 
        RandomAccessFile(String name, String mode) 
                創建從中讀取和向其中寫入(可選)的隨機訪問文件流,該文件具有指定名稱。 

        特點:
        1.該對象既能讀,也能寫。
        2.該對象內部維護了一個byte數組,並通過指針可以操作數組中的元。
        3.可以通過getFilePointer方法獲取指針的位置,和通過seek方法設置指針的位置。
        4.其實該對象就是將字節輸入流和輸出流進行了封裝。
        5.該對象的源和目的只能是文件。

        使用環境:
        1.讀取有規律的文本
        2.多個線程同時寫一個文本的不同位置。(其他流都是從頭開始寫,所以做不到)
 */
public class Demo8 {

    public static void main(String[] args) throws IOException {
        writeFile();  
        readFile();
    }
    public static void readFile() throws IOException{
        RandomAccessFile raf = new RandomAccessFile("ranacc.txt", "r");
        byte[] buf =new byte[4];
        raf.read(buf);

        String name = new String(buf);
        System.out.println("name = " + name);

        int age = raf.readInt();
        System.out.println("age = " + age);

        System.out.println(raf.getFilePointer());
        raf.seek(0);  //設置指針位置。向從哪讀就哪讀,     隨機讀。

        raf.close();

    }

    //使用RandomAccessFile對象寫入一些人員信息,比如姓名和年齡。
    public static void writeFile() throws IOException{
        /*
                如果文件不存在,則創建,如果文件存在,不創建,但是寫數據時會從0開始寫,修改原有數據。
         */
        RandomAccessFile raf = new RandomAccessFile("ranacc.txt", "rw");

        raf.write("張三".getBytes());
//      raf.write(97);  //只保留低8位
        raf.writeInt(97);  //4個字節全存

        raf.seek(2);    //設置指針位置。     隨機寫。
        raf.write("王五".getBytes());

        raf.close();
    }
}

6.管道流:PipedInputStream,PipedOutputStream

輸入輸出可以直接進行連接,通過結合線程使用。

PipedInputStream:
    管道輸入流應該連接到管道輸出流;管道輸入流提供要寫入管道輸出流的所有數據字節。通常,數據由某個線程從 PipedInputStream 對象讀取,並由其他線程將其寫入到相應的 PipedOutputStream。不建議對這兩個對象嘗試使用單個線程,因爲這樣可能死鎖線程。



public class PipedStream {

    public static void main(String[] args) throws IOException {

        PipedInputStream input = new PipedInputStream();
        PipedOutputStream output = new PipedOutputStream();

        input.connect(output);

        new Thread(new Input(input)).start();
        new Thread(new Output(output)).start();
    }

}


class Input implements Runnable{

    private PipedInputStream in;
    Input(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="+ s);
            in.close();
        } catch (Exception e) {
            // TODO: handle exception
        }
    }
}

class Output implements Runnable{
    private PipedOutputStream out;
    Output(PipedOutputStream out){
        this.out = out;
    }

    public void run(){
        try {
            Thread.sleep(5000);
            out.write("hi, 管道來了!".getBytes());
        } catch (Exception e) {
            // TODO: handle exception
        }
    }

}

7.操作基本類型數據的流對象 DataInputStream,DataOutputStream

/*
        DataOutputStream:
        數據輸出流允許應用程序以適當方式將基本 Java 數據類型寫入輸出流中。
        然後,應用程序可以使用數據輸入流DataInputStream將數據讀入。 


 */
public class DataStreamDemo {

    public static void main(String[] args) throws IOException {
//      writeData();
        readData();
    }

    public static void readData() throws IOException {
        DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
        String s = dis.readUTF();
        System.out.println(s);
    }

    public static void writeData() throws IOException {
         DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
         dos.writeUTF("你好");        //UTF修改版:帶編碼的標頭。轉換流指定不了它的編碼。
                                                        //只能用對應的數據輸入流DataInputStream讀了。
         dos.close();
    }

}

8.操作數組的流:

1.操作字節數組:ByteArrayInputStream,ByteArratOutputStream,只是操作內存。

ByteArratOutputStream:
    此類實現了一個輸出流,其中的數據被寫入一個 byte 數組。緩衝區會隨着數據的不斷寫入而自動增長。可使用 toByteArray() 和 toString() 獲取數據。 
    關閉 ByteArrayOutputStream 無效。此類中的方法在關閉此流後仍可被調用,而不會產生任何 IOException。 

ByteArrayInputStream :
    ByteArrayInputStream 包含一個內部緩衝區,該緩衝區包含從流中讀取的字節。內部計數器跟蹤 read 方法要提供的下一個字節。 
    關閉 ByteArrayInputStream 無效。此類中的方法在關閉此流後仍可被調用,而不會產生任何 IOException。 

構造函數:源和目的都是數組
    ByteArrayOutputStream() 
          創建一個新的 byte 數組輸出流。 
    ByteArrayOutputStream(int size) 
          創建一個新的 byte 數組輸出流,它具有指定大小的緩衝區容量(以字節爲單位)。 


    ByteArrayInputStream(byte[] buf) 
          創建一個 ByteArrayInputStream,使用 buf 作爲其緩衝區數組。 
    ByteArrayInputStream(byte[] buf, int offset, int length) 
          創建 ByteArrayInputStream,使用 buf 作爲其緩衝區數組。 

示例:

public class ByteArrarStreamDemo {

    public static void main(String[] args) {
//如果源或目的設備的內存的話可以用這兩個對象。一般操作的數據都不大
        ByteArrayInputStream bis = new ByteArrayInputStream("abceds".getBytes());
        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        int ch = 0;

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

        System.out.println(bos.toString());
    }

}

2.操作字符數組:CharArrayReader,CharArrayWriter

3.操作字符串:StingReader,StringWriter

9.編碼表:

ASCII:美國標準信息交換碼。
    用一個字節的7位可以表示。
ISO8859-1:拉丁碼錶。歐洲碼錶。
    用一個字節的8位表示。(兼容ASCII碼錶)
GB2312:中國的中文編碼表。(六七千)
GBK:中國的中文編碼表升級,融合了更多的中文文字符號。(兩萬多)
Unicode:國際標準碼,融合了多種文字。
    所有的文字都是用2個字節來表示,Java語言用的就是unicode
UTF-8:最多用三個字節來表示一個字符。

簡單編碼解碼:

字符串-->字節數組:編碼。
字節數組-->字符串:解碼。

你好:GBK: -60 -29 -70 -61
你好:utf-8:-28 -67 -96 -27 -91 -67


String str ="你好";
//編碼:
byte[] buf = str.getBytes("GBK");
//解碼:
String str =new String(buf,"GBK");  //你好

編碼解碼問題:

①編碼:如果你編錯了,解不出來。(編錯了,用的是未知字符代替的,如?)
     如果編對了,解錯了,有可能有救。(如果解錯了,就用錯誤的碼錶再返回去編碼(如過這個錯誤的碼不是未知字符的話,ISO8859-1碼錶就沒問題,因爲它不支持中文但是是單字節編碼,會有對應的字節。而UTF-8 1到3個字節的都識別,所以可能出現未知字符?), 得到原字節,在換正確的碼錶重新解碼)

這裏寫圖片描述

②但是如果是用UTF-8解碼解錯的,那麼返回去編碼的時候,得到的可能就不是原來的原字節了。
    因爲UTF-8裏可能沒有對應的字符,用的用未知字符?(-17 -65 -67)代替。

這裏寫圖片描述

UTF-8編碼解碼規範:編碼頭用來識別如何開始查表,0表示,一個字節開始查表,110 ,10兩個字節開始查表,1110,10,10三個字節開始查表。其實的未知字符? 表示 。

UTF-8編碼解碼

③ ” 聯通 ” 問題

String str = "聯通";

byte[] buf = str.getByte("gbk");

for(byte b : buf){
    System.out.println(Integer.toBinaryString(b&255));
}
/*
    11000001
    10101010
    11001101
    10101000
*/

聯通的gbk編碼正好和UTF-8的編碼格式一致。

編碼解碼練習:按字節截取字符串

/*
 * 在java中,字符串“abcd”與字符串“ab你好”的長度是一樣的,都是4個字符。
 * 但對應的字節數不同,一個字節佔兩個字節。
 * 
 * 定義一個方法,按照指定的字節數來取子串,保證取得的子串是完整的。
 * 
 * 如:對於“ab你好”,如果取三個字節,那麼子串就是ab與“你”的半個,那麼半個就要捨棄。
 * 如果去取4個字節就是“ab你”,取5個字節還是“ab你”
 */

public class Demo9 {

    public static void main(String[] args) throws UnsupportedEncodingException {
        System.out.println(cutStringByte("ab你好",4));
    }

    public static String cutStringByte(String str,int len) throws UnsupportedEncodingException{
            byte[] buf =  str.getBytes("gbk");
            int count = 0;     //記錄到截取未知的漢字字節數

            for(int x= len - 1;x>=0;x--){   //判斷截取的最後一個字節是不是負數(漢字一個字節)
                if(buf[x] <0)
                    count++;       //記錄負數(漢字一個字節)
                else
                    break;
            }

            if(count%2==0)   //如果到截取未知的漢字字節數是偶數,則不捨棄
                return new String(buf,0,len,"gbk");
            else//如果到截取未知的漢字字節數是奇數,則捨棄
                return new String(buf,0,len-1,"gbk");
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章