2013年9月10日 星期二

Format input and output



作者: 許裕永

字串的運算可以說是程式中最常用的運算。以輸入而言,無論是從鍵盤或檔案輸入,其輸入值一律是字串(字元的組合)。而輸出也是以字元碼的方式輸出。雖然 Java已經提供了三個字串的相關類別來協助設計師儲存及運算字串,但是它們的功能還是不夠應付越來越多元化的需求。比方說我們要從輸入的一堆文字及數值 混合的資料中,只取得其中的數值資料。怎麼辦?如果要取得的數值還必須是具有小數點位數的呢?如果我們希望使用者輸入的文字符合某個特定的樣式呢(例:訂 單編號)?
本章介紹的類別,便是用來協助設計師有關字串的進階運算。舉凡輸入、分析、處理及輸出,都會用到,其中還有許多設定樣式的符號,讀者應仔細研讀。
 
1. Pattern
輸入的格式化,指的就是在一群資料來源中,依照某個樣式來擷取部份的值,以供程式使用;或者檢測輸入的字串是否符合某個特定的樣式。這個樣式是依照 Java設定的符號來建立,建立時可以建構成String物件,也可以建成Patter物件。這些符號則撰寫在 java‧util‧regex‧Pattern類別的JDK文件中。僅就常用符號列示於下:
1-1 建立樣式的符號
字元符號
  1. 财 \d  一個數字字元。
  2. 财 \s  一個空白字元。
  3. 财 \w  一個文字字元,數字字元或底線符號。
  4. 财 ‧   一個任意字元(含空白)。
  5. 财 〔〕  括號中的指定字元,若有括號有一個以上之字元,則表示括號中的任意一個字元均可。
  6. 财 〔-〕 括號中‘-’前後字元之間的所有字元。
例一:
\d\d\d[-]\d\d\d\d
符合字串:123-4567。
例二:
\d\d\d[-\w]\d\d\d\d
符合字串:123-4567或123_4567。
例三:
[A-Z][12]\d\d\d\d\d\d\d\d
符合字串:A123456789 或 B223456789。
例四:
[Aa][9][5][-][A-a-]\d\d\d\d
符合字串:A95-C5555 或 a95-F2456。
例中的‘9’、‘5’及‘-’三個字元不見得要置於中括號之中,但是在讀者尚未完全掌握有那一些字元及符號屬於Pattern樣式之前,這種寫法可以避免錯誤發生。
字數控制
  1. 财 +  前置符號一個或一個以上。若要包含一個以上之前置符號可以使用小括號。
  2. 财 ?  前置符號零個或一個。若要包含一個以上之前置符號可以使用小括號。
  3. 财 *  前置符號零個或任意個。若要包含一個以上之前置符號可以使用小括號。
例一:
\d+
符合字串:1 或 12 或 123。
例二:
\w\d+
符合字串:a2 或 a23 或 a234。(+只針對\d)
例三:
(\w\d)+
符合字串:a1a2 或 a1b2a3b4。
例四:
\w\d?
符合字串:a 或 a1。
例五:
\w\d*
符合字串:a 或 a1 或 a12。
1-2 建構物件
Pattern和Math類別一樣,它們都不是抽象類別,但是也都沒有提供建構方法,所以也都不能用new建構物件。不過,Pattern類別有提供一個類別方法成員,來建構Pattern類別物件。
1-3 重要方法
方法一: static boolean matches(String regex, CharSequence input)
運算結果:第二個參數的值是否完全符合第一個參數所設定的樣式。第二個參數的Charsequence是一個界面,它可以代表所有字串的相關類別。第一個 參數則是以上一節介紹的樣式符號組合的字串。這個方法是一個類別方法成員,可以用類別名稱直接呼叫,不必建構Pattern物件。也就是說:如果要比對資 料來源是否完全符合某一個特殊樣式,而且資料來源的資料量不大,則適用此方法。
例一:
Scanner s=new Scanner(System.in);
System.out.print("請輸入身份證字號-->");
if(Pattern.matches("[A-Za-z][12]\\d\\d\\d\\d\\d\\d\\d\\d",s.next()))
 System.out.println("身份證字號格式正確");
else
 System.out.println("身份證字號格式錯誤");
特別注意:在上一節中介紹的符號有部份是以‘\’開頭,但在字串中,因為‘\’是屬於特殊字元,所以它的前面必須再加一個‘\’。
例二:
Scanner s=new Scanner(System.in);
System.out.print("請輸入訂單號碼-->");
if(Pattern.matches("[Aa][9][5][-][A-Fa-f]\\d\\d\\d\\d",s.next()))
 System.out.println("訂單號碼格式正確");
else
 System.out.println("訂單號碼格式錯誤");
Pattern‧matches()的檢測是包含樣式與字數都必須完全符合。如果是要檢測在大量的資料中,是否有一部份的文字符合設定的樣式,就不適用此方法了。
 
方法二:static Pattern compile(String regex)
運算結果:依照String物件的樣式,來建構Pattern物件。
例一:
Pattern p=Pattern.compile(“\\d\\d*”);
 
方法三:String[] split(CharSequence input)
運算結果:將資料來源依照Pattern物件中設定的樣式切割,並建構成String物件陣列。
例一:
Pattern p=Pattern.compile("\\w\\d+");
String[] array=p.split("a1xxxxb22yyyyc333zzzz");
for(String temp:array)
 System.out.println(temp);
列印結果:   xxxx yyyy zzzz    。請注意:符合樣式的切割點有三個,所以會將資料來源切割成五個部份,所以陣列長度為5。
 
方法四:Matcher matcher(CharSequence input)
運算結果:建構資料來源物件。Matcher類別將於下一節介紹,Pattern物件只是提供樣式,或執行完全比對及切割。實際上能在大量資料中,執行逐 一比對工作的是Matcher物件。參數列中的參數就是要比對的資料。CharSequence(界面)可以代表所有的字串相關類別。
 
2. Matcher
java‧util‧regex‧Matcher類別的物件,才能在大量的資料來源中,逐一取得符合樣式的文字。
2-1 建構物件
Matcher類別,也不是抽象類別,也沒有提供建構方法,而且沒有提供任何的類別方法成員來建構物件,它的物件只能透過Pattern物件呼叫matcher方法來建構。
例一:
Pattern p=Pattern.compile("\\w(\\d)+");
Matcher m=p.matcher("a2a333");
”a2a333”為資料來源,建立Matcher物件。
2-2 重要方法
方法一:boolean find()。
運算結果:資料來源中是否有符合Pattern樣式的文字。若有找到運算值為true,並將指標移至符合Pattern樣式文字的終點,來做為下次尋找的起點。若沒有找到運算值為false。
方法二:String group()
運算結果:將find()找到的文字,建構成String物件。
方法三:int start()
運算結果:find()找到文字的起點序號。
例一:
Pattern p=Pattern.compile("\\w\\d+");
Matcher m=p.matcher("a1b12c1d12e11fg55");
while(m.find())
 System.out.println(m.start()+":"+m.group());
列印結果:0:a1 2:b12 5:c1 7:d12 10:e11 14:g55。
 
例二:
Pattern p=Pattern.compile("\\w\\d+");
String source="a1xxxxb22yyyyc333zzzz";
Matcher m=p.matcher(source);
while(m.find()){
 String temp=m.group();
 source=source.replace(temp,"<p>");
}
System.out.println(source);
列印結果:<p>xxxx<p>yyyy<p>zzzz。
 
例三:
import java.io.*;
import java.util.regex.*;
import java.util.*;
public class JavaTest{
 public static void main(String[] args)throws FileNotFoundException,IOException{
  BufferedReader br=new BufferedReader(new FileReader("JavaTest.java"));
  StringBuffer fileContent=new StringBuffer();
  String temp=null;
  while((temp=br.readLine())!=null){
   fileContent.append(temp);
  }
  br.close();
  Pattern p=Pattern.compile("[Jj]ava");
  Matcher m=p.matcher(fileContent);
  int javaCount=0;
  while(m.find()){
   javaCount++;
  }
  System.out.println("Javajava出現的次數為:" + javaCount + "");
 }
}
列印結果:Javajava出現的次數為:11次。
 
方法四:Matcher usePattern(Pattern newPattern)
運算結果:重新設定Matcher物件的Pattern物件。也就是用新的樣式來檢測相同的資料來源。
方法五:boolean matches()
運算結果:資料來源和Pattern物件的樣式是否完全一樣。
例一:
Scanner s=new Scanner(System.in);
Pattern p=Pattern.compile(“[A-Za-z][12]\\d\\d\\d\\d\\d\\d\\d\\d”);
System.out.print(“請輸入身份證字號à”);
Matcher m=p.matcher(s.next());
if(m.matches())
 System.out.println(“身份證字號格式正確”);
else
 System.out.println(“身份證字號格式錯誤”);
本方法的使用和Pattern‧matches()一樣。
 
3. Scanner
java‧util‧Scanner類別的物件,目前為此我們都用它來接收鍵盤的輸入。其實它是Java為了簡化輸入的流程,特別新設計的類別。它能接收任何InputSteam或檔案的資料,還能從資料來源中,只擷取部份特定樣式的文字,或特定格式的資料進入程式。
3-1 建構物件
方法一:Scanner(File source)
以檔案為資料來源參數建構Scanner物件,可以省下大量的讀檔程式碼。
例一:
Scanner fileContent=new Scanner(new File("JavaTest.java"));
本行敍述句便已經將開啟檔案JavaTest‧java為資料來源。相對的也請記得,讀檔完畢後應呼叫close()來關閉檔案。
方法二:Scanner(InputStream source)
以任何InputStream物件為資料來源,建構Scanner物件。
例一:
Scanner s=new Scanner(System.in);
System‧in指的是java‧lang‧System類別中的類別常數成員in。它是一個InputStream類別的物件,用來接收鍵盤的輸入。 但是它提供read()一次只能讀一個字元,操作上不方便,所以本書中都利用它建構Scanner物件,來簡化接收鍵盤輸入的程式碼。
方法三:Scanner(Readable source)
以任何Reader物件為資料來源,建構Scanner物件。
方法四:Scanner(String source)
以String物件為資料來源,建構Scanner物件。
 
3-2 重要方法
方法一:boolean hasNext() 
運算結果:是否有未讀入之字串。
方法二:String next()
運算結果:讀入文字並建構為String物件(讀入至空白字元)。
例一:
Scanner fileContent=new Scanner(new File("JavaTest.java"));
while(fileContent.hasNext()){
 System.out.println(fileContent.next().toUpperCase());
}
fileContent.close();
每次的讀入以空白為單位。
方法三:boolean hasNextLine() 
運算結果:是否有未讀入之字串行。
方法四:String nextLine()
運算結果:讀入一行文字並建構為String物件(不含換行符號)。
例一:
Scanner fileContent=new Scanner(new File("JavaTest.java"));
while(fileContent.hasNextLine()){
 System.out.println(fileContent.nextLine().toUpperCase());
}
fileContent.close();
每次的讀入以行為單位。
 
方法五:boolean hasNext(樣式參數)
運算結果:是否有未讀入而且符合樣式參數的字串。樣式參數可以是String物件或Pattern物件。
方法六:String next(樣式參數)
運算結果:讀入下一個符合樣式參數的文字並建構為String物件。樣式參數可以是String物件或Pattern物件。
例一:
Scanner fileContent=new Scanner(new File("JavaTest.java"));
Pattern p=Pattern.compile(".*i.*");
while(fileContent.hasNext(p)){
 System.out.println(fileContent.next(p));
}
fileContent.close();
列印結果:import java.io.*; import java.util.regex.*; import java.util.*;
public。
列印檔案文字中有包含有‘i’字元的單字。連續無空白者視為一個單字。值得注意的是迴圈的寫法,只要有遇到第一個讓hasNext(p)產生false的文字,迴圈便會結束,所以本例只列印到public程式便結束了,並沒有檢測完整個檔案。請注意例二的寫法。
 
例二:
Scanner fileContent=new Scanner(new File("JavaTest.java"));
while(fineContent.hasNext()){
 if(s.hasNext(".*[Jj]ava.*"))
  System.out.println(fileContent.next(".*[Jj]ava.*"));
 else
  s.next();
}
fileContent.close();
列印結果:檔案中所有包含java或Java的文字。
本例的迴圈是以hasNext()為條件,先檢測是否有一下個文字,迴圈中再檢測那個文字是否符合樣式參數,若有取則得該文字,若沒有也會用next()移至下一個文字。如此才可以檢測完整個檔案。
 
方法七:boolean hasNextXxx() 
運算結果:是否還有符合指定資料型別的資料。
方法八:Xxx nextXxx() 
運算結果:讀入下一個指定資料型別的資料。
例一:
Scanner s=new Scanner(System.in);
System.out.print("請輸入任意整數-->");
int a=s.nextInt();
System.out.println(a);
本例是之前我們用來接收使用者輸入一個整數的程式碼,但這種寫法卻很容易因為使用者輸入的文字不是數字,而造成程式中斷。
例二:
Scanner s=new Scanner(System.in);
int a=0;
while(true){
 System.out.print("請輸入任意整數-->");
 if(s.hasNextInt()){
  a=s.nextInt();
  break;
 }
 s.next();
}
System.out.println(a);
這種寫法便可以讓使用者一定要輸入一個整數。當然,不是指能限定為整數或某一種資料型別。讀者可以自行練習,強迫使用者一定要輸入符合某一樣式的文字。
 
4. PrintStream 與 PrintWriter
Java除了開發新類別Scanner來簡化輸入的流程,也加強了Java‧io中的PrintStream及PrintWriter兩個類別來簡化輸出 的流程,兩個類別的差異在於Stream是以一個byte為單位,Writer是以字元為單位(適用於純文字檔),其他包括建構方法的參數或實體成員方法 均大同小異,本章便以PrintStream為主體來介紹。
之前我們在程式碼中執行輸出時所寫的:System‧out‧println(); 中的System‧out,指的是java‧lang‧System類別中的類別常數成員out,而out便是一個以PrintStream類別建構的物 件。而輸出的目的地是螢幕。本章介紹的重要方法,自然也可以用System‧out來呼叫。
4-1 建構物件
用來建構PrintStream物件的參數,便是後續程式碼,執行輸出時的目的地。
建構方法一:PrintStream(File file)
以檔案物件為參數建構PrintStream物件。
建構方法二:PrintStream(OutputStream out)
以OutputStream物件為參數建構PrintStream物件。
建構方法三:PrintStream(String fileName)
以String物件為參數建構PrintStream物件。請注意此String物件代表檔案名稱,可以省略建構File物件的步驟,但也就沒有辦法指定是要以加入或覆蓋的方式輸出。
4-2 重要方法
方法一:void println()
運算結果:輸出空白行。
方法二:void print(參數)
運算結果:將參數輸出。
方法三:void println(參數)
運算結果:將參數輸出後並輸出換行符號。
例一:
import java.io.*;
import java.util.*;
public class JavaTest{
 public static void main(String[] args)throws FileNotFoundException,IOException{
  Scanner s=new Scanner(new File("JavaTest.java"));
  PrintStream pw=new PrintStream("Copy_JavaTest.java");
  while(true){
   if(!s.hasNextLine())
    break;
   pw.println(s.nextLine());
  }
  pw.close();
 }
}
執行結果:將JavaTest‧java的檔案內容,輸出到Copy_JavaTest‧java之中。請記得要呼叫close()。
 
方法四:PrintStream printf(String format, Object... args)
運算結果:將參數值以特定格式輸出。
Java提供了NumberFormat類別,可以用來把數值格式化成String物件,以做為輸出物件,也可以轉回數值再運算。但是如果只是要輸出,而 沒有要再運算,而且其格式也不很複雜的話,用NumberFormat就顯得很囉嗦。所以Java又提供了printf這個方法,來方便格式化的輸出。
而且在println()中,變數與字串必須用‘+’串聯,在printf()中也有不一樣的寫法。
例一:
String s="Yung";
Integer a=18;
System.out.printf("My name is %s,my age is %d." ,s ,a);
列印結果:My name is Yung,my age is 18‧
在字串參數中,用符號代表要置入的值的型別,再將變數(值)逐一依序置入為後方參數。
資料型別代號:
  1. 财 b Boolean
  2. 财 c Char
  3. 财 d Byte Short Integer Long
  4. 财 f Float Double
  5. 财 s String
在printf()中的第二個參數寫的是Object‧‧‧,指的是任意個物件,也因為是物件,所以上列的資料型別寫的是Wrapper Classes 的類別名稱。不過因為有Auto Boxing 及 unBoxing所以撰寫程式時,即使變數是用基本資料型別宣告也是可以的。另外,以運算式為參數也 可以哦。夠方便吧!
 
例二:
double d=10.0;
System.out.printf("10除以3的值為:%f",d/3);
運算結果:3‧333333。本例示範以基本資料型別變數的運算式為參數。
 
例三:
double d=10.0;
System.out.printf("10除以3的值為:%.2f",d/3);
運算結果:3‧33。本例示範指定格式的輸出。
完整格式語法(中括號代表可以省略):
%〔參數序號$〕〔格式代號〕〔寬度〕〔‧小數點位數〕資料型別代號
參數序號:指定某一序號之參數,預設為依序置入。
格式代號(可複數使用,但搭配時應注意邏輯):
  1. 财 - 若寬度大於數值長度,靠左對齊。
  2. 财 + 數值前方加正負符號。
  3. 财 0 若寬度大於數值長度,空格的部份以0顯示。
  4. 财 , 使用千分號。
  5. 财 ( 如果值為負值,將該值加上小括號。
寬度:若數值長度大於寬度,還是會完整顯示數值。
小數點位數:若數值的實際小數點位數小於設定值,則補0。
 
例四:
double d=-100000.0;
System.out.printf("%3$-10d除以%2$+05d的值:%1$(,.2f",d/3,3,-100000);
列印結果:-100000   除以+0003的值為:(33,333.33)。
方法五:PrintStream format(String format, Object... args)  
運算結果:和printf()完全一樣。
 
5. 認證重點
5-1 Pattern
  • 财 Pattern和Math類別一樣,它們都不是抽象類別,但是也都沒有提供建構方法,所以也都不能用new建構物件。
  • 财 matches()的檢測是包含樣式與字數都必須完全符合。
  • 财 compile(String regex)會依照String物件的樣式,來建構Pattern物件。請注意不是getInstance()。
  • 财 matcher(CharSequence input)會建構Matcher物件。
5-2 Matcher
  • 财 Matcher類別,也不是抽象類別,也沒有提供建構方法,而且沒有提供任何的類別方法成員來建構物件,它的物件只能透過Pattern物件呼叫matcher方法來建構。
5-3 Scanner
  • 财 java‧util‧Scanner類別的物件,是Java為了簡化輸入的流程,特別新設計的類別。它能接收任何InputSteam或檔案的資料,還能從資料來源中,只擷取部份特定樣式的文字,或特定格式的資料進入程式。
  • 财 以File物件建構Scanner物件,就是開啟檔案來做為資料來源。相對的也請記得,讀檔完畢後應呼叫close()來關閉檔案。
  • 财 System‧in指的是java‧lang‧System類別中的類別常數成員in。它是一個InputStream類別的物件,用來接收鍵盤的輸入。 但是它提供read()一次只能讀一個字元,操作上不方便,所以本書中都利用它建構Scanner物件,來簡化接收鍵盤輸入的程式碼。
  • 财 擷取部份資料時,應利用hasNext()搭配next(),才比較不會造成執行時期的錯誤。
5-4 PrintStream與PrintWriter
  • 财 Java加強了Java‧io中的PrintStream及PrintWriter兩個類別來簡化輸出的流程。
  • System‧out‧println();中的System‧out,指的是java‧lang‧System類別中的類別常數成員out,而out便是一個以PrintStream類別建構的物件。而輸出的目的地是螢幕。
  • 财 用String物件代表檔案名稱,可以省略建構File物件的步驟,但也就沒有辦法指定是要以加入或覆蓋的方式輸出。
  • 财 在printf()中的第二個參數寫的是Object‧‧‧,指的是任意個物件,也因為是物件,所以應該要用Wrapper Classes的類別建構的 物件。不過因為有Auto Boxing 及 unBoxing所以撰寫程式時,即使變數是用基本資料型別宣告也是可以的。另外,以運算式為參數也可以。
  • 财 printf()的String參數中設定格式的完整語法(中括號代表可以省略):
  • %〔參數序號$〕〔格式代號〕〔寬度〕〔‧小數點位數〕資料型別代號

沒有留言:

張貼留言