內存操作流&打印流&輸入流(Scanner)

內存操作流

定義:除了文件之外,IO操作也可以發生在內存中,發生在內存中的操作流稱爲內存流。

文件流的操作裏面一定會產生一個文件數據(不管後這個文件數據是否被保留)。
如果現在需求是:需要進行IO處理,但是又不希望產生文件,這種情況下就可以使用內存作爲操作終端。

內存流也分爲兩類:

  1. 字節內存流:ByteArrayInputStreamByteArrayOutputStream
  2. 字符內存流:CharArrayReaderCharArrayWriter

首先來觀察ByteArrayInputStream和ByteArrayOutputStream的構造方法:

public
class ByteArrayInputStream extends InputStream {

    /**
     * Creates a <code>ByteArrayInputStream</code>
     * so that it  uses <code>buf</code> as its
     * buffer array.
     * The buffer array is not copied.
     * The initial value of <code>pos</code>
     * is <code>0</code> and the initial value
     * of  <code>count</code> is the length of
     * <code>buf</code>.
     *
     * @param   buf   the input buffer.
     */
    public ByteArrayInputStream(byte buf[]) {
        this.buf = buf;
        this.pos = 0;
        this.count = buf.length;
    }

public class ByteArrayOutputStream extends OutputStream {

    /**
     * Creates a new byte array output stream. The buffer capacity is
     * initially 32 bytes, though its size increases if necessary.
     */
    public ByteArrayOutputStream() {
        this(32);
    }

在這裏插入圖片描述

範例:通過內存流實現大小寫轉換

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

public class TestFileStream{
    public static void main(String[] args) throws Exception{
        String msg = "hello world";
        ByteArrayInputStream inputStream = new ByteArrayInputStream(msg.getBytes());
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        int len = 0;
        while ((len = inputStream.read()) != -1){
            outputStream.write(Character.toUpperCase(len));
        }
        System.out.println(outputStream);
        inputStream.close();
        outputStream.close();
    }
}

打印流

打印流解決的就是OutputStream的設計缺陷,屬於OutputStream功能的加強版。
如果操作的不是二進制數據, 只是想通過程序向終端目標輸出信息的話,OutputStream不是很方便,其缺點有兩個:

  1. 所有的數據必須轉換爲字節數組。
  2. 如果要輸出的是int、double等類型就不方便了。

打印流概念

打印流設計的主要目的是爲了解決OutputStream的設計問題,其本質不會脫離OutputStream。

範例:設計一個簡單打印流

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

class PrintUtil{
    private OutputStream outputStream;

    public PrintUtil(OutputStream outputStream) {
        this.outputStream = outputStream;
    }
    public void print(String str){
        try {
            outputStream.write(str.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void println(String str){
        this.print(str+"\r\n");
    }
    public void print(int num){
        this.print(num);
    }
    public void println(int num){
        this.print(num+"\r\n");
    }
    public void print(double data){
        this.print(data);
    }
    public void println(double data){
        this.print(data+"\r\n");
    }
}
public class TestFileStream {
    public static void main(String[] args)throws Exception {
        PrintUtil printUtil = new PrintUtil(new FileOutputStream
                (new File("C:" + File.separator + "Users" + File.separator
                        + "DELL" + File.separator + "Desktop" + File.separator + "Test.txt")));
        printUtil.print("姓名:");
        printUtil.println("劉德華") ;
        printUtil.print("年齡:") ;
        printUtil.println(50) ;
        printUtil.print("工資:") ;
        printUtil.println(1000.123456789);
    }
}

經過簡單處理之後,讓OutputStream的功能變的更加強大了,其實本質就只是對OutputStream的功能做了一個 封裝而已。java提供有專門的打印流處理類:PrintStreamPrintWriter

使用系統提供的打印流

打印流分爲字節打印流:PrintStream、字符打印流:PrintWriter
首先來觀察這兩 個類的繼承結構與構造方法:

在這裏插入圖片描述
此時看上圖繼承關係我們會發現,有點像之前講過的代理設計模式,但是代理設計模式有如下特點:

  1. 代理是以接口爲使用原則的設計模式。
  2. 最終用戶可以調用的方法一定是接口定義的方法。

打印流優缺點:

  1. 優點:擴展功能特別方便,需要不同的功能時只需要更換裝飾類即可。
  2. 缺點:類結構複雜。

打印流應用的是裝飾設計模式(基於抽象類的設計模式):核心依然是某個類(OutputStream)的功能(writer()),但是爲了得到更好的操作效果,讓其支持的功能更多。

範例:使用打印流

public class TestFileStream {
    public static void main(String[] args)throws Exception {
        PrintStream printUtil = new PrintStream(new FileOutputStream
                (new File("C:" + File.separator + "Users" + File.separator
                        + "DELL" + File.separator + "Desktop" + File.separator + "Test.txt")));
        printUtil.print("姓名:");
        printUtil.println("劉德華") ;
        printUtil.print("年齡:") ;
        printUtil.println(50) ;
        printUtil.print("工資:") ;
        printUtil.println(1000.123456789);
    }
}

格式化輸出

C語言有一個printf()函數,這個函數在輸出的時候可以使用一些佔位符,例如:字符串(%s)、數字(%d)、小數 (%m.nf)、字符(%c)等。從JDK1.5開始,PrintStream類中也追加了此種操作。

  • 格式化輸出:
    /**
     * A convenience method to write a formatted string to this output stream
     * using the specified format string and arguments.
     *
     * <p> An invocation of this method of the form <tt>out.printf(format,
     * args)</tt> behaves in exactly the same way as the invocation
     *
     * <pre>
     *     out.format(format, args) </pre>
     *
     * @param  format
     *         A format string as described in <a
     *         href="../util/Formatter.html#syntax">Format string syntax</a>
     *
     * @param  args
     *         Arguments referenced by the format specifiers in the format
     *         string.  If there are more arguments than format specifiers, the
     *         extra arguments are ignored.  The number of arguments is
     *         variable and may be zero.  The maximum number of arguments is
     *         limited by the maximum dimension of a Java array as defined by
     *         <cite>The Java&trade; Virtual Machine Specification</cite>.
     *         The behaviour on a
     *         <tt>null</tt> argument depends on the <a
     *         href="../util/Formatter.html#syntax">conversion</a>.
     *
     * @throws  java.util.IllegalFormatException
     *          If a format string contains an illegal syntax, a format
     *          specifier that is incompatible with the given arguments,
     *          insufficient arguments given the format string, or other
     *          illegal conditions.  For specification of all possible
     *          formatting errors, see the <a
     *          href="../util/Formatter.html#detail">Details</a> section of the
     *          formatter class specification.
     *
     * @throws  NullPointerException
     *          If the <tt>format</tt> is <tt>null</tt>
     *
     * @return  This output stream
     *
     * @since  1.5
     */
    public PrintStream printf(String format, Object ... args) {
        return format(format, args);
    }
  • 格式化字符串:
    /**
     * Writes a formatted string to this output stream using the specified
     * format string and arguments.
     *
     * <p> The locale always used is the one returned by {@link
     * java.util.Locale#getDefault() Locale.getDefault()}, regardless of any
     * previous invocations of other formatting methods on this object.
     *
     * @param  format
     *         A format string as described in <a
     *         href="../util/Formatter.html#syntax">Format string syntax</a>
     *
     * @param  args
     *         Arguments referenced by the format specifiers in the format
     *         string.  If there are more arguments than format specifiers, the
     *         extra arguments are ignored.  The number of arguments is
     *         variable and may be zero.  The maximum number of arguments is
     *         limited by the maximum dimension of a Java array as defined by
     *         <cite>The Java&trade; Virtual Machine Specification</cite>.
     *         The behaviour on a
     *         <tt>null</tt> argument depends on the <a
     *         href="../util/Formatter.html#syntax">conversion</a>.
     *
     * @throws  java.util.IllegalFormatException
     *          If a format string contains an illegal syntax, a format
     *          specifier that is incompatible with the given arguments,
     *          insufficient arguments given the format string, or other
     *          illegal conditions.  For specification of all possible
     *          formatting errors, see the <a
     *          href="../util/Formatter.html#detail">Details</a> section of the
     *          formatter class specification.
     *
     * @throws  NullPointerException
     *          If the <tt>format</tt> is <tt>null</tt>
     *
     * @return  This output stream
     *
     * @since  1.5
     */
    public PrintStream format(String format, Object ... args) {
        try {
            synchronized (this) {
                ensureOpen();
                if ((formatter == null)
                    || (formatter.locale() != Locale.getDefault()))
                    formatter = new Formatter((Appendable) this);
                formatter.format(Locale.getDefault(), format, args);
            }
        } catch (InterruptedIOException x) {
            Thread.currentThread().interrupt();
        } catch (IOException x) {
            trouble = true;
        }
        return this;
    }

System類對IO的支持

  • 輸出(均是打印流PrintStream的對象)
  1. 標準輸出(顯示器):System.out
  2. 錯誤輸出:System.err
  • 輸入(輸入流InputStream的對象)

    標準輸入(鍵盤):System.in

系統輸出

系統輸出一共有兩個常量:out、err,並且這兩個常量表示的都是PrintStream類的對象。

  1. out輸出的是希望用戶能看到的內容
  2. err輸出的是不希望用戶看到的內容

這兩種輸出在實際的開發之中都沒用了,取而代之的是"日誌。

System.err只是作爲一個保留的屬性而存在,現在幾乎用不到。唯一可能用到的就是System.out
由於System.out是PrintStream的實例化對象,而PrintStream又是OutputStream的子類,所以可以直接使用 System.out直接爲OutputStream實例化,這個時候的OutputStream輸出的位置將變爲屏幕

範例:使用System.out爲OutputStream實例化

import java.io.OutputStream;

public class TestFileStream {
    public static void main(String[] args)throws Exception {
        OutputStream outputStream = System.out;
        outputStream.write("你好,中國".getBytes());
        outputStream.close();
    }
}

系統輸入

System.in對應的類型是InputStream,而這種輸入流指的是由用戶通過鍵盤進行輸入(用戶輸入)。java本身並沒有直接的用戶輸入處理,如果要想實現這種操作,必須使用java.io的模式來完成

範例:利用InputStream實現數據輸入

import java.io.InputStream;

public class TestFileStream {
    public static void main(String[] args)throws Exception {
        InputStream inputStream = System.in;
        byte[] data = new byte[1024];
        System.out.println("請輸入內容:");
        int temp = inputStream.read(data);
        System.out.println("輸入內容爲:" + new String(data,0,temp));
        inputStream.close();
    }
}

現在發現當用戶輸入數據的時候程序需要暫停執行,也就是程序進入了阻塞狀態。直到用戶輸入完成(按下回車), 程序才能繼續向下執行。

以上的程序本身有一個致命的問題,核心點在於:開闢的字節數組長度固定,如果現在輸入的長度超過了字節數組長度,那麼只能夠接收部分數據。這個時候是由於一次讀取不完所造成的問題,所以此時好的做法是引入內存操作流來進行控制,這些數據先保存在內存流中而後一次取出。

範例:引入內存流

import java.io.ByteArrayOutputStream;
import java.io.InputStream;

public class TestFileStream {
    public static void main(String[] args)throws Exception {
        InputStream inputStream = System.in;
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] data = new byte[1024];
        System.out.println("請輸入內容:");
        int temp = 0;
        while ((temp = inputStream.read(data)) != -1){
            byteArrayOutputStream.write(data,0,temp);
            if (temp < data.length){
                break;
            }
        }
        inputStream.close();
        byteArrayOutputStream.close();
        System.out.println("輸出內容爲:" + new String(byteArrayOutputStream.toByteArray()z));
    }
}

現在雖然實現了鍵盤輸入數據的功能,但是整體的實現邏輯過於混亂了,即java提供的System.in並不好用,還要結 合內存流來完成,複雜度很高。

如果要想在IO中進行中文的處理,最好的做法是將所有輸入的數據保存在一起再處理,這樣纔可以保證不出現亂碼

兩種輸入流(原生InputStream的進化版)

BufferedReader類

BufferedReader類屬於一個緩衝的輸入流,而且是一個字符流的操作對象。
在java中對於緩衝流也分爲兩類:字節緩衝流(BufferedInputStream)、字符緩衝流(BufferedReader)。
之所以選擇BufferedReader類操作是因爲在此類中提供有如下方法(讀取一行數據):

    /**
     * Reads a line of text.  A line is considered to be terminated by any one
     * of a line feed ('\n'), a carriage return ('\r'), or a carriage return
     * followed immediately by a linefeed.
     *
     * @return     A String containing the contents of the line, not including
     *             any line-termination characters, or null if the end of the
     *             stream has been reached
     *
     * @exception  IOException  If an I/O error occurs
     *
     * @see java.nio.file.Files#readAllLines
     */
    public String readLine() throws IOException {
        return readLine(false);
    }

readLine()這個方法可以直接讀取一行數據(以回車爲換行符)
但是這個時候有一個非常重要的問題要解決,來看BufferedReader類的定義與構造方法:

public class BufferedReader extends Reader {

    /**
     * Creates a buffering character-input stream that uses an input buffer of
     * the specified size.
     *
     * @param  in   A Reader
     * @param  sz   Input-buffer size
     *
     * @exception  IllegalArgumentException  If {@code sz <= 0}
     */
    public BufferedReader(Reader in, int sz) {
        super(in);
        if (sz <= 0)
            throw new IllegalArgumentException("Buffer size <= 0");
        this.in = in;
        cb = new char[sz];
        nextChar = nChars = 0;
    }

    /**
     * Creates a buffering character-input stream that uses a default-sized
     * input buffer.
     *
     * @param  in   A Reader
     */
    public BufferedReader(Reader in) {
        this(in, defaultCharBufferSize);
    }

而System.in是InputStream類的子類,這個時候與Reader沒有關係,要建立起聯繫就要用到InputStreamReader 類。如下:
在這裏插入圖片描述

範例:利用BufferedReader實現鍵盤輸入

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class TestFileStream {
    public static void main(String[] args)throws Exception {
        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(System.in));
        System.out.println("請輸入信息:");
        String str = bufferedReader.readLine();
        System.out.println("輸入信息爲:" + str);
    }
}

以上形式實現的鍵盤輸入有一個大特點:由於接收的數據類型爲String,可以使用String類的各種操作進行數據處理並且可以變爲各種常見數據類型

java.util.Scanner類

打印流解決的是OutputStream類的缺陷,BufferedReader解決的是InputStream類的缺陷。而Scanner解決的是 BufferedReader類的缺陷(替換了BufferedReader類) 。Scanner是一個專門進行輸入流處理的程序類,利用這個類可以方便處理各種數據類型,同時也可以直接結合正則表達式進行各項處理。

在這個類中主要關注以下方法:

  • 判斷是否有指定類型輸入
public boolean hasNextXXX()
  • 取得指定類型數據
public 數據類型 nextXXX()
  • 自定義分隔符
public Scanner useDelimiter(Pattern pattern)
  • 構造方法
public Scanner(InputStream source) 

範例:使用Scanner實現數據輸入

import java.util.Scanner;

public class TestFileStream {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入信息:");
        if (scanner.hasNext()){
            System.out.println("輸出內容爲:" + scanner.next());
        }
        scanner.close();
    }
}

使用Scanner還可以接收各種數據類型,並且幫助用戶減少轉型處理

範例:接收其他類型數據

import java.util.Scanner;

public class TestFileStream {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入年齡:");
        if (scanner.hasNextInt()){
            System.out.println("年齡爲:" + scanner.next());
        }else {
            System.out.println("輸入的不是數字!"); ;
        }
        scanner.close();
    }
}

最爲重要的是,Scanner可以對接收的數據類型使用正則表達式判斷

範例:利用正則表達式進行判斷

import java.util.Scanner;

public class TestFileStream {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入出生日期:");
        if (scanner.hasNext("\\d{4}-\\d{2}-\\d{2}")){
            System.out.println("生日爲:" + scanner.next());
        }else {
            System.out.println("輸入的格式非法,不是生日");
        }
        scanner.close();
    }
}

範例:使用Scanner操作文件

import java.io.File;
import java.util.Scanner;

public class TestFileStream {
    public static void main(String[] args) throws Exception{
        Scanner scanner = new Scanner(new File("C:" + File.separator + "Users" + File.separator
                + "DELL" + File.separator + "Desktop" + File.separator + "Test.txt"));
        scanner.useDelimiter("\n") ; // 自定義分隔符
        while (scanner.hasNext()){
            System.out.println(scanner.next());
        }
        scanner.close();
    }
}

總結:以後除了二進制文件拷貝的處理之外,那麼只要是針對程序的信息輸出都是用打印流(PrintStreamPrintWriter),信息輸出使用Scanner

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