內存操作流
定義:除了文件之外,IO操作也可以發生在內存中,發生在內存中的操作流稱爲內存流。
文件流的操作裏面一定會產生一個文件數據(不管後這個文件數據是否被保留)。
如果現在需求是:需要進行IO處理,但是又不希望產生文件,這種情況下就可以使用內存作爲操作終端。
內存流也分爲兩類:
- 字節內存流:
ByteArrayInputStream
、ByteArrayOutputStream
- 字符內存流:
CharArrayReader
、CharArrayWriter
首先來觀察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
不是很方便,其缺點有兩個:
- 所有的數據必須轉換爲字節數組。
- 如果要輸出的是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提供有專門的打印流處理類:PrintStream
、PrintWriter
使用系統提供的打印流
打印流分爲字節打印流:PrintStream
、字符打印流:PrintWriter
。
首先來觀察這兩 個類的繼承結構與構造方法:
此時看上圖繼承關係我們會發現,有點像之前講過的代理設計模式,但是代理設計模式有如下特點:
- 代理是以接口爲使用原則的設計模式。
- 最終用戶可以調用的方法一定是接口定義的方法。
打印流優缺點:
- 優點:擴展功能特別方便,需要不同的功能時只需要更換裝飾類即可。
- 缺點:類結構複雜。
打印流應用的是裝飾設計模式(基於抽象類的設計模式):核心依然是某個類(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™ 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™ 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
的對象)
- 標準輸出(顯示器):
System.out
- 錯誤輸出:
System.err
-
輸入(輸入流
InputStream
的對象)標準輸入(鍵盤):
System.in
系統輸出
系統輸出一共有兩個常量:out、err,並且這兩個常量表示的都是PrintStream類的對象。
- out輸出的是希望用戶能看到的內容
- 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();
}
}
總結:以後除了二進制文件拷貝的處理之外,那麼只要是針對程序的信息輸出都是用打印流(PrintStream
、 PrintWriter
),信息輸出使用Scanner
。