1. 程式人生 > >Java——I/O(字元編碼、記憶體流、列印流、System、輸入流、序列化)

Java——I/O(字元編碼、記憶體流、列印流、System、輸入流、序列化)

1.常見的編碼

 GBK、GB2312:國標編碼,GBK包含簡體中文和繁體中文,而GB2312只包含簡體中文。

UNICODE編碼:java提供的16進位制編碼,可以描述世界上任意的文字資訊。由於使用16進位制編碼,編碼體積太大,造成網路傳輸的負擔。

ISO8859-1:國際通用編碼,不支援中文。(瀏覽器預設編碼)

UTF編碼:相當於結合了UNICODE、ISO8859-1,支援所有語言且體積較小,常用的就是UTF-8編碼形式。 

2.亂碼產生原因

95%的亂碼產生於編解碼不一致

讀取java執行屬性

import java.io.*;
import java.nio.file.Paths;

/**
 * 亂碼的產生
 * Author: qqy
 */
public class Test {
    public static void main(String[] args) {
        File file=Paths.get("E:","JAVA","Test.txt").toFile();
        try( FileOutputStream out=new FileOutputStream(file);
        ) {
            //統一採用UTF-8
           out.write("你好hello".getBytes("ISO-8859-1"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.記憶體流

除了檔案之外,IO的操作也可以發生在記憶體之中。發生在記憶體中的操作流稱為記憶體操作流。

  • 3.1 分類:

位元組記憶體流:ByteArrayInputStream、ByteArrayOutputStream(輸出無引數)

字元記憶體流:CharArrayReader、CharArrayWriter

  • 3.2應用

  • 記憶體流實現字母轉換
import java.io.*;

/**
 * 記憶體流應用
 * Author: qqy
 */
public class Test {
    public static void main(String[] args) {
        //方法一:
        String msg="hello";
        //取得記憶體流
        ByteArrayInputStream inputStream=new ByteArrayInputStream(msg.getBytes());
        ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
        int len=-1;
        while((len=inputStream.read())!=-1){
            outputStream.write(Character.toUpperCase(len));
        }
        //直接將記憶體輸出流輸出
        System.out.println(outputStream);
        //方法二:
        try(InputStream in = new ByteArrayInputStream(msg.getBytes());
            OutputStream out = new ByteArrayOutputStream()//預設構造方法快取區32個位元組(32)
        ){
            byte[] buff=new byte[3];
            int count=0;
            while((len=in.read(buff))!=-1) {
                for (int i = 0; i < len; i++) {
                    byte b=buff[i];
                    if(b>='a'&&b<='z') {
                        count++;
                        buff[i] = (byte) (buff[i] - 32);
                    }
                }
                out.write(buff,0,len);
            }
            //OutputStream中沒有toByteArray,當前情況下要使用必須向下轉型
            byte[] newData= ((ByteArrayOutputStream) out).toByteArray();
            System.out.println(new String(newData));
            System.out.println("轉換次數:"+count);
        }catch (IOException e){

        }
    }
}
  • 檔案合併(考慮到記憶體,檔案不宜過大)
import java.io.*;
import java.nio.file.Paths;

/**
 * 記憶體流合併兩個檔案(程式碼量重複過多)
 * Author: qqy
 */
public class Test1 {
    public static void main(String[] args) {
        File file1=Paths.get("E:","JAVA","Test.txt").toFile();
        File file2=Paths.get("E:","JAVA","Test1.txt").toFile();
        //目標檔案
        File file3=Paths.get("E:","JAVA","destTest.txt").toFile();

        try(FileInputStream in1=new FileInputStream(file1);
            FileInputStream in2=new FileInputStream(file2);
            ByteArrayOutputStream memoryOut=new ByteArrayOutputStream();
            FileOutputStream out=new FileOutputStream(file3);
        ){
            //讀檔案並寫入記憶體流
            byte[] buff=new byte[5];
            int len=-1;
            while((len=in1.read(buff))!=-1){
                memoryOut.write(buff,0,len);
            }
            while((len=in2.read(buff))!=-1){
                memoryOut.write(buff,0,len);
            }
            //寫入檔案
            out.write(memoryOut.toByteArray());
        }catch (IOException e){

        }
    }
}

優化後:

import java.io.*;
import java.nio.file.Paths;

/**
 * 記憶體流合併兩個檔案(程式碼量重複過多)
 * Author: qqy
 */
public class Test1 {
    public static void main(String[] args) {
        File file1=Paths.get("E:","JAVA","Test.txt").toFile();
        mergeFileBetter(new String[]{
                "E:"+File.separator+"JAVA"+File.separator+"Test.txt",
                "E:"+File.separator+"JAVA"+File.separator+"Test1.txt"
        },"E:"+File.separator+"JAVA"+File.separator+"destTest.txt");
    }

    public static void mergeFileBetter(String[] mergePaths,String outPaths){
        //引數校驗
        File[] files=new File[mergePaths.length];
        //初始化所有輸入檔案
        for(int i=0;i<mergePaths.length;i++){
            files[i]=new File(mergePaths[i]);
        }
        //輸出流
        try(ByteArrayOutputStream memoryOut=new ByteArrayOutputStream();
            FileOutputStream out=new FileOutputStream(outPaths)
        ) {
            //遍歷合併的檔案,輸入到記憶體流
            for (File f : files) {
                try (FileInputStream in = new FileInputStream(f)) {
                    //讀檔案並寫入記憶體流
                    byte[] buff=new byte[3];
                    int len=-1;
                    while((len=in.read(buff))!=-1){
                        memoryOut.write(buff,0,len);
                    }
                } catch (IOException e) {

                }
            }
            //記憶體流輸出到檔案
            out.write(memoryOut.toByteArray());
        }catch (IOException e){

        }
    }
}

4.列印流

輸出流的進化版

  •  4.1 自定義列印流:

import java.io.*;
import java.nio.file.Paths;

/**
 * 設計列印流
 * Author: qqy
 */
public class DesignPrintStream {
    public static void main(String[] args) {
        //DesignPrintStream print = new DesignPrintStream(System.out);
        DesignPrintStream print =null;
        try {
            print = new DesignPrintStream(
                    new FileOutputStream(Paths.get("E:","JAVA","Test.txt").toFile())
            );
            print.println("Hello");
            print.println(10);
            print.println(10.0d);
            print.println(true);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    private OutputStream outputStream;

    public DesignPrintStream(OutputStream outputStream){
        this.outputStream=outputStream;
    }

    public void print(String str){
        try {
            //核心在於OutputStream提供的write
            outputStream.write(str.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void println(String str){
        print(str+"\r\n");
    }

    public void println(int value){
        println(String.valueOf(value));
    }

    public void println(double value){
        println(String.valueOf(value));
    }

    public void println(boolean value){
        println(String.valueOf(value));
    }
}
  • 4.2 系統提供的列印流

位元組列印流:PrintStream         字元列印流:PrintWriter

  • 列印流應用的是裝飾設計模式(基於抽象類):核心依然是某個類(OutputSream)的功能(write()),但是為了得到更好的操作效果,讓其支援的功能更多。
  • 優點:擴充套件功能方便,需要不同的功能時只需要更換裝飾類即可。           缺點:類結構複雜

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.nio.file.Paths;

/**
 * Author: qqy
 */
public class Test {
    public static void main(String[] args) {
        PrintStream print =null;
        try {
            print = new PrintStream(
                    new FileOutputStream(Paths.get("E:","JAVA","Test.txt").toFile())
            );
            print.println("Hello");
            print.println(10);
            print.println(10.0d);
            print.println(true);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}
  • 4.3 格式化輸出

public PrintStream printf(String format, Object ... args)
  • 簡答:

System是類(java.lang.System)

out / err 是物件(PrintStream的屬性,java.io.PrintStream)/ in是物件(java.io.InputStream)

println是PrintStream類的成員方法,out物件的方法 / read是InputStream類的成員方法,in物件的方法

/**
 * Author: qqy
 */
public class TestFormat {
    public static void main(String[] args) {
        System.out.printf("%s","bonjour");
        System.out.printf("姓名:%s,年齡:%d,身高:%.2f","李四",18,180.6);
        System.out.println();
        //正:右對齊,負:左對齊
        System.out.printf("姓名:%4s\n年齡:%-4d\n身高:%8.2f\n","張三",25,168.8);
        //java.util.Formatter
        //String.format,System.out.printf用法一直
        String str=String.format("姓名:%s\n年齡:%d\n身高:%.2f\n","張三",25,168.8);
        //System是類,out是物件,
        System.out.println(str);
    }
}

5.System對I/O的支援

  • 輸出:均是列印流PrintStream的物件

標準輸出——顯示器   System.out

錯誤輸出——System.err

  • 輸入:輸入流InputStream的物件

標準輸入——鍵盤       System.in

標準輸出:

public class Test {
    public static void main(String[] args) {
        try{
            //讓OutputStream的輸出位置變為螢幕
            OutputStream out=System.out;
            out.write("hello\n".getBytes());
            System.err.println("bonjour");
            Integer.parseInt("123aaa");
        }catch (NumberFormatException e){
            System.out.println(e.getMessage());
            System.err.println(e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

標準輸入:

有限制:

import java.io.IOException;
import java.io.InputStream;

/**
 * Author: qqy
 */
public class Test1 {
    public static void main(String[] args) throws IOException {
        //從鍵盤輸入
        InputStream in=System.in;
        //陣列長度固定,可能無法讀取全部內容
        byte[] data=new byte[6];
        System.out.println("請輸入內容:");
        int len=in.read(data);
        System.out.println(new String(data,0,len));
    }
}

優化:

import java.io.*;

/**
 * Author: qqy
 */
public class Test3 {
    public static void main(String[] args) {
        InputStream in=System.in;
        byte[] buff=new byte[5];
        System.out.println("請輸入內容:");
        int len=-1;
        //利用記憶體流消除限制
        try(ByteArrayOutputStream out=new ByteArrayOutputStream()){
            while((len=in.read(buff))!=-1){
                out.write(buff,0,len);
                //當讀取的長度陣列的長度,則讀取完成,跳出迴圈
                if(len<buff.length){
                    break;
                }
            }
            System.out.println(new String(out.toByteArray()));
        }catch (IOException e){
            System.out.println(e.getMessage());
        }
    }
}

6.兩種輸入流

原生InputStream的進化版

  • 6.1 BufferedReader(瞭解)

——readLine() 讀取一行輸入

import java.io.*;

/**
 * 利用BufferedReader實現鍵盤輸入
 * Author: qqy
 */
public class Test4 {
    public static void main(String[] args) {
        //BufferedReader->InputStreamReader->InputStream
        try( InputStream inputStream=System.in;
             InputStreamReader inputStreamReader=new InputStreamReader(inputStream);
             BufferedReader reader=new BufferedReader(inputStreamReader)
        ){
            String line;
            System.out.println("請輸入內容:");
            while(!(line=reader.readLine()).equals("q")){
                System.out.println(line);
            }
        }catch(IOException e){
            System.out.println(e.getMessage());
        }
    }
}
  • 6.2 Scanner(java.util.Scanner)

  1. 判斷是否有指定型別資料輸入: public boolean hasNextXXX()
  2. 取得指定型別的資料: public 資料型別 nextXXX()
  3. 自定義分隔符:public Scanner useDelimiter(Pattern pattern)
  4. 構造方法:public Scanner(InputStream source) 
  • 輸出使用列印流,輸入使用Scanner類

Scanner讀取資料:

import java.util.Scanner;

/**
 * Scanner讀取資料
 * Author: qqy
 */
public class Test2 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.println("請輸入內容:");
        //判斷是否有內容
        if (scanner.hasNext()) {
            //輸出輸入的內容(空格被忽略)
            System.out.println(scanner.next());
        }
        while (true) {
            System.out.println("請輸入年齡:");
            if (scanner.hasNextInt()) {
                System.out.println("年齡是:" + scanner.nextInt());
                break;
            } else {
                System.out.println("您輸入的格式有誤,請重新輸入:");
                scanner.next();//丟棄不符合要求的資料
            }
        }
        System.out.println("請輸入出生日期:");
        //正則表示式 d——整數  數字——長度
        if (scanner.hasNext("\\d{4}-\\d{2}-\\d{2}")) {
            System.out.println(scanner.next());
        }
    }
}

Scanner讀取檔案:

import java.io.IOException;
import java.nio.file.Paths;
import java.util.Scanner;

/**
 * Scanner讀取檔案
 * Author: qqy
 */
public class Test5 {
    public static void main(String[] args) {
        try (Scanner scanner=new Scanner(Paths.get("E:","JAVA","Test.txt"))){
           while(scanner.hasNext()){
               //宣告檔案的分隔符是\n
               scanner.useDelimiter("\n");
               System.out.println(scanner.next());
           }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

System 拓展:

1.系統屬性

import java.util.Properties;

/**
 * 系統屬性
 * Author: qqy
 */
public class Test6 {
    public static void main(String[] args) {
        //系統屬性
        Properties properties=System.getProperties();

        //常用系統屬性
        //user.home / user.dir / java.home / path.separator / file.separator

        //獲取使用者目錄
        String userHome=(String)properties.get("user.home");
        System.out.println(userHome);
    }
}

2.系統環境變數

import java.io.File;
import java.nio.file.Paths;
import java.util.Map;

/**
 * 系統環境變數
 * Author: qqy
 */
public class Test7 {
    public static void main(String[] args) {
        //返回Map介面的例項化物件
        Map<String, String> env = System.getenv();
        for (Map.Entry<String, String> entry : env.entrySet()) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }

        //JAVA_HOME    配置的環境變數
        //SystemDrive  獲取驅動盤
        //Path
        //ProgramData  存放資料
        //ProgramW6432 獲取安裝目錄
        //TEMP         臨時目錄
        //NUMBER_OF_PROCESSORS  處理器的核數
        System.out.println(System.getenv("JAVA_HOME"));
        
        //將檔案寫入臨時目錄
        File tempFile = Paths.get(
                System.getenv("TEMP"),
                "abc.txt")
                .toFile();

        System.out.println(tempFile.getAbsolutePath());
    }
}

7.序列化

將記憶體中的物件變為二進位制資料流的形式進行傳輸或保存於文字中。

  • 7.1 初識

        1.Java提供的一種序列化(JDK提供) 遠端方法呼叫(RPC)

序列化:將記憶體轉換為位元組陣列,可在網路中傳輸,儲存檔案

               Object(In Memory) -> byte[]

反序列化:

               byte[] -> Object(In Memory)

        2. Object  -> JSON (前端開發 JavaScript) Person(name,age) -> {"name":"Jack", "age": 22}

               JSON    -> Object

        3. Object -> XML (XML檔案——可擴充套件的標誌語言)(JDK提供)

               XML    -> Object

       4.Java序列化:

序列化物件的屬性資訊

反序列化不會執行構造方法和構造塊

  • 7.2 實現

Java中的類如果要被序列化輸出,該類必須實現Serializable介面,該介面是一個標識介面,表示該類具有序列化的功能。

  • 7.3 序列化與反序列化操作:

要想實現序列化與反序列化的物件操作,需要使用java.io包中提供的兩個類:ObjectOutputStream、 ObjectInputStream

物件序列化輸出—ObjectOutputStream          物件反序列化輸入—ObjectInputStream

  • 將物件序列化輸出方法:
 public final void writeObject(Object obj) throws IOException
  • 將物件反序列化輸入方法:
  public final Object readObject() throws IOException, ClassNotFoundException
  • 使用Serializable序列化輸出時,預設將物件的所有屬性以及值均序列化以及反序列化。若希望某些屬性值不進行序列化輸出,可以在屬性前加transient關鍵字

程式碼示例:

import java.io.*;
import java.nio.file.Paths;

/**
 * Author: qqy
 */
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        File file=Paths.get("E:","JAVA","Test.txt").toFile();
        //取得相應輸出流
        Person1 per=new Person1("張三",18);
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(file));
        //資料序列化輸出
        oos.writeObject(per);
        oos.close();

        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file));
        //資料反序列化輸入
        Object res=ois.readObject();
        System.out.println(res);  //Person{name=張三 age=0"}
        ois.close();
    }
}

//Serializable  標識介面
class Person1 implements Serializable{
    private String name;
    private transient int age;

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

    @Override
    public String toString() {
        return "Person{" +
                "name=" + name +
                " age=" + age +
                "\"}";
    }
}