JDK APIjavascript
什麼是 JDK APIphp
API文檔是咱們用來了解JDK中提供的類庫,咱們能夠先經過索引輸入並找到咱們須要瞭解的類,然後咱們就能夠方便的瞭解該類的做用,常量的做用,以及該類提供的全部方法的做用,以及方法的參數及返回值的含義。java
JDK包結構c++
文檔註釋規範程序員
文檔註釋正則表達式
經過註釋提升Java源程序代碼的可讀性;使得Java程序條理清晰,易於區分代碼行與註釋行。另外一般在程序開頭加入做者,時間,版本,要實現的功能等內容註釋,方便後來的維護以及程序員的交流。objective-c
文檔註釋規範算法
Javadoc命令生成文檔編程
String及其常見APIc#
String是不可變對象
因爲字符串在實際開發中被普遍使用,那麼在頻繁使用某個字符串時,會出現頻繁建立一個字符串對象的現象,java爲此對字符串的使用採用了一個優化措施,使得String對象爲不可變對象,一旦在內存中建立,內容不能發生變化,若要對字符串內容改變,那麼就會建立新對象。這樣作的目的是能夠最大程度的重用相同內容的字符串以減少系統資源的開銷。
String常量池
JVM對字符串有一個限制,讓字符串做爲不變對象,這樣就能夠作到重用。事實上,當咱們經過字面量,常量來初始化一個字符串時,JVM首先會從字符串的常量池(一個JVM內部維護的內存區域,用來保存已經建立過的字符串對象)中查詢用來保存該字符串的對象是否存在,若存在則直接引用,若不存在則建立該字符串對象並存入常量池,而後引用它。由於字符串內容不能改變,因此咱們能夠放心的重用他們。
內存編碼及長度
使用indexOf實現檢索
int indexOf(int ch):用來檢查給定的一個字符在當前字符串中第一次出現的下標位置。這裏的下標和數組的下標意思相近,0表示該字符串的第1個字符,以此類推。當該字符串中並不包含給定的字符時,那麼該方法返回-1。 例如:
String str = "HelloWorld"; System.out.println(str.indexOf('W'));//5 System.out.println(str.indexOf('h'))//-1
使用substring獲取子串
String substring(int begin,int end):用來截取當前字符串的部份內容以獲取這個子字符串。咱們只須要傳入兩個整數,一個用來表示從哪裏開始,另外一個用來表示截取到哪裏。這裏的位置要使用字符串的下標來表示,而且要注意,這兩個數字表示的範圍是「含頭不含尾的」,換句話說就是包含開始下標的字符,可是不包含結束下標的字符。
trim
String trim():將字符串兩邊的空白(空白有不少種,空格是其中之一)去除掉,並將去除後的新字符串返回給咱們。
charAt
char charAt(int index):用於給定一個下標位置,來獲取該字符串中這個位置的字符。
startsWith和endsWith
boolean startsWith(String suffix):用來判斷當前字符串是不是以給定的字符串開始的。這裏要注意大小寫是敏感的。
boolean endsWith(String suffix):用來判斷當前字符串是不是以給定的字符串結尾的。
例如咱們可使用endsWith()就能夠根據一個文件的名字來判斷它是不是以".jpg",".gif"等字符串結尾來得知該文件是否爲圖片。
String str = "java.jpg"; if(str.endsWith(".jpg")){ System.out.println("是一張圖片"); }else{ System.out.println("不是一張圖片"); }
大小寫變換
valueOf
字符串提供了不少重載的valueOf()方法,能夠將其餘基本類型的值以字符串的形式描述。
static String valueOf(int i): 返回 int 參數的字符串表示形式 static String valueOf(boolean b): 返回 boolean 參數的字符串表示形式 static String valueOf(char c): 返回 char 參數的字符串表示形式 static String valueOf(double d): 返回 double 參數的字符串表示形式 static String valueOf(char[] c): 返回 char 數組參數的字符串表示形式 static String valueOf(char[] c,int offset,int count): 返回 char 數組參數的特定子數組的字符串表示形式。 static String valueOf(float): 返回 float 參數的字符串表示形式 static String valueOf(long l): 返回 long 參數的字符串表示形式 static String valueOf(Object o): 返回 Object 參數的字符串表示形式
StringBuilder及其經常使用API
StringBuilder封裝可變字符串
StringBuilder經常使用方法
append(String str):追加字符串; insert (int dstOffset,String s):插入字符串; delete(int start,int end):刪除字符串; replace(int start,int end,String str): 替換字符串; reverse():字符串反轉。
StringBuilder
StringBuilder的不少方法的返回值均爲StringBuilder類型。這些方法的返回語句均爲:return this。也就是能夠作鏈式調用
因爲改變封裝的字符序列後又返回了該對象的引用。
buf.append("ibm").append("java").insert(3, "oracle").replace(9, 13, "JAVA"); System.out.println(buf.toString());
StringBuilder 總結
StringBuilder是可變字符串。字符串的內容計算,建議採用StringBuilder實現,這樣性能會好一些。
java的字符串鏈接的過程是利用StringBuilder實現的,代碼以下所示:
String s = "AB"; String s1 = s + "DE"+1; String s1 = new StringBuilder(s).append("DE").append(1).toString();
StringBuffer 和StringBuilder的區別:
StringBuffer是線程安全的,同步處理的,性能稍慢; StringBuilder是非線程安全的,併發處理的,性能稍快。
基本正則表達式
正則表達式簡介
所謂正則表達式就是使用一系列預約義的特殊字符來描述一個字符串的格式規則,而後使用該格式規則匹配某個字符串是否符合格式要求。
一、「.」和"\" "."點兒,在正則表達式中表示任意一個字符。 "\"在正則表達式中是轉意字符,當咱們須要描述一個已經被正則表達式使用的特殊字符時,咱們就能夠經過使用"\"將其轉變爲本來的意思。 "\"在正則表達式中也有一些預約義的特殊內容: \d:表示任意一個數字 \w:表示任意一個單詞字符(只能是 數字,字母,下劃線) \s:表示任意一個空白字符(\t \r \n \f \x0B) \D:表示任意一個非數字字符 \W:表示任意一個非單詞字符 \S:表示任意一個非空白字符 二、"字符集合 []" "[]"用來描述單一字符,方括號內部能夠定義這個字符的內容,也能夠描述一個範圍。例如: [abc]:表示該字符只能是a或者b或者c [123]:表示該字符只能是1或者2或者3 當咱們須要描述全部小寫字母時,咱們可使用範圍 [a-z],表示該字符能夠是任意一個小寫字母。 一樣還可使用 [0-9] 來表示該字符能夠是任意一個數字。 也能夠在多個範圍內選擇。好比,[a-zA-Z0-9_] 表示該字符能夠是任意字母,數字以及"下劃線"。 三、"*"、"+"、"?" 一般咱們須要描述的字符串會有不少重複出現的元素,但又不須要嚴格限制出現的次數時,咱們就可使用"*","+"這些量詞。 例如:郵箱地址,那麼在"@"字符前容許出現若干字符做爲用戶名。這時候咱們就可使用"\w+"來描述這裏至少出現一個單詞字符了。 "+":表示內容能夠連續出現至少1次以上 "*":表示內容出現0-若干次 "?":表示內容出現0-1次 四、{n}、{n,}{n,m} 除了前面講到的量詞外,有時咱們也須要要求內容出現的次數有具體要求。好比手機號碼。 這時咱們要求出現的數字就不能是一個模糊的概念了,而必需要求11位。 又好比咱們要求用戶輸入密碼時,要求密碼是6-15位。遇到這類問題是,咱們可使用: {n}:表示內容必須出現n次 {n,m}:表示內容出現n-m次 {n,}:表示內容出現至少n次 例如,\d{11} 就表示數字只能出現11位,這樣就解決了上述的問題。
分組
經過上面的內容,咱們還沒法解決相似下面的問題: 在描述電話號碼時,前面有區號,區號的形式能夠是0086或者+86 那麼咱們如何在這兩個字符串之間選擇? 這時咱們可使用分組"()"。() 能夠將內容看作一個總體,()中可使用"|"來表示或關係。例如,(+86|0086) 表示這裏能夠是+86或者0086。
"^"和"$"
經過在正則表達式的開始添加"^"以及末尾添加"$"來表示一個總體。若不使用它們,那麼正則表達式只匹配某個字符串的部份內容是否符合格式規則, 但使用它們,則要求字符串必須從頭至尾都知足該格式規則。 例如,^\w{ 8,10 }$ 表示總體字符串只能出現單詞字符8-10個。
String正則相關API
matches方法
matches()方法的參數要求咱們傳入一個用字符串描述的正則表達式,而後使用該正則表達式描述的字符串格式規則來匹配當前字符串,若知足那麼該方法返回true。不然返回false。 例如:
String emailRegEx = "^[a-zA-Z0-9_.-]+@([a-zA-Z0-9-]+\\.)+[a-zA-Z0-9]{2,4}$"; String email = "bjliyi@tarena.com.cn"; System.out.println(email.matches(emailRegEx));//true
split方法
String[] split(String regex):參數要求傳入一個用字符串描述的正則表達式,而後使用該正則表達式描述的字符串規則來匹配當前字符串,並按照知足的部分將字符串拆分。 例如:
String str = "java,c#,php,javascript"; String[] array = str.split(","); //[java,c#,php,javascript] System.out.println(Arrays.toString(array));
replaceAll方法
String replaceAll(String regex,String replacement):參數要求傳入一個用字符串描述的正則表達式和一個須要替換的字符串,而後使用該正則表達式描述的字符串規則來匹配當前字符串,並將知足的部分替換爲須要替換的這個字符串。 例如:
String str = "abc123bcd45ef6g7890";; str = str.replaceAll("\\d+", "數字"); System.out.println(str);//abc數字bcd數字ef數字g數字
Object
Object
Object類是java中全部類的頂級父類。若咱們定義的一個類沒有顯式的使用extends繼承某個類時,默認就是繼承自Object的。
toString()方法
1. 如何重寫toString方法 1. 既然Object是全部類的頂級父類,那麼在Object中定義的方法全部的類都具有。其中之一就是toStirng()方法。 2. String toString():該方法java但願咱們重寫時返回一個字符串,這個字符串的原則爲:用一個字符串來描述當前對象。 3. Object實現了toString()方法,返回的是當前對象的「句柄」。 格式爲:類的徹底限定名@hashcode。 由於Object實現的toString()方法不具有什麼實際開發意義,因此若咱們須要在子類中使用該方法時一般咱們會重寫它。 2. String類重寫toString() public String toString(){ return this; } 從源碼中咱們能夠看到,String重寫了Object的toString()方法,該方法直接將當前字符串對象自身返回。
equals()方法
equals方法
boolean equals():該方法java但願咱們重寫時返回一個boolean值,表示兩個對象間的內容比較是否一致。 Object已經實現了該方法,代碼以下:
public boolean equals (Object obj) { return (this == obj); }
由此看出,實際上Object中重寫該方法依舊使用"=="比較,因此當咱們在子類中須要比較對象內容時就要重寫該方法。
如何重寫equals方法
重寫equals方法應遵循幾個規則:
1. 任何對象與null比較都應返回false 2. 兩個對象不屬於同一個類時應返回false 3. 同一個對象equals比較應當恆等爲true
那麼除此以外,兩個對象在比較時,應根據具體的業務需求來自行決定對象的哪些屬性相同時對象內容相同。
String重寫equals()方法
String重寫了equals方法,做用是比較兩個字符串對象中保存的字符序列是否徹底一致。
equals與 == 的區別
"=="是值比較,對於引用類型變量而言,該變量保存的是對象的地址,因此使用"=="比較時,意思爲兩個變量的地址是否相等, 換句話說就是看兩個變量引用的是否爲同一個對象 equals是內容比較,對於兩個引用變量而言,是比較兩個變量所引用的對象內容是否相同。 舉個例子, 就好像一對雙胞胎,他們是兩個獨立的個體,是兩個對象。 因此那麼用"=="比較是 false。可是由於他們「長得同樣」,因此equals方法比較是true。 咱們也能夠變相的理解爲:"=="是判斷是否爲同一個,而"equals"是判斷像不像。
包裝類概述
用於解決基本類型不能參與面向對象開發的問題。 包裝類能夠將基本類型以對象的形式存在,從而 就具備了面向對象的相關特性。 數字類型包裝類繼承自Number類,能夠在6中數字 類型之間轉換。
8個基本類型包裝類
Number及其主要方法
上一節咱們已經知道,除了Character與Boolean以外的其餘包裝類都是繼承自Number的,這些包裝類都有一個共性,描述的都是數字。 那麼咱們來了解一下他們的父類:java.lang.Number Number是一個抽象類。自己不能實例化。Number 的子類必須提供將表示的數值轉換爲 byte、double、float、int、long 和 short 的方法 好比: abstract double doubleValue() 以double形式返回指定的數值 abstract int intValue() 以int形式返回指定的數值 abstract float floatValue() 以float形式返回指定的數值 剩下的抽象方法請參閱API文檔:java.lang.Number 。
Integer經常使用功能
java.lang.Integer是int的包裝類,其每個實例用於描述一個基本類型int的值。 Integer有一個靜態方法static int parseInt(String s)。 該方法的做用是將一個描述整數的字符串解析爲該整數,並用int形式返回。該方法可能會拋出NumberFormatException異常: 當給定的字符串裏邊含有非整數字符時。
Double經常使用功能
java.lang.Double是double的包裝類,其每個實例用於描述一個基本類型double的值。 Double有一個靜態方法static double parseDouble(String s)。 該方法的做用是將一個描述小數的字符串解析爲該小數,並用double形式返回。 該方法可能會拋出NumberFormatException異常: 若是字符串不包含可解析的 double 值。
自動裝箱和拆箱操做
java 5.0以後推出的一個新的特性
public static void main(String[] args) { /* * 自動拆裝箱不是JVM承認的,而是編譯器 * 承認的。 * * 當編譯器在編譯下面代碼時,會自動添加 * 代碼將基本類型轉換爲引用類型,因此在 * 編譯後的class文件中,下面代碼的樣子 * 是: * Integer i = Integer.valueOf(1); */ Integer i = 1; /* * 一樣的,下面代碼在編譯後的class文件 * 中的樣子: * int ii = i.intValue(); */ int ii = i; }
Date及其經常使用API
JAVA 中的時間
Date類簡介
java.util.Date 類封裝日期及時間信息。
Date類的大多數用於進行時間份量計算的方法已經被Calendar取代。
由於Date的設計具備"千年蟲"以及"時區"的問題,因此Date中的大部分方法已經不建議使用了,它們都被java.util.Calendar類所取代
setTime與getTime方法
void setTime(long time):
該方法用於爲一個Date對象設置其須要表示的時間,該參數爲一個long值,其含義是須要表示的這個時間點距離1970年1月1日 00:00:00之間的毫秒差。
long getTime()
該方法用於獲取一個Date對象所表示的時間點,該返回值爲一個long值,表示該時間點距離1970年1月1日 00:00:00之間的毫秒差。
Date 重寫 toString方法
Date重寫了toString()方法,用一個字符串來描述當前Date對象所表示的時間。 格式以下:
Mon Feb 17 15:36:55 CST 2014
由此咱們能夠看出,實際上Date的toString()方法返回的字符串雖然很清晰的描述了時間,可是對於非英語地區來說,該字符串不夠友好,咱們更但願按照特定地區表示時間的方式。好比咱們更習慣如下的風格:
2014-02-17 15:36:55 星期一
那麼有沒有方式能夠代替 Date的toString()方法來獲取一個特定格式的字符串呢?答案是確定的,java爲咱們提供了一個類,叫作SimpleDateFormat,該類就能夠完成。
SimpleDateFormat
SimpleDateFormat簡介
SimpleDateFormat 是一個以與語言環境有關的方式來格式化和解析日期的具體類。它容許進行格式化(日期 -> 文本)、解析(文本 -> 日期)和規範化。
簡單的說,SimpleDateFormat就是根據一個特定的日期格式在字符串與Date之間相互轉換。
日期模式匹配字符串
日期模式的匹配字符串如表所示。
例如: yyyy年MM月dd日--HH:mm:ss 能夠匹配 2014年01月06日--13:22:41
將Date格式化爲String
將Date格式化爲String,咱們須要使用SimpleDateFormat提供的方法:
String format(Date d)
例如:
Date now = new Date();//默認實例化的Date表示當前系統時間 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String str = sdf.format(now); System.out.println(str);//2014-01-06 13:21:12
將String解析爲Date
將String格式化爲Date,咱們須要使用SimpleDateFormat提供的方法:
Date parse(String s)
例如:
String str = "2008年08月08日 12:22:46"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); Date date = sdf.parse(str); System.out.println(date);// Fri Aug 08 12:22:46 CST 2008
Calendar類
Calendar簡介
getInstance()方法
設置日期及時間份量
Calendar提供了一種通用的設置時間的方式:
void set(int field,int value)
該方法能夠經過對不一樣的時間份量分別設置不一樣的值。Calendar對不一樣的時間份量提供了相應的常量,咱們在使用set方法設置時,第一個參數就應當使用對應的常量做爲時間份量。
Calendar calendar = Calendar.getInstance();//建立出的Calendar表示當前系統時間 //設置年爲2008年 calendar.set(Calendar.YEAR,2008); //設置月爲5月 calendar.set(Calendar.Month,4);//月份從0開始 calendar.set(Calendar.Month,Calendar.MAY);//也可使用常量來設置 //設置日爲30日 caneldar.set(Calendar.DAY_OF_MONTH,30);
獲取時間份量對應的值
Calendar提供了一種通用的獲取時間份量的方式:
int get(int field)
該方法能夠經過對不一樣的時間份量獲取相應的值。Calendar對不一樣的時間份量提供了相應的常量,咱們在使用get方法獲取時,參數就應當使用對應的常量做爲時間份量。
Calendar calendar = Calendar.getInstance(); int year = calendar.get(Calendar.YEAR); int month = calednar.get(Calendar.Month); int date = calendar.get(Calendar.DAY_OF_MONTH); //須要注意,月份要加1,由於月份是從0開始的 System.out.println(year+"-"+(month+1)+"-"+date);//2014-1-4
getActualMaximum方法
int getActualMaximum(int field)方法用於獲取給定時間份量所容許的最大值
例如:
獲取當前系統時間中當月的最後一天(日所容許的最大值)
Calendar calendar = Calendar.getInstance(); int max = calendar. getActualMaximum(Calendar.DAY_OF_MONTH); System.out.println("當前月的最後一天爲:"+max+"日");//當前月的最後一天爲31日
add方法
Calendar還容許咱們使用統一的方式來對某個時間份量的值進行計算。咱們須要使用方法 void add(int field,int amount)。該方法能夠爲某個時間份量的值加上給定的值,若想減去給定的值,那麼傳入的值須要是負數便可。而且計算後會自動進行相應的進位,例如若當前爲月底,那麼加一天後,爲下個月的月初,而月就會進位。
例如: 當前系統時間爲 2014-01-31日
Calendar calendar = Calendar.getInstance(); //計算明天(在日的基礎上加一天) calendar.add(Calendar.DAY_OF_YEAR,1);//當前Calendar表示的爲2014-02-01,月進位了
setTime與getTime方法
Calendar的void setTime(Date date),容許咱們爲Calendar設置Date對象所表示的時間。
Calendar的 Date getTime(),容許咱們獲取一個使用Date對象描述的Calendar所表示的時間。
例如:
Calendar calendar = Calendar.getInstance(); Date date = calendar.getTime(); System.out.println(date);// Mon Feb 17 15:36:55 CST 2014
Calendar與Date之間的相互轉換
/* * 默認建立出來的是陽曆曆法: * GregorianCalendar * * 默認就表示當前系統時間 */ Calendar calendar = Calendar.getInstance(); /* * Calendar的toString不能直觀反映 * 表示的具體日期 */ System.out.println(calendar); /* * * Calendar提供了與Date之間互轉的相關 * 方法: * Calendar->Date * * Date getTime()方法 * 該方法會返回一個Date對象,該對象表示 * 的時間就是當前Calendar表示的時間。 */ Date date = calendar.getTime(); System.out.println(date); /* * Date -> Calendar * void setTime(Date date) * 該方法容許當前Calendar表示給定的 * Date所表示的時間 */ calendar.setTime(date);
Collection
java提供了一種能夠存數一組數據的數據結構,其提供了豐富的方法,在實際開發中每每比數組使用的普遍。這種數據結構成爲集合:Collection。 Collection是一個接口,其定義了集合的相關功能方法。
List和Set
Collection派生出了兩個子接口,一個是List另外一個則是Set。 List:稱爲可重複集,顧名思義,該集合中是容許存放重複元素的,那麼何爲重複元素? 重複元素指的並不是是同一個元素,而是指equals方法比較爲true的元素。 Set:稱爲不可重複集,因此,該集合中是不能將相同的元素存入集合兩次,同List, 這裏相同指的也是兩個元素的equals比較結果爲true。
集合持有對象的引用
集合中存儲的都是引用類型的元素,那麼引用類型變量實際上存儲的是對象的「地址」,因此實際上集合只存儲了元素對象在堆中的地址。而並非將對象自己存入了集合中。
add()方法
Collection定義了一個add方法用於向集合中添加新元素。
contains方法
該方法會用於判斷給定的元素是否被包含在集合中。若包含則返回true,不然返回false。
這裏須要注意的是,集合在判斷元素是否被包含在集合中是使用元素的equals的比較結果。
(o==null ? e==null : o.equals(e)) 其中e是集合中的元素。
size,clear,isEmpty方法
size方法用於獲取當前集合中的元素總數。該方法定義爲:int size()
clear方法用於清空集合。該方法定義爲:void clear()
isEmpty方法用於判斷當前集合中是否不 包含元素。該方法定義爲:boolean isEmpty()
addAll與containsAll方法
addAll方法用於將給定集合中的全部元素添加到當前集合中
containsAll方法用於判斷當前集合是否包含給定集合中的全部元素,若包含則返回true。
Iterator 迭代器
Collection提供了一個遍歷集合的通用方式,迭代器(Iterator)。 獲取迭代器的方式是使用Collection定義的方法: Iterator iterator() 迭代器Iterator是一個接口,集合在覆蓋Collection的iterator()方法時提供了迭代器的實現。 Iterator提供了統一的遍歷集合元素的方式。
hasNext與next方法
迭代器用於遍歷集合的兩個主要方法: boolean hasNext():判斷集合是否還有元素能夠遍歷。 E next():返回迭代的下一個元素 遍歷集合應遵循「先問後取」的方式,也就是說,應當在肯定hasNext()方法的返回值爲true的狀況下再經過next()方法取元素。 由此能夠看出,使用迭代器遍歷集合是經過boolean值驅動的,因此它更適合使用while循環來遍歷。 例如: Collection<String> c = new HashSet<String>(); c.add("java"); c.add("cpp"); c.add("php"); c.add("c#"); c.add("objective-c"); Iterator<String> it = c.iterator(); while (it.hasNext()) { String str = it.next(); System.out.println(str); }
remove方法
Collection c = new ArrayList(); c.add("one"); c.add("#"); c.add("two"); c.add("#"); c.add("three"); c.add("#"); c.add("four"); /* * Iterator iterator() * 該方法會返回一個Iterator的實現類:迭代器 * 集合遍歷元素使用的就是該統一的方式。 * 迭代器遍歷集合遵循: * 問,取,刪。 * 其中刪除元素操做不是必須的。 */ Iterator it = c.iterator(); /* * boolean hasNext() * 經過迭代器詢問集合是否還有元素 * 能夠取出。 */ while(it.hasNext()){ /* * E next() * 從集合中取出下一個元素 */ String str = (String)it.next(); System.out.println(str); //從集合中刪除"#" if("#".equals(str)){ /* * 在使用迭代器遍歷集合的過程當中 * 不能經過集合的方法修改集合元素 * 數量,不然迭代器可能會拋出異常。 */ // c.remove(str); /* * void remove() * 迭代器提供的remove方法用於 * 從集合中刪除經過next()方法 * 取出來的元素。 */ it.remove(); } }
加強for循環
Java5.0以後推出了一個新的特性,加強for循環,也稱爲新循環。該循環不通用於傳統循環的工做,其只用於便利集合或數組。 語法:
for(元素類型 e : 集合或數組){ 循環體 }
新循環並不是新的語法,而是在編譯過程當中,編譯器會將新循環轉換爲迭代器模式。因此新循環本質上是迭代器。 例如:
Collection<String> c = new HashSet<String>(); c.add("java"); c.add("cpp"); c.add("php"); c.add("c#"); c.add("objective-c"); for (String str : c) { System.out.print(str.toUpperCase() + " "); } // CPP PHP C# JAVA OBJECTIVE-C
泛型機制
泛型在集合中的應用
泛型是Java SE 5.0引入的特性,泛型的本質是參數化類型。在類、接口和方法的定義過程當中,所操做的數據類型被傳入的參數指定。 Java泛型機制普遍的應用在集合框架中。全部的集合類型都帶有泛型參數,這樣在建立集合時能夠指定放入集合中的對象類型。Java編譯器能夠據此進行類型檢查,這樣能夠減小代碼在運行時出現錯誤的可能性。 咱們來舉個例子,好比ArrayList,其在定義時是這樣的:
public class ArrayList<E> { … … … public boolean add(E e) {…}; public E get(int index) {…}; }
由此咱們能夠看出,再聲明ArrayList時,類名的右側有一個<E>。"<>"表示泛型,而其中可使用數字字母下劃線(數字不能的第一個字符)來表示泛型的名字。(一般咱們使用一個大寫字母來表示,固然這個不是規定。)這時,在類中聲明的方法的參數,返回值類型能夠被定義爲泛型。這樣在建立對象時能夠將類型做爲參數傳遞,此時,類定義全部的E將被替換成傳入的參數。 例如:
ArrayList<String> list = new ArrayList<String>();//泛型E在這裏被指定爲String類型 list.add("One");//那麼add方法的參數就被替換爲String類型 list.add(100);//這裏就會出現編譯錯誤,由於這裏的參數應爲String類型。
List
List接口是Collection的子接口,用於定義線性表數據結構;能夠將List理解爲存放對象的數組,只不過其元素個數能夠動態的增長或減小。而且List是可重複集
ArrayList和LinkedList
List接口的兩個常見實現類爲ArrayList和LinkedList,分別用動態數組和鏈表的方式實現了List接口。
能夠認爲ArrayList和LinkedList的方法在邏輯上徹底同樣,只是在性能上有必定的差異,ArrayList更適合於隨機訪問而LinkedList更適合於插入和刪除;在性能要求不是特別苛刻的情形下能夠忽略這個差異。
get與set方法
List除了繼承Collection定義的方法外,還根據其線性表的數據結構定義了一系列方法,其中最經常使用的就是基於下標的get和set方法。
E get(int index):獲取集合中指定下標對應的元素,下標從0開始。
E set(int index, E elment):將給定的元素存入給定位置,並將原位置的元素返回。
例如:
List<String> list = new ArrayList<String>(); list.add("java"); list.add("cpp"); list.add("php"); list.add("c#"); list.add("objective-c"); // get方法遍歷List for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } String value = list.set(1, "c++"); System.out.println(value); // cpp System.out.println(list); // [java, c++, php, c#, objective-c] // 交換位置1和3上的元素 list.set(1, list.set(3, list.get(1))); System.out.println(list); // [java, c#, php, c++, objective-c]
插入和刪除
List根據下標的操做還支持插入與刪除操做:
void add(int index,E element):
將給定的元素插入到指定位置,原位置及後續元素都順序向後移動。
E remove(int index):
刪除給定位置的元素,並將被刪除的元素返回。
例如:
List<String> list = new ArrayList<String>(); list.add("java"); list.add("c#"); System.out.println(list); // [java, c#] list.add(1, "cpp"); System.out.println(list); // [java, cpp, c#] list.remove(2); System.out.println(list); // [java, cpp]
subList方法
List的subList方法用於獲取子List。
須要注意的是,subList獲取的List與原List佔有相同的存儲空間,對子List的操做會影響的原List。
List<E> subList(int fromIndex, int toIndex);
fromIndex和toIndex是截取子List的首尾下標(前包括,後不包括) 。
例如:
List<Integer> list = new ArrayList<Integer>(); for (int i = 0; i < 10; i++) { list.add(i); } System.out.println(list); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] List<Integer> subList = list.subList(3, 8); System.out.println(subList); // [3, 4, 5, 6, 7] // subList得到的List和源List佔有相同的數據空間 for (int i = 0; i < subList.size(); i++) { subList.set(i, subList.get(i) * 10); } System.out.println(subList); // [30, 40, 50, 60, 70] System.out.println(list); // [0, 1, 2, 30, 40, 50, 60, 70, 8, 9] // 能夠用於刪除連續元素list.subList(3, 8).clear(); System.out.println(list);
List轉換爲數組
List的toArray方法用於將集合轉換爲數組。但實際上該方法是在Collection中定義的,因此全部的集合都具有這個功能。
Collection<String> c = new ArrayList<String>(); c.add("one"); c.add("two"); c.add("three"); c.add("four"); //Object[] array = c.toArray(); String[] array = c.toArray(new String[c.size()]); System.out.println(array.length); System.out.println(Arrays.toString(array));
數組轉換爲List
Arrays類中提供了一個靜態方法asList,使用該方法咱們能夠將一個數組轉換爲對應的List集合。 其方法定義爲:
static <T>List<T> asList<T… a>
返回的List的集合元素類型由傳入的數組的元素類型決定。
須要注意的是,返回的集合咱們不能對其增刪元素,不然會拋出異常。而且對集合的元素進行的修改會影響數組對應的元素。 例如:
String[] strArr = { "a", "b", "c" }; List<String> list = Arrays.asList(strArr); System.out.println(list); // [a, b, c] // list.add("d"); // 會拋出UnsupportedOperationException // java.util.Arrays$ArrayList System.out.println(list.getClass().getName()); List<String> list1 = new ArrayList<String>(); list1.addAll(Arrays.asList(strArr));
示例代碼:
String[] array = {"one","two","three","four"}; for(int i=0;i<array.length;i++){ System.out.println(array[i]); } List<String> list = Arrays.asList(array); System.out.println(list); list.set(0, "1"); System.out.println(list); //修改集合元素就是修改數組對應元素 for(int i=0;i<array.length;i++){ System.out.println(array[i]); } //數組轉換的集合不容許添加新元素 // list.add("five"); /* * 如有修改元素數量需求時,能夠自行建立 * 一個集合 */ List<String> list1 = new ArrayList<String>(list); // list1.addAll(list); list1.add("five"); System.out.println(list1); // Set<String> set // = new HashSet<String>(list1); // System.out.println(set);
List排序
Collections.sort方法實現排序
Collections是集合的工具類,它提供了不少便於咱們操做集合的方法,其中就有用於集合排序的sort方法。該方法的定義爲:
void sort(List<T> list)
其做用是對集合元素進行天然排序(按照元素的由小至大的順序) 例如:
List<Integer> list = new ArrayList<Integer>(); Random r = new Random(1); for (int i = 0; i < 10; i++) { list.add(r.nextInt(100)); } System.out.println(list); // [85, 88, 47, 13, 54, 4, 34, 6, 78, 48] Collections.sort(list); System.out.println(list); // [4, 6, 13, 34, 47, 48, 54, 78, 85, 88]
Comparable
經過上一節咱們知道了如何對集合元素進行天然排序,可是要想對元素進行天然排序那麼就必需要有一個必要條件,就是元素的大小。集合中存入的都是引用類型,是以對象的形式存在於內存中,那麼對象是如何進行的大小比較呢?實際上,若想對某個集合的元素進行天然排序,該集合的元素有一個要求,就是這些元素必須是Comparable的子類。
Comparable是一個接口,用於定義其子類是能夠比較的。由於該接口有一個抽象方法:
int compareTo(T t) 全部子類都須要重寫該方法來定義對象間的比較規則。該方法要求返回一個整數,這個整數不關心具體的值,而是關注取值範圍。 當返回值>0時,表示當前對象比參數給定的對象大。 當返回值<0時,表示當前對象比參數給定的對象小。 當返回值=0時,表示當前對象和參數給定的對象相等。 例如: Class Cell implements Comparable<Cell>{ int row; int col; public Cell(int row,int col){ this.row = row; this.col = col; } public int compareTo(Cell c){ //根據row比較大小 return this.row - c.row; } }
那麼Collections的sort在進行排序時就會根據集合中元素的compareTo方法的返回值來判斷大小從而進行天然排序。
// Cell實現了Comparable接口,CompareTo方法邏輯爲按照row值的大小排序 List<Cell> cells = new ArrayList<Cell>(); cells.add(new Cell(2, 3)); cells.add(new Cell(5, 1)); cells.add(new Cell(3, 2)); Collections.sort(cells); System.out.println(cells); // [(2,3), (3,2), (5,1)]
Comparator
一旦Java類實現了Comparable,其比較邏輯就已經肯定;若是但願在排序的操做中臨時指定比較規則,能夠採用Comparator接口回調的方式。
該接口要求實現類必須重寫其定義的方法:
int compare(T o1,T o2)
該方法的返回值要求,若o1>o2則返回值應>0,若o1<o2則返回值應<0,若o1==o2則返回值應爲0 例如:
List<Cell> cells = new ArrayList<Cell>(); cells.add(new Cell(2, 3)); cells.add(new Cell(5, 1)); cells.add(new Cell(3, 2)); // 按照col值的大小排序 Collections.sort(cells, new Comparator<Cell>() { @Override public int compare(Cell o1, Cell o2) { return o1.col - o2.col;} }); System.out.println(cells); // [(5,1), (3,2), (2,3)]
隊列和棧
Queue
隊列(Queue)是經常使用的數據結構,能夠將隊列當作特殊的線性表,隊列限制了對線性表的訪問方式:只能從線性表的一端添加(offer)元素,從另外一端取出(poll)元素。
隊列遵循先進先出(FIFO First Input First Output )的原則。
JDK中提供了Queue接口,同時使得LinkedList實現了該接口(選擇LinkedList實現Queue的緣由在於Queue常常要進行插入和刪除的操做,而LinkedList在這方面效率較高)。
Queue提供了操做隊列的相關方法,其主要方法以下: boolean offer(E e):將元素追加到隊列末尾,若添加成功則返回true。 E poll():從隊首刪除並返回該元素。 E peek():返回隊首元素,可是不刪除。
例如:
Queue<String> queue = new LinkedList<String>(); queue.offer("a"); queue.offer("b"); queue.offer("c"); System.out.println(queue); // [a, b, c] String str = queue.peek(); System.out.println(str); // a while (queue.size() > 0) { str = queue.poll(); System.out.print(str + " "); // a b c }
Deque
Deque是Queue的子接口,定義了所謂「雙端隊列」即從隊列的兩端分別能夠入隊(offer)和出隊(poll),LinkedList實現了該接口。
若是將Deque限制爲只能從一端入隊和出隊,則可實現「棧」(Stack)的數據結構,對於棧而言,入棧稱之爲push,出棧稱之爲pop。
棧遵循先進後出(FILO First Input Last Output )的原則。
Deque提供了操做棧的相關方法,其主要方法以下: void push(E e):將給定元素"壓入"棧中。存入的元素會在棧首。即:棧的第一個元素 E pop():將棧首元素刪除並返回。
例如:
Deque<String> stack = new LinkedList<String>(); stack.push("a"); stack.push("b"); stack.push("c"); System.out.println(stack); // [c, b, a] String str = stack.peek(); System.out.println(str); // c while (stack.size() > 0) { str = stack.pop(); System.out.print(str + " "); // c b a }
Map接口
Map 接口
java提供了一組能夠以鍵值對(key-value)的形式存儲數據的數據結構,這種數據結構成爲Map。 咱們能夠把Map當作一個多行兩列的表格,其中第一列存放key,第二列存放value。 而每一行就至關於一組key-value對,表示一組數據。 Map對存入的元素有一個要求,就是key不能重複,所謂不能重複指的是在Map中不能包含兩個equals爲true的key。 Map對於key,value的類型沒有嚴格要求,只要是引用類型都可。可是爲了保證在使用時不會形成數據混亂,一般咱們會使用泛型去約束key與value的類型。
put方法
既然咱們知道了Map在保存數據時其實是存入了兩部分信息的 ,key與value。那麼咱們來看看如何向Map中存入數據。
Map提供了一個方法:
V put(K k,V v)
該方法的做用是將key-value對存入Map中,由於Map中不容許出現重複的key,因此若當次存入的key已經在Map中存在,則是替換value操做,而返回值則爲被替換的元素。若此key不存在,那麼返回值爲null。
get方法
咱們學會了如何向Map中存入數據,那麼咱們再來看看如何獲取數據。Map中獲取數據的方式是給定Key獲取對應的Value。
Map提供了一個方法:
V get(Object key)
該方法的做用就是根據給定的key去查找Map中對應的value並返回,若當前Map中不包含給定的key,那麼返回值爲null。
containsKey方法
Map中的containsKey方法用於檢測當前Map中是否包含給定的key。其方法定義以下:
boolean containsKey(Object key)
若當前Map中包含給定的key(這裏檢查是否包含是根據key的equals比較結果爲依據的。)則返回true。
HashMap
hash表原理
HashMap是Map的一個經常使用的子類實現。其實使用散列算法實現的。 HashMap內部維護着一個散列數組(就是一個存放元素的數組),咱們稱其爲散列桶, 而當咱們向HashMap中存入一組鍵值對時,HashMap首先獲取key這個對象的hashcode()方法的返回值, 而後使用該值進行一個散列算法,得出一個數字,這個數字就是這組鍵值對要存入散列數組中的下標位置。 那麼得知了下標位置後,HashMap還會查看散列數組當前位置是否包含該元素。(這裏要注意的是,散列數組中 每一個元素並不是是直接存儲鍵值對的,而是存入了一個鏈表,這個鏈表中的每一個節點纔是真實保存這組鍵值對 的。)檢查是否包含該元素時根據當前要存入的key在當前散列數組對應位置中的鏈表裏是否已經包含這個key, 若不包含則將這組鍵值對存入鏈表,不然就替換value。 那麼在獲取元素時,HashMap一樣先根據key的hashcode值進行散列算法,找到它在散列數組中的位置, 而後遍歷該位置的鏈表,找到該key所對應的value以後返回。 看到這裏可能有個疑問,鏈表中應該只能存入一個元素,那麼HashMap是如何將key-value存入鏈表的 某個節點的呢?實際上,HashMap會將每組鍵值對封裝爲一個Entry的實例,而後將該實例存入鏈表。
hashcode方法
HashMap的存取是依賴於key的hashcode方法的返回值的,而hashcode方法其實是在Object中定義的。其定義以下:
int hashCode() 重寫一個類的hashcode()方法有如下注意事項: 一、若一個類重寫了equals方法,那麼就應當重寫hashcode()方法。 二、若兩個對象的equals方法比較爲true,那麼它們應當具備相同的hashcode值。 三、對於同一個對象而言,在內容沒有發生改變的狀況下,屢次調用hashCode()方法應當老是返回相同的值。 四、對於兩個對象equals比較爲false的,並不要求其hashcode值必定不一樣,可是應儘可能保證不一樣,這樣能夠提升散列表性能。
裝載因子及HashMap優化
在散列表中有一下名詞須要瞭解: Capacity:容量, hash表裏bucket(桶)的數量, 也就是散列數組大小. Initial capacity:初始容量, 建立hash表的時 初始bucket的數量, 默認構建容量是16. 也可使用特定容量. Size : 大小, 當前散列表中存儲數據的數量. Load factor:加載因子, 默認值0.75(就是75%), 向散列表增長數據時若是 size/capacity 的值大於Load factor則發生擴容而且從新散列(rehash). 那麼當加載因子較小時候散列查找性能會提升, 同時也浪費了散列桶空間容量. 0.75是性能和空間相對平 衡結果. 在建立散列表時候指定合理容量, 從而能夠減小rehash提升性能。
有序Map
LinkedHashMap實現有序的Map
Map 接口的哈希表和鏈表實現,具備可預知的迭代順序。此實現與 HashMap 的不一樣之處在於, LinkedHashMap維護着一個雙向循環鏈表。此鏈表定義了迭代順序,該迭代順序一般就是將存放元素的順序。
須要注意的是,若是在Map中從新存入以有的key,那麼key的位置會不會發生改變,只是將value值替換。
示例代碼
Map中的API
示例代碼:
//Map能夠分別制定Key與Value的類型 Map<String,Integer> map = new HashMap<String,Integer>(); /* * V put(K k,V v) * 將指定的key與value存入Map中 * 因爲Map要求key不容許重複,因此 * 若使用已有的key存入一個value,則會 * 將該key原有對應的value值替換,並將 * 被替換的value返回。若使用新的key, * 則返回值爲NULL。 */ map.put("語文", 98); map.put("數學", 97); map.put("英語", 96); map.put("物理", 95); map.put("化學", 98); System.out.println(map); Integer num = map.put("語文", 99); System.out.println(map); System.out.println(num); /* * V get(K k) * 根據給定的key獲取對應的value * 若給定的key在Map中不存在,則 * 返回值爲null */ num = map.get("政治"); System.out.println(num); num = map.get("英語"); System.out.println(num); /* * V remove(K k) * 根據給定的key從Map中刪除對應的 * 這組鍵值對。而返回值則是該key對應 * 的value */ System.out.println("刪除英語..."); Integer old = map.remove("英語"); System.out.println(map); System.out.println(old);
示例代碼:
//Map能夠分別制定Key與Value的類型 Map<String,Integer> map = new HashMap<String,Integer>(); map.put("語文", 98); map.put("數學", 97); map.put("英語", 96); map.put("物理", 95); map.put("化學", 98); System.out.println(map); boolean containsKey = map.containsKey("語文"); System.out.println(containsKey);
遍歷Map
示例代碼:
/** * Map的遍歷 * 遍歷Map有三種方式: * 1:遍歷全部的key * 2:遍歷全部的key-value對 * 3:遍歷全部的value(相對而言不經常使用) * @author Administrator * */ public class MapDemo3 { public static void main(String[] args) { Map<String,Integer> map = new LinkedHashMap<String,Integer>(); map.put("語文", 98); map.put("數學", 97); map.put("英語", 96); map.put("物理", 95); map.put("化學", 98); /* * 遍歷全部的key * * Set<K> keySet() * 將當前Map中全部的key存入到一個Set * 集合中,並將該集合返回。 */ Set<String> keySet = map.keySet(); for(String key : keySet){ System.out.println("key:"+key); } /* * 遍歷每一組鍵值對 * Entry是Map的內部類,其每個實例用於 * 表示一組鍵值對。有key,value兩個主要 * 屬性組成。 * * Set<Entry<K,V>> entrySet() * */ Set<Entry<String,Integer>> entrySet = map.entrySet(); for(Entry<String,Integer> entry:entrySet){ String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key+":"+value); } /* * Collection<V> values() * 將當前Map中全部的value存入到一個集合中 * 而後返回該集合 */ Collection<Integer> values = map.values(); for(Integer value : values){ System.out.println("value:"+value); } } }
建立File對象
java.io.File用於表示文件(目錄),也就是說程序員能夠經過File類在程序中操做硬盤上的文件和目錄。 File類只用於表示文件(目錄)的信息(名稱、大小等),換句話說只能訪問文件或目錄的相關屬性,不能對文件的內容進行訪問。
File(pathname)
File提供了較多的構造方法來建立實例,其中之一就是:
File(String pathname)
經過將給定路徑名字符串轉換成抽象路徑名來建立一個新 File 實例
提示:抽象路徑應儘可能使用相對路徑,而且目錄的層級分隔符不要直接寫」/」或」\」,應使用File.separator這個常量表示,以免不一樣系統帶來的差別。
File(parent,child)
File的另外一個經常使用構造方法:
File(File parent,String child)
根據 parent 抽象路徑名和 child 路徑名字符串建立一個新 File 實例。
isFile() 方法
File的isFile方法用於判斷當前File對象表示的是否爲一個文件
boolean isFile()
該方法若返回true,這表示File表示的是一個文件。
File表示文件信息
length方法
File的length方法用於返回由此抽象路徑名錶示的文件的長度,其定義爲:
long length()
該方法返回的long值表示該文件所佔用的字節量。
exists方法
File的exists方法用於測試此抽象路徑名錶示的文件或目錄是否存在,其方法定義:
boolean exists()
若該File表示的文件或目錄存在則返回true,不然返回false。
createNewFile方法
File的createNewFile方法用於當且僅當不存在具備此抽象路徑名指定的名稱的文件時,原子地建立由此抽象路徑名指定的一個新的空文件。 其方法定義:
boolean createNewFile()
返回值:若是指定的文件不存在併成功地建立,則返回 true;若是指定的文件已經存在,則返回 false 。
delete方法
File的delete方法用於刪除此抽象路徑名錶示的文件或目錄。 其方法定義:
boolean delete()
返回值:當且僅當成功刪除文件或目錄時,返回 true;不然返回 false。
須要注意的是,若此File對象所表示的是一個目錄時,在刪除時須要保證此爲空目錄才能夠成功刪除(目錄中不能含有任何子項)。
isDirectory()
File的isDirectory方法用於判斷當前File對象表示的是否爲一個目錄
boolean isDirectory()
返回值:若File對象表示的是一個目錄,則返回true
File表示目錄信息
mkdir方法
File的mkdir方法用於建立此抽象路徑名指定的目錄。其方法定義:
boolean mkdir()
返回值:當且僅當已建立目錄時,返回 true;不然返回 false
mkdirs方法
File的mkdirs方法用於建立此抽象路徑名指定的目錄,包括全部必需但不存在的父目錄。注意,此操做失敗時也可能已經成功地建立了一部分必需的父目錄。其方法定義:
boolean mkdirs()
返回值:當且僅當已建立目錄以及全部必需的父目錄時,返回 true;不然返回 false
delete方法
前面咱們介紹了File的delete方法是用於刪除此抽象路徑名錶示的文件或目錄。在此強調,在刪除目錄時要特別注意:須要保證此爲空目錄才能夠成功刪除(目錄中不能含有任何子項)。
listFiles方法
File的listFiles方法用於返回一個抽象路徑名數組,這些路徑名錶示此抽象路徑名錶示的目錄中的文件。其方法定義:
File[] listFiles()
返回值:抽象路徑名數組,這些路徑名錶示此抽象路徑名錶示的目錄中的文件和目錄。若是目錄爲空,那麼數組也將爲空。若是抽象路徑名不表示一個目錄,或者發生 I/O 錯誤,則返回 null。
FileFilter接口
經過listFiles方法咱們能夠獲取一個目錄下的全部子項,但有些時候咱們並不但願獲取所有子項,而是想獲取部分知足咱們實際需求的子項時,咱們可使用File的重載方法:
File[] listFiles(FileFilter filter)
這裏咱們看到,該重載方法 要求咱們傳入一個參數,其類型是FileFilter。什麼是FileFilter呢? FileFilter是用於抽象路徑名的過濾器,說白了就是定義一個規律規則,那麼結合listFiles方法,咱們就能夠將知足此過濾規則的子項返回,其餘則忽略。
FileFilter是一個接口,因此當咱們須要定義某種過濾規則時,咱們能夠定義一個類來實現這個接口,而此接口的實例可傳遞給 File 類的 listFiles(FileFilter) 方法。
例如:
File[] list = dir.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.getName().startsWith("."); } });
該方法的參數FileFilter實例的accept方法並進行過濾,listFiles方法會將全部accept方法返回true的子項保留並返回。這個例子裏咱們會將dir中子項的名字以"."開頭的返回。
示例代碼
訪問File
使用File能夠:
1:訪問文件或目錄的屬性信息
2:操做文件或目錄(建立,刪除)
3:查看目錄中的子項
示例代碼:
File file = new File( "."+File.separator+"test.txt" ); //獲取文件名 String name = file.getName(); System.out.println("name:"+name); //文件大小(字節) long length = file.length(); System.out.println("length:"+length+"字節"); //最後修改時間 long lm = file.lastModified(); boolean cr = file.canRead(); boolean cw = file.canWrite(); boolean ih = file.isHidden();
使用File建立文件
/* * 在當前項目根目錄下建立文件demo.txt */ File file = new File("demo.txt"); /* * boolean exists() * 判斷當前File表示的文件或目錄是否真實 * 存在。 */ if(!file.exists()){ System.out.println("不存在!"); file.createNewFile(); System.out.println("建立完畢!"); }
使用File刪除現有文件
/* * 刪除項目根目錄下的demo.txt */ File file = new File("demo.txt"); if(file.exists()){ //刪除File表示的文件 file.delete(); System.out.println("刪除完畢!"); }
使用File建立一個目錄
/* * 在當前項目根目錄下建立一個叫demo的目錄 */ File dir = new File("demo"); if(!dir.exists()){ //建立目錄 dir.mkdir(); System.out.println("建立完畢!"); }
使用File建立一個多級目錄
/* * 在當前目錄下建立目錄:a/b/c/d/e/f */ File dir = new File( "a"+File.separator+ "b"+File.separator+ "c"+File.separator+ "d"+File.separator+ "e"+File.separator+ "f"); if(!dir.exists()){ /* * mkdirs會在建立當前目錄的同時將 * 全部不存在的父目錄所有自動建立 */ dir.mkdirs(); System.out.println("建立完畢!"); }
刪除一個目錄
File dir = new File("demo"); if(dir.exists()){ /* * 若當前目錄中含有子項,該目錄不能 * 被刪除。 */ dir.delete(); System.out.println("刪除完畢!"); }
使用File獲取其表示的目錄中的全部子項,一個目錄中的子項無非仍是文件或目錄
/* * 獲取當前目錄下的全部子項 */ File dir = new File("."); /* * File[] listFiles() * 將當前目錄中全部子項(若干File對象表示) * 存入一個數組後返回 */ File[] subs = dir.listFiles(); for(File sub : subs){ /* * boolena isFile() * 判斷當前File對象表示的是否爲一個文件 */ if(sub.isFile()){ System.out.print("文件:"); } if(sub.isDirectory()){ System.out.print("目錄:"); } System.out.println(sub.getName()); }
File的listFiles方法有一個重載,容許咱們指定一個文件過濾器,而後將File表示的目錄下,知足過濾器要求的子項獲取回來。
File dir = new File("."); /* * 獲取當前目錄下全部名字以"."開頭的子項 * * FileFilter是一個接口,有一個抽象方法: * accept,該方法的做用是定義過濾條件 */ FileFilter filter = new FileFilter(){ public boolean accept(File file) { String name = file.getName(); System.out.println("正在過濾:"+name); return name.startsWith("."); } }; File[] subs = dir.listFiles(filter); for(File sub : subs){ System.out.println(sub.getName()); }
將指定的File表示的文件或目錄刪除
public static void main(String[] args) { File dir = new File("a"); delete(dir); } /** * 刪除指定File表示的文件或目錄 * @param file * 遞歸 */ public static void delete(File file){ if(file.isDirectory()){ //將當前目錄下的全部子項先刪除 for(File sub : file.listFiles()){ delete(sub); } } file.delete(); }
建立對象
簡介
Java提供了一個能夠對文件隨機訪問的操做,訪問包括讀和寫操做。該類名爲RandomAccessFile。該類的讀寫是基於指針的操做。
只讀模式
RandomAccessFile在對文件進行隨機訪問操做時有兩個模式,分別爲只讀模式(只讀取文件數據),和讀寫模式(對文件數據進行讀寫)。
只讀模式:
在建立RandomAccessFile時,其提供的構造方法要求咱們傳入訪問模式:
RandomAccessFile(File file,String mode) RandomAccessFile(String filename,String mode)
其中構造方法的第一個參數是須要訪問的文件,而第二個參數則是訪問模式:
r」:字符串」r」表示對該文件的訪問是隻讀的。
讀寫模式
建立一個基於文件訪問的讀寫模式的RandomAccessFile咱們只須要在第二個參數中傳入」rw」便可。
RandomAccessFile raf = new RandomAccessFile(file,」rw」);
那麼這時在使用RandomAccessFile對該文件的訪問就是又可讀又可寫的。
字節數據讀寫操做
write(int d)方法
RandomAccessFile提供了一個能夠向文件中寫出字節的方法:
void write(int d)
該方法會根據當前指針所在位置處寫入一個字節,是將參數int的」低8位」寫出。
read()方法
RandomAccessFile提供了一個能夠從文件中讀取字節的方法:
int read()
該方法會從RandomAccessFile當前指針位置讀取一個byte(8位) 填充到int的低八位, 高24位爲0, 返回值範圍正數: 0~255, 若是返回-1表示讀取到了文件末尾EOF(EOF:End Of File)! 每次讀取後自動移動文件指針, 準備下次讀取。
write(byte[] d)方法
RandomAccessFile提供了一個能夠向文件中寫出一組字節的方法:
void write(byte[] d)
該方法會根據當前指針所在位置處連續寫出給定數組中的全部字節,與該方法類似的還有一個經常使用方法:
void write(byte[] d,int offset,int len)
該方法會根據當前指針所在位置處連續寫出給定數組中的部分字節,這個部分是從數組的offset處開始,連續len個字節。
read(byte[] d)方法
RandomAccessFile提供了一個能夠從文件中批量讀取字節的方法:
int read(byte[] b)
該方法會從文件中嘗試最多讀取給定數組的總長度的字節量,並從給定的字節數組第一個位置開始,將讀取到的字節順序存放至數組中,返回值爲實際讀取到的字節量 。
close方法
RandomAccessFile在對文件訪問的操做所有結束後,要調用close()方法來釋放與其關聯的全部系統資源。
void close()
例如:
RandomAccessFile raf = new RandomAccessFile(file,」rw」); …..//讀寫操做 raf.close();//訪問完畢後要關閉以釋放系統資源。
文件指針操做
getFilePointer方法
RandomAccessFile的讀寫操做都是基於指針的,也就是說老是在指針當前所指向的位置進行讀寫操做。
RandomAccessFile提供了一個能夠獲取當前指針位置的方法:
long getFilePointer()
RandomAccessFile在建立時默認指向文件開始(第一個字節),經過getFilePointer方法獲取指針位置時值是"0"。
例如:
RandomAccessFile raf = new RandomAccessFile(file,」rw」); System.out.println(raf.getFilePointer());//0 raf.write(‘A’);//寫出一個字節後,指針自動向後移動到下一個字節位置 System.out.println(raf.getFilePointer());//1 raf.writeInt(3); System.out.println(raf.getFilePointer());//5 raf.close();
seek方法
RandomAccessFile的提供了一個方法用於移動指針位置。
void seek(long pos)
使用該方法能夠移動指針到指定位置。
例如:
RandomAccessFile raf = new RandomAccessFile(file,」rw」); System.out.println(raf.getFilePointer());//0 raf.write(‘A’);//指針位置1 raf.writeInt(3);//指針位置5 raf.seek(0);//將指針移動到文件開始處(第一個字節的位置) System.out.println(raf.getFilePointer());//0 raf.close();
skipBytes方法
RandomAccessFile的提供了一個方法能夠嘗試跳過輸入的 n 個字節以丟棄跳過的字節,方法定義爲:
int skipBytes(int n)
該方法可能跳過一些較少數量的字節(可能包括零)。這可能由任意數量的條件引發;在跳過n個字節以前已到達文件的末尾只是其中的一種可能。此方法不拋出 EOFException。返回跳過的實際字節數。若是 n 爲負數,則不跳過任何字節。
示例代碼
對項目根目錄下的demo.dat文件進行讀寫操做
RandomAccessFile raf = new RandomAccessFile( "demo.dat","rw" ); //int i = 1; /* * void write(int i) * 向文件中寫出一個字節,寫出的是給定的 * int值對應的二進制中的"低八位" * vvvvvvvv * 00000000 00000000 00000000 00000001 * * 00000000 00000000 00000001 00000001 */ raf.write(257); // 文件中只有1 /* * 關閉釋放資源 */ raf.close();
讀取字節
RandomAccessFile raf = new RandomAccessFile( "demo.dat","r" ); /* * byte read() * 該方法會讀取一個字節,並將該字節轉換爲 * 一個int值保存。該int值只有"低八位"有效 * 若該int值表示的數字爲-1,則表示讀取到了 * 文件末尾。 * 00000000 00000000 00000000 11111111 */ int d = raf.read(); System.out.println(d); raf.close();
RandomAccessFile讀寫基本類型數據以及基於指針的讀寫操做原理
RandomAccessFile raf = new RandomAccessFile( "test.dat","rw" ); long pos = raf.getFilePointer(); System.out.println("pos:"+pos); //向文件中寫入int最大值 int max = Integer.MAX_VALUE; /* * vvvvvvvv * 01111111 11111111 11111111 11111111 * */ raf.write(max>>>24); raf.write(max>>>16); raf.write(max>>>8); raf.write(max); System.out.println("pos:"+raf.getFilePointer()); raf.writeInt(max); System.out.println("pos:"+raf.getFilePointer()); raf.writeLong(123L); System.out.println("pos:"+raf.getFilePointer()); raf.writeDouble(123.123); System.out.println("pos:"+raf.getFilePointer()); /* * void seek(long pos) * 移動指針到指定的位置 */ raf.seek(0); System.out.println("seek到0"); System.out.println("pos:"+raf.getFilePointer()); //EOF (end of file) int i = raf.readInt(); System.out.println(i); System.out.println("pos:"+raf.getFilePointer()); //讀double raf.seek(16); double d = raf.readDouble(); System.out.println(d); System.out.println("pos:"+raf.getFilePointer()); raf.close();
使用RAF複製文件
/* * 1:建立一個RAF讀取原文件 * 2:再建立一個RAF用於向目標文件中寫 * 3:循環從原文件中讀取每個字節,而後 * 將該字節的內容寫入到目標文件中,直到 * 讀取到原文件的末尾 */ //1 RandomAccessFile src = new RandomAccessFile( "music.mp3","r" ); //2 RandomAccessFile desc = new RandomAccessFile( "music_copy.mp3","rw" ); long start = System.currentTimeMillis(); //3 int d = -1; while((d=src.read())!=-1){ desc.write(d); } long end = System.currentTimeMillis(); System.out.println("複製完畢!耗時:"+(end-start)+"ms"); src.close(); desc.close();
基於緩存的文件複製
RandomAccessFile src = new RandomAccessFile( "music.mp3","r" ); RandomAccessFile desc = new RandomAccessFile( "music_copy2.mp3","rw" ); /* * int read(byte[] d) * 一次性讀取給定的字節數組長度的字節量 * 並存入給定的數組中,而返回值爲實際讀取 * 到得字節量 * 若讀取到了文件末尾則返回-1 */ int len = -1; byte[] buf = new byte[1024*10]; long start = System.currentTimeMillis(); while((len = src.read(buf))!=-1){ /* * void write(byte[] d) * 一次性將給定的字節數組中的全部字節 * 寫出去 * void write(byte[] d,int offset,int len) * 將給定數組中offset指定的位置處開始的連續 * len個字節寫出 * */ desc.write(buf,0,len); } long end = System.currentTimeMillis(); System.out.println("複製完畢!耗時:"+(end-start)+"毫秒"); src.close(); desc.close();
InputStream與OutputStream
輸入與輸出
咱們編寫的程序除了自身會定義一些數據信息外,常常還會引用外界的數據,或是將自身的數據發送到外界。好比,咱們編寫的程序想讀取一個文本文件,又或者咱們想將程序中的某些數據寫入到一個文件中。這時咱們就要使用輸入與輸出。
什麼是輸入:輸入是一個從外界進入到程序的方向,一般咱們須要「讀取」外界的數據時,使用輸入。因此輸入是用來讀取數據的。
什麼是輸出:輸出是一個從程序發送到外界的方向,一般咱們須要」寫出」數據到外界時,使用輸出。因此輸出是用來寫出數據的。
節點流與處理流
按照流是否直接與特定的地方 (如磁盤、內存、設備等) 相連,分爲節點流和處理流兩類。
處理流的構造方法老是要帶一個其餘的流對象作參數。一個流對象通過其餘流的屢次包裝,稱爲流的連接。
InputStream與OutputStream經常使用方法
InputStream是全部字節輸入流的父類,其定義了基礎的讀取方法,經常使用的方法以下:
int read()
讀取一個字節,以int形式返回,該int值的」低八位」有效,若返回值爲-1則表示EOF。
int read(byte[] d)
嘗試最多讀取給定數組的length個字節並存入該數組,返回值爲實際讀取到的字節量。
OutputStream是全部字節輸出流的父類,其定義了基礎的寫出方法,經常使用的方法以下:
void write(int d)
寫出一個字節,寫的是給定的int的」低八位」
void write(byte[] d)
將給定的字節數組中的全部字節所有寫出
文件流
建立FOS對象(重寫模式)
FileOutputStream是文件的字節輸出流,咱們使用該流能夠以字節爲單位將數據寫入文件。
構造方法:
FileOutputStream(File file)
建立一個向指定 File 對象表示的文件中寫入數據的文件輸出流。
例如:
FIle file = new File("demo.dat"); FileOutputStream fos = new FileOutputStream(file);
構造方法:
FileOutputStream(String filename):
建立一個向具備指定名稱的文件中寫入數據的輸出文 件流。
例如:
FileOutputStream fos = new FileOutputStream("demo.dat");
這裏須要注意,若指定的文件已經包含內容,那麼當使用FOS對其寫入數據時,會將該文件中原有數據所有清除。
建立FOS對象(追加模式)
經過上一節的構造方法建立的FOS對文件進行寫操做時會覆蓋文件中原有數據。若想在文件的原有數據以後追加新數據則須要如下構造方法建立FOS
構造方法:
FileOutputStream(File file,boolean append)
建立一個向指定 File 對象表示的文件中寫入數據的文件輸出流。
例如:
File file = new File("demo.dat"); FileOutputStream fos = new FileOutputStream(file,true);
構造方法:
FileOutputStream(String filename,boolean append):
建立一個向具備指定名稱的文件中寫入數據的輸出文 件流。
例如:
FileOutputStream fos = new FileOutputStream("demo.dat",true);
以上兩個構造方法中,第二個參數若爲true,那麼經過該FOS寫出的數據都是在文件末尾追加的。
建立FIS對象
FileInputStream是文件的字節輸入流,咱們使用該流能夠以字節爲單位讀取文件內容。
FileInputStream有兩個經常使用的構造方法:
FileInputStream(File file):
建立用於讀取給定的File對象所表示的文件FIS
例如:
File file = new File("demo.dat"); FileInputStream fis = new FileInputStream(file);//建立一個用於讀取demo.dat文件的輸入流
另外一個構造方法:
FileInputStream(String name):
建立用於讀取給定的文件系統中的路徑名name所指定的文件的FIS
例如
FileInputStream fis //建立一個用於讀取demo.dat文件的輸入流 = new FileInputStream("demo");
read()和write(int d)方法
FileInputStream繼承自InputStream,其提供了以字節爲單位讀取文件數據的方法read。
int read()
今後輸入流中讀取一個數據字節,若返回-1則表示EOF(End Of File)
FileOutputStream繼承自OutputStream,其提供了以字節爲單位向文件寫數據的方法write。
void write(int d)
將指定字節寫入此文件輸出流。,這裏只寫給定的int值的」低八位」
例如
FileOutputStream fos = new FileOutputStream("demo.dat"); fos.write('A');//這裏要注意,char佔用2個字節,但這裏只寫入了1個字節。
read(byte[] d)和write(byte[] d)方法
FileInputStream也支持批量讀取字節數據的方法:
int read(byte[] b)
今後輸入流中將最多 b.length 個字節的數據讀入一個 byte 數組中 。
FileOutputStream也支持批量寫出字節數據的方法:
void write(byte[] d)
將 b.length 個字節從指定 byte 數組寫入此文件輸出流中。
例如:
FileOutputStream fos = new FileOutputStream("demo.txt"); byte[] data = "HelloWorld".getBytes(); fos.write(data);//會將HelloWorld的全部字節寫入文件。
將指定 byte 數組中從偏移量 off 開始的 len 個字節寫入此文件輸出流的方法:
void write(byte[] d,int offset,int len)
例如:
FileOutputStream fos = new FileOutputStream("demo.txt"); byte[] data = "HelloWorld".getBytes(); fos.write(data,5,5);//只會將world這5個字節寫入文件。
緩衝流
BOS基本工做原理
與緩衝輸入流類似,在向硬件設備作寫出操做時,增大寫出次數無疑也會下降寫出效率,爲此咱們可使用緩衝輸出流來一次性批量寫出若干數據減小寫出次數來提升寫 出效率。
BufferedOutputStream緩衝輸出流內部也維護着一個緩衝區,每當咱們向該流寫數據時,都會先將數據存入緩衝區,當緩衝區已滿時,緩衝流會將數據一次性所有寫出。
BOS實現寫出緩衝
實現寫出緩衝:
public void testBos()throws Exception { FileOutputStream fos = new FileOutputStream("demo.dat"); //建立緩衝字節輸出流 BufferedOutputStream bos = new BufferedOutputStream(fos); //全部字節被存入緩衝區,等待一次性寫出 bos.write("helloworld".getBytes()); //關閉流以前,緩衝輸出流會將緩衝區內容一次性寫出 bos.close(); }
BOS的flush方法
使用緩衝輸出流能夠提升寫出效率,可是這也存在着一個問題,就是寫出數據缺少即時性。有時咱們須要在執行完某些寫出操做後,就但願將這些數據確實寫出,而非在緩衝區中保存直到緩衝區滿後才寫出。這時咱們可使用緩衝流的一個方法flush。
void flush()
清空緩衝區,將緩衝區中的數據強制寫出。
例如:
BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream("demo.dat") ); bos.write('a');//並無向磁盤寫出,而是寫入到了BOS的緩存中 bos.flush();//強制將緩存中的數據一次性寫出,這時‘a’纔會被寫入磁盤 bos.close();//實際上,close()方法在變比緩衝流前也調用了flush()
BIS基本工做原理
在讀取數據時若以字節爲單位讀取數據,會致使讀取次數過於頻繁從而大大的下降讀取效率。爲此咱們能夠經過提升一次讀取的字節數量減小讀寫次數來提升讀取的效率。
BufferedInputStream是緩衝字節輸入流。其內部維護着一個緩衝區(字節數組),使用該流在讀取一個字節時,該流會盡量多的一次性讀取若干字節並存入緩衝區,而後逐一的將字節返回,直到緩衝區中的數據被所有讀取完畢,會再次讀取若干字節從而反覆。這樣就減小了讀取的次數,從而提升了讀取效率。
BIS是一個處理流,該流爲咱們提供了緩衝功能。
BIS實現輸入緩衝
使用緩衝流來實現文件複製:
FileInputStream fis = new FileInputStream("java.zip"); BufferedInputStream bis = new BufferedInputStream(fis); FileOutputStream fos = new FileOutputStream("copy_java.zip"); BufferedOutputStream bos = new BufferedOutputStream(fos); int d = -1; while((d = bis.read())!=-1){ bos.write(d); } bis.close();//讀寫完畢後要關閉流,只須要關閉最外層的流便可 bos.close();
對象流
對象序列化概念
對象是存在於內存中的。有時候咱們須要將對象保存到硬盤上,又有時咱們須要將對象傳輸到另外一臺計算機上等等這樣的操做。這時咱們須要將對象轉換爲一個字節序列,而這個過程就稱爲對象序列化。相反,咱們有這樣一個字節序列須要將其轉換爲對應的對象,這個過程就稱爲對象的反序列化。
使用OOS實現對象序列化
ObjectOutputStream是用來對對象進行序列化的輸出流。
其實現對象序列化的方法爲:
void writeObject(Object o)
該方法能夠將給定的對象轉換爲一個字節序列後寫出。
例如:
Emp emp = new Emp("張三",12,"男"); FileOutputStream fos = new FileOutputStream("Emp.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(emp);//將emp對象序列化後寫入文件 oos.close();
使用OIS實現對象反序列化
ObjectInputStream是用來對對象進行反序列化的輸入流。
其實現對象反序列化的方法爲:
Object readObject()
該方法能夠從流中讀取字節並轉換爲對應的對象。
例如:
FileInputStream fis = new FileInputStream("Emp.obj"); ObjectInputStream ois = new ObjectInputStream(fis); Emp emp = (Emp)ois.readObject();//將Emp對象從文件中讀取並反序列 .... ois.close();
Serializable接口
ObjectOutputStream在對對象進行序列化時有一個要求,就是須要序列化的對象所屬的類必須實現Serializable接口。
實現該接口不須要重寫任何方法。其只是做爲可序列化的標誌。
一般實現該接口的類須要提供一個常量serialVersionUID,代表該類的版本。若不顯示的聲明,在對象序列化時也會根據當前類的各個方面計算該類的默認serialVersionUID,但不一樣平臺編譯器實現有所不一樣,因此若向跨平臺,都應顯示的聲明版本號。
若是聲明的類序列化存到硬盤上面,以後隨着需求的變化更改了類別的屬性(增長或減小或更名),那麼當反序列化時,就會出現InvalidClassException,這樣就會形成不兼容性的問題。 但當serialVersionUID相同時,它就會將不同的field以type的預設值反序列化,可避開不兼容性問題。
例如:
public class Emp implements Serializable{ private static final long serialVersionUID = 1L; private String name; private int age; private String gender; //getter and setter and other ... }
transient關鍵字
對象在序列化後獲得的字節序列每每比較大,有時咱們在對一個對象進行序列化時能夠忽略某些沒必要要的屬性,從而對序列化後獲得的字節序列」瘦身」。
關鍵字 transient
被該關鍵字修飾的屬性在序列化時其值將被忽略
例如:
public class Emp implements Serializable{ private static final long serialVersionUID = 1L; private String name; private transient int age;//該屬性在序列化時會被忽略 private String gender; //getter and setter and other ... }
Reader和Writer
字符流原理
Reader是全部字符輸入流的父類,而Writer是全部字符輸出流的父類。字符流是以字符(char)爲單位讀寫數據的。一次處理一個unicode。字符流都是高級流,其底層都是依靠字節流進行讀寫數據的,因此底層仍然是基於字節讀寫數據的。
經常使用方法
Reader的經常使用方法:
int read()
讀取一個字符,返回的int」值低16」位有效。
int read(char[] chs)
從該流中讀取一個字符數組length個字符並存入該數組,返回值爲實際讀取到的字符量。
Writer的經常使用方法:
void write(int c)
寫出一個字符,寫出給定int值」低16」位表示的字符。
void write(char[] chs)
將給定字符數組中全部字符寫出。
void write(String str)
將給定的字符串寫出
void write(char[] chs,int offset,int len):
將給定的字符數組中從offset處開始連續的len個字符寫出
轉換流
字符轉換流原理
InputStreamReader:字符輸入流, 使用該流能夠設置字符集,並按照指定的字符集從流中按照該編碼將字節數據轉換爲字符並讀取。
OutputStreamWriter:字符輸出流,使用該流能夠設置字符集,並按照指定的字符集將字符轉換爲對應字節後經過該流寫出。
指定字符編碼
InputStreamReader的構造方法容許咱們設置字符集:
InputStreamReader(InputStream in,String charsetName)
基於給定的字節輸入流以及字符編碼建立ISR
InputStreamReader(InputStream in)
該構造方法會根據系統默認字符集建立ISR
OutputStreamWriter:的構造方法:
OutputStreamWriter(OutputStream out,String charsetName)
基於給定的字節輸出流以及字符編碼建立OSW
OutputStreamWriter(OutputStream out)
該構造方法會根據系統默認字符集建立OSW
使用OutputStreamWriter
... public void testOutput() throws IOException{ FileOutputStream fos = new FileOutputStream("demo.txt"); OutputStreamWriter writer //這裏使用的字符編碼爲UTF-8 = new OutputStreamWriter(fos,"UTF-8"); String str = "你們好!";//UTF-8中文爲3個字節,英文符號佔1個字節 writer.write(str);//寫出後該文件大小應該爲10字節 writer.close(); } ...
使用InputStreamReader
... public void testInput() throws IOException{ FileInputStream fis = new FileInputStream("demo.txt"); /* * 這裏設置了字符編碼爲GBK * 以後再經過ISR讀取demo.txt文件時 * 就使用GBK編碼讀取字符了 */ InputStreamReader reader = new InputStreamReader(fis,"GBK"); int c = -1; while((c = reader.read()) != -1){ System.out.print((char)c); } reader.close(); } ...
PrintWriter
建立PrintWriter對象
PrintWriter是具備自動行刷新的緩衝該字符輸出流。其提供了比較豐富的構造方法:
PrintWriter(File file) PrintWriter(String fileName) PrintWriter(OutputStream out) PrintWriter(OutputStream out,boolean autoFlush) PrintWriter(Writer writer) PrintWriter(Writer writer,boolean autoFlush)
其中參數爲OutputStream與Writer的構造方法提供了一個能夠傳入boolean值參數,該參數用於表示PrintWriter是否具備自動行刷新。
PrintWriter的重載print和println方法
使用PrintWriter寫出字符串時咱們一般不使用Writer提供的write()相關方法,而是使用print和println等方法,PrintWriter提供了若干重載的print與println方法,其中println方法是在寫出數據後自動追加一個系統支持的換行符。
重載方法有:
void print(int i)//打印整數 void print(char c)//打印字符 void print(boolean b)//打印boolean值 void print(char[] c)//打印字符數組 void print(double d)//打印double值 void print(float f)//打印float值 void print(long l)//打印long值 void print(String str)//打印字符串
注:這些方法還有相似的println方法,參數與上面相同。
使用PW輸出字符數據
FileOutputStream fos = new FileOutputStream("demo.txt"); OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8"); //建立帶有自動行刷新的PW PrintWriter pw = new PrintWriter(osw,true); pw.println("你們好!"); pw.close();
BufferedReader
構建BufferedReader對象
BufferedReader是緩衝字符輸入流,其內部提供了緩衝區,能夠提升讀取效率。
BufferedReader的經常使用構造方法:
BufferedReader(Reader reader)
例如:
FileInputStream fis = new FileInputStream("demo.txt"); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr); ….
注:由於BufferedReader在構造實例時須要傳入一個字符流,因此當咱們想基於一個字節流進行讀取時,要先將字節流轉換爲字符流後方可建立緩衝字符輸入流BufferedReader。
使用BR讀取字符串
BufferedReader提供了一個能夠便於讀取一行字符串:
String readLine()
該方法連續讀取一行字符串,直到讀取到換行符爲止,返回的字符串中不包含該換行符。若EOF則該方法返回null。
例如:
FileInputStream fis = new FileInputStream("demo.txt"); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr); String str = null; while((str = br.readLine()) != null){ System.out.println(str); } br.close();
異常處理概述
使用返回值狀態標識異常
在JAVA語言出現之前,傳統的異常處理方式多采用返回值來標識程序出現的異常狀況,這種方式雖然爲程序員所熟悉,但卻有多個壞處。
首先,一個API能夠返回任意的返回值,而這些返回值自己並不能解釋該返回值是否表明一個異常狀況發生了和該異常的具體狀況,須要調用API的程序本身判斷並解釋返回值的含義。
其次,並無一種機制來保證異常狀況必定會獲得處理,調用程序能夠簡單的忽略該返回值,須要調用API的程序員記住去檢測返回值並處理異常狀況。這種方式還讓程序代碼變得冗長,尤爲是當進行IO操做等容易出現異常狀況的處理時,代碼的很大部分用於處理異常狀況的switch分支,程序代碼的可讀性變得不好。
異常處理機制
當程序中拋出一個異常後,程序從程序中致使異常的代碼處跳出,java虛擬機檢測尋找和try關鍵字匹配的處理該異常的catch塊,若是找到,將控制權交到catch塊中的代碼,而後繼續往下執行程序,try塊中發生異常的代碼不會被從新執行。若是沒有找處處理該異常的catch塊,在全部的finally塊代碼被執行和當前線程的所屬的ThreadGroup的uncaughtException方法被調用後,遇到異常的當前線程被停止。
異常的捕獲和處理
Throwable,Error和Exception
Java異常結構中定義有Throwable類,Exceotion和Error是其派生的兩個子類。其中Exception表示因爲網絡故障、文件損壞、設備錯誤、用戶輸入非法等狀況致使的異常,這類異常是能夠經過Java異常捕獲機制處理的。而Error表示Java運行時環境出現的錯誤,例如:JVM內存溢出等。
try-catch
try {...} 語句指定了一段代碼,該段代碼就是一次捕獲並處理例外的範圍。在執行過程當中,該段代碼可能會產生並拋出一種或幾種類型的異常對象,它後面的catch語句分別對這些異常作相應的處理。
若是沒有列外產生,全部的catch代碼段都被略過不執行
在catch語句塊中是對異常進行處理的代碼。catch中聲明的異常對( catch(SomeException e) )封裝了異常事件發生的信息,在catch語句塊中可使用這個對象的一些方法獲取這些信息
常見格式:
... try{ //可能出現異常的代碼片斷 }catch(Exception e){ //處理該異常的代碼片斷 } ...
多個catch
每一個try語句塊能夠伴隨一個或多個catch語句,用於處理可能產生的不一樣類型的異常 。catch捕獲的異常類型由上至下的捕獲異常類型的順序應是子類到父類的
例如
try{ … }catch(NullPointerException e){ //子類異常應在上面捕獲 … }catch(RuntimeException e){ //父類異常在下面捕獲 … }catch(Exception e){ //應養成最終捕獲Exception的習慣 … }
一般在書寫代碼的時候,咱們應當在最後一個catch中捕獲Exception,這樣能夠保證代碼不會由於出現一個未在catch中聲明的異常而致使捕獲失敗使得程序終止。
finally的做用
finally語句爲異常處理提供一個統一的出口,使得在控制流程轉到程序其它部分之前,可以對程序的狀態做統一管理。
不管try所指定的程序塊中是否拋出例外,finally所指定的代碼都要被執行,一般在finally語句中能夠進行資源的消除工做,如關閉打開的文件、刪除臨時文件等。
finally語句塊只能定義在try語句塊以後,或者最後一個catch語句塊以後,且只能定義一次。
throw關鍵字
當程序發生錯誤而沒法處理的時候,會拋出對應的異常對象,除此以外,在某些時刻,您可能會想要自行拋出異常,例如在異常處理結束後,再將異常拋出,讓下一層異常處理區塊來捕捉,若想要自行拋出異常,您可使用「throw」關鍵詞,並生成指定的異常對象。
例如:
throw new ArithmeticException();
throws關鍵字
程序中會聲明許多方法(Method),這些方法中可能會因某些錯誤而引起異常,但您不但願直接在這個方法中處理這些異常,而但願調用這個它的方法來統一處理,這時候您可使用「throws」關鍵詞來聲明這個方法將會拋出異常
例如:
public static void stringToDate(String str) throws ParseException{ …… }
重寫方法時的throws
當使用繼承時,在父類的某個方法上聲明瞭throws拋出某些異常,而在子類中重寫該方法時,咱們能夠作如下的操做:
1. 不處理異常(重寫方法時不聲明throws) 2. 可僅在throws中聲明父類中聲明的部分異常 3. 可在throws中聲明父類方法中拋出的異常的子類異常
可是不能作如下操做:
1. 重寫方法時在throws中聲明拋出額外的異常 2. 重寫方法時在throws中聲明父類方法中聲明的拋出異常的父類異常
示例代碼:
public class Father { public void dosome() throws IOException,AWTException{ } } public class Son extends Father{ //能夠再也不聲明拋出任何異常 // public void dosome(){ // // } //能夠僅拋出父類拋出的部分異常 // public void dosome()throws IOException{ // // } /* * 能夠拋出父類拋出異常的子類異常 */ // public void dosome() // throws FileNotFoundException{ // // } /* * 不能夠拋出與父類拋出異常沒有繼承關係的其餘異常 */ // public void dosome()throws SQLException{ // // } /* * 不能夠拋出父類拋出異常的父類異常 */ // public void dosome()throws Exception{ // // } }
Java異常API
RuntimeException
Java異常能夠分爲可檢測異常,非檢測異常
RuntimeException 類屬於非檢測異常,由於普通JVM操做引發的運行時異常隨時可能發生,此類異常通常是由特定操做引起。但這些操做在java應用程序中會頻繁出現。所以它們不受編譯器檢查與處理或聲明規則的限制 。
常見RuntimeException
IllegalArgumentException 拋出的異常代表向方法傳遞了一個不合法或不正確的參數 NullPointerException 當應用程序試圖在須要對象的地方使用 null 時,拋出該異常 ArrayIndexOutOfBoundsException 當使用的數組下標超出數組容許範圍時,拋出該異常 ClassCastException 當試圖將對象強制轉換爲不是實例的子類時,拋出該異常 NumberFormatException 當應用程序試圖將字符串轉換成一種數值類型,但該字符串不能轉換爲適當格式時,拋出該異常。
Exception經常使用API
printStackTrace
Throwable中定義了一個方法能夠輸出錯誤信息,用來跟蹤異常事件發生時執行堆棧的內容。該方法定義爲:
void printStackTrace()
例如:
try{ … }catch(Exception e){ e.printStackTrace();//輸出執行堆棧信息 }
getMessage
Throwable中定義了一個方法能夠獲得有關異常事件的信息。該方法定義爲:
String getMessage()
例如:
try{ … }catch(Exception e){ System.out.println(e.getMessage()); }
getCause
不少時候,當一個異常由另外一個異常致使異常而被拋出的時候,Java庫和開放源代碼會將一種異常包裝成另外一種異常。這時,日誌記錄和打印根異常就變得很是重要。Java異常類提供了 getCause()方法來檢索致使異常的緣由,這些能夠對異常根層次的緣由提供更多的信息。該Java實踐對代碼的調試或故障排除有很大的幫助。另外,若是要把一個異常包裝成另外一種異常,構造一個新異常就要傳遞源異常。
Throwable getCause()
獲取該異常出現的緣由。
自定義Exception
自定義異常的意義
Java異常機制能夠保證程序更安全和更健壯。雖然Java類庫已經提供不少能夠直接處理異常的類,可是有時候爲了更加精準地捕獲和處理異常以呈現更好的用戶體驗,須要開發者自定義異常。
繼承Exception自定義異常
建立自定義異常類,語法格式:
public class [自定義異常類名] extends Exception{ … }
如何編寫構造方法
當定義好自定義異常後,咱們能夠經過Eclipse來自動生成相應的構造方法。
具體步驟以下:
例如:
public class MyException extends Exception{ public MyException() { super(); // TODO Auto-generated constructor stub } public MyException(String message, Throwable cause) { super(message, cause); // TODO Auto-generated constructor stub } public MyException(String message) { super(message); // TODO Auto-generated constructor stub } public MyException(Throwable cause) { super(cause); // TODO Auto-generated constructor stub } }
進程和線程
什麼是進程
所謂進程(process)就是一塊包含了某些資源的內存區域。操做系統利用進程把它的工做劃分爲一些功能單元。進程中所包含的一個或多個執行單元稱爲線程(thread)。進程還擁有一個私有的虛擬地址空間,該空間僅能被它所包含的線程訪問。線程只能歸屬於一個進程而且它只能訪問該進程所擁有的資源。當操做系統建立一個進程後,該進程會自動申請一個名爲主線程或首要線程的線程。操做系統中有若干個線程在"同時"運行。一般,操做系統上運行的每個應用程序都運行在一個進程中,例如:QQ,IE等等。
注:進程並非真正意義上的同時運行,而是併發運行。後面咱們會具體說明。
什麼是線程
一個線程是進程的一個順序執行流。同類的多個線程共享一塊內存空間和一組系統資源,線程自己有一個供程序執行時的堆棧。線程在切換時負荷小,所以,線程也被稱爲輕負荷進程。一個進程中能夠包含多個線程。
注:切換——線程併發時的一種現象,後面講解併發時會具體說明。
進程與線程的區別
一個進程至少有一個線程。線程的劃分尺度小於進程,使得多線程程序的併發性高。另外,進程在執行過程當中擁有獨立的內存單元,而多個線程共享內存,從而極大地提升了程序的運行效率。
線程在執行過程當中與進程的區別在於每一個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。可是線程不可以獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。
從邏輯角度來看,多線程的意義在於一個應用程序中,有多個執行部分能夠同時執行。但操做系統並無將多個線程看作多個獨立的應用來實現進程的調度和管理以及資源分配。
線程使用的場合
線程一般用於在一個程序中須要同時完成多個任務的狀況。咱們能夠將每一個任務定義爲一個線程,使他們得以一同工做。
例如咱們在玩某個遊戲時,這個遊戲由操做系統運行,因此其運行在一個獨立的進程中,而在遊戲中咱們會聽到某些背景音樂,某個角色在移動,出現某些絢麗的動畫效果等,這些在遊戲中都是同時發生的,但實際上,播放音樂是在一個線程中獨立完成的,移動某個角色,播放某些特效也都是在獨立的線程中完成的。這些事情咱們沒法在單一線程中完成。
也能夠用於在單一線程中能夠完成,可是使用多線程能夠更快的狀況。好比下載文件。
好比迅雷,咱們嚐嚐會開到它會打開不少個節點來同時下載一個文件。
併發原理
經過上面幾節知識咱們知道進程與線程都是併發運行的,那麼什麼是併發呢?
多個線程或進程」同時」運行只是咱們感官上的一種表現。事實上進程和線程是併發運行的,OS的線程調度機制將時間劃分爲不少時間片斷(時間片),儘量均勻分配給正在運行的程序,獲取CPU時間片的線程或進程得以被執行,其餘則等待。而CPU則在這些進程或線程上來回切換運行。微觀上全部進程和線程是走走停停的,宏觀上都在運行,這種都運行的現象叫併發,可是不是絕對意義上的「同時發生。
注1:之因此這樣作是由於CPU只有一個,同一時間只能作一件事情。但隨着計算機的發展,出現了多核心CPU,例如兩核心的CPU能夠實現真正意義上的2線程同時運行,但由於CPU的時間片斷分配給那個進程或線程是由線程調度決定,因此不必定兩個線程是屬於同一個進程的,不管如何咱們只須要知道線程或進程是併發運行便可。
注2:OS—Operating System咱們稱爲:操做系統
注3:線程調度機制是OS提供的一個用於併發處理的程序。java虛擬機自身也提供了線程調度機制,用於減輕OS切換線程帶來的更多負擔。
線程狀態
對於程序而言,咱們實際上關心的是線程而非進程。經過上面學習的只是,咱們瞭解了什麼是線程以及併發的相關知識。那麼咱們來看看線程在其生命週期中的各個狀態:
New:當咱們建立一個線程時,該線程並無歸入線程調度,其處於一個new狀態。 Runnable:當調用線程的start方法後,該線程歸入線程調度的控制,其處於一個可運行狀態,等待分配時間片斷以併發運行。 Running:當該線程被分配到了時間片斷後其被CPU運行,這是該線程處於running狀態。 Blocked:當線程在運行過程當中可能會出現阻塞現象,好比等待用戶輸入信息等。但阻塞狀態不是百分百出現的,具體要看代碼中是否有相關需求。 Dead:當線程的任務所有運行完畢,或在運行過程當中拋出了一個未捕獲的異常,那麼線程結束,等待GC回收
建立線程
使用Thread建立線並啓動線程
java.lang.Thread類是線程類,其每個實例表示一個能夠併發運行的線程。咱們能夠經過繼承該類並重寫run方法來定義一個具體的線程。其中重寫run方法的目的是定義該線程要執行的邏輯。啓動線程時調用線程的start()方法而非直接調用run()方法。start()方法會將當前線程歸入線程調度,使當前線程能夠開始併發運行。當線程獲取時間片斷後會自動開始執行run方法中的邏輯。
例如:
public class TestThread extends Thread{ @Override public void run() { for(int i=0;i<100;i++){ System.out.println("我是線程"); } } }
建立和啓動線程:
… Thread thread = new TestThread();//實例化線程 thread.start();//啓動線程 …
當調用完start()方法後,run方法會很快執行起來。
使用Runnable建立並啓動線程
實現Runnable接口並重寫run方法來定義線程體,而後在建立線程的時候將Runnable的實例傳入並啓動線程。
這樣作的好處在於能夠將線程與線程要執行的任務分離開減小耦合,同時java是單繼承的,定義一個類實現Runnable接口這樣的作法能夠更好的去實現其餘父類或接口。由於接口是多繼承關係。
例如:
public class TestRunnable implements Runnable{ @Override public void run() { for(int i=0;i<100;i++){ System.out.println("我是線程"); } } }
啓動線程的方法:
… Runnable runnable = new TestRunnable(); Thread thread = new Thread(runnable);//實例化線程並傳入線程體 thread.start();//啓動線程 …
使用內部類建立線程
一般咱們能夠經過匿名內部類的方式建立線程,使用該方式能夠簡化編寫代碼的複雜度,當一個線程僅須要一個實例時咱們一般使用這種方式來建立。
例如:
繼承Thread方式:
Thread thread = new Thread(){ //匿名類方式建立線程 public void run(){ //線程體 } }; thread.start();//啓動線程
Runnable方式:
Runnable runnable = new Runnable(){ //匿名類方式建立線程 public void run(){ } }; Thread thread = new Thread(runnable); thread.start();//啓動線程
線程操做API
Thread.currentThread方法
Thread的靜態方法currentThread方法能夠用於獲取運行當前代碼片斷的線程。
Thread current = Thread.currentThread();
獲取線程信息
Thread提供了 獲取線程信息的相關方法:
long getId():返回該線程的標識符 String getName():返回該線程的名稱 int getPriority():返回線程的優先級 Thread.state getState():獲取線程的狀態 boolean isAlive():測試線程是否處於活動狀態 boolean isDaemon():測試線程是否爲守護線程 boolean isInterrupted():測試線程是否已經中斷
例如:
public class TestThread { public static void main(String[] args) { Thread current = Thread.currentThread(); long id = current.getId(); String name = current.getName(); int priority = current.getPriority(); Thread.State state = current.getState(); boolean isAlive = current.isAlive(); boolean isDaemon = current.isDaemon(); boolean isInterrupt = current.isInterrupted(); System.out.println("id:"+id); System.out.println("name:"+name); System.out.println("priority:"+priority); System.out.println("state:"+state); System.out.println("isAlive:"+isAlive); System.out.println("isDaemon:"+isDaemon); System.out.println("isInterrupt:"+isInterrupt); } }
線程優先級
線程的切換是由線程調度控制的,咱們沒法經過代碼來干涉,可是咱們能夠經過提升線程的優先級來最大程度的改善線程獲取時間片的概率。
線程的優先級被劃分爲10級,值分別爲1-10,其中1最低,10最高。線程提供了3個常量來表示最低,最高,以及默認優先級:
Thread.MIN_PRIORITY, Thread.MAX_PRIORITY, Thread.NORM_PRIORITY
設置優先級的方法爲:
void setPriority(int priority)
守護線程
守護線程與普通線程在表現上沒有什麼區別,咱們只須要經過Thread提供的方法來設定便可:
void setDaemon(boolean )
當參數爲true時該線程爲守護線程。
守護線程的特色是,當進程中只剩下守護線程時,全部守護線程強制終止。
GC就是運行在一個守護線程上的。
須要注意的是,設置線程爲後臺線程要在該線程啓動前設置。
Thread daemonThread = new Thread(); daemonThread.setDaemon(true); daemonThread.start();
示例代碼:
/* * rose:扮演者爲前臺線程 */ Thread rose = new Thread(){ public void run(){ for(int i=0;i<10;i++){ System.out.println( "rose:let me go!" ); try { Thread.sleep(1000); } catch (InterruptedException e) { } } System.out.println( "rose:啊啊啊啊AAAAAaaaaa....."); System.out.println("效果:噗通!"); } }; /* * jack:扮演者後臺線程 */ Thread jack = new Thread(){ public void run(){ while(true){ System.out.println( "jack:you jump!i jump!" ); try { Thread.sleep(1000); } catch (InterruptedException e) { } } } }; //設置後臺線程要在start方法調用前進行 jack.setDaemon(true); rose.start(); jack.start();
sleep方法
Thread的靜態方法sleep用於使當前線程進入阻塞狀態:
static void sleep(long ms)
該方法會使當前線程進入阻塞狀態指定毫秒,當指定毫秒阻塞後,當前線程會從新進入Runnable狀態,等待分配時間片。
該方法聲明拋出一個InterruptException。因此在使用該方法時須要捕獲這個異常。
例如:電子鐘程序
public static void main(String[] args) { SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss"); while(true){ System.out.println(sdf.format(new Date())); try { Thread.sleep(1000);//每輸出一次時間後阻塞1秒鐘 } catch (InterruptedException e) { e.printStackTrace(); } } }
注:改程序可能會出現"跳秒"現象,由於阻塞一秒後線程並不是是馬上回到running狀態,而是出於runnable狀態,等待獲取時間片。那麼這段等待時間就是"偏差"。因此以上程序並不是嚴格意義上的每隔一秒鐘執行一次輸出。
yield方法
Thread的靜態方法yield:
static void yield()
該方法用於使當前線程主動讓出當次CPU時間片回到Runnable狀態,等待分配時間片。
join方法
Thread的方法join:
void join()
該方法用於等待當前線程結束。此方法是一個阻塞方法。
該方法聲明拋出InterruptException。
例如:
public static void main(String[] args) { final Thread t1 = new Thread(){ public void run(){ //一些耗時的操做 } }; Thread t2 = new Thread(){ public void run(){ try { t1.join();//這裏t2線程會開始阻塞,直到t1線程的run方法執行完畢 } catch (InterruptedException e) { e.printStackTrace(); } //如下是當前線程的任務代碼,只有t1線程運行完畢纔會運行。 } }; }
示例代碼:
//用來標示圖片是否下載完畢的一個狀態 public static boolean isFinish; public static void main(String[] args) { final Thread download = new Thread(){ public void run(){ System.out.println("down:開始下載圖片..."); for(int i=1;i<=100;i++){ System.out.println("down:"+i+"%"); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("down:圖片下載完畢!"); isFinish = true; } }; Thread show = new Thread(){ public void run(){ System.out.println("show:開始顯示圖片!"); /* * 在這裏等待download將圖片下載完畢 */ try{ download.join(); }catch(InterruptedException e){ e.printStackTrace(); } if(!isFinish){ throw new RuntimeException("圖片沒有下載完畢!"); } System.out.println("show:顯示圖片完畢!"); } }; download.start(); show.start(); }
線程同步
synchronized關鍵字
多個線程併發讀寫同一個臨界資源時候會發生"線程併發安全問題「
常見的臨界資源: 多線程共享實例變量 多線程共享靜態公共變量
若想解決線程安全問題,須要將異步的操做變爲同步操做。 何爲同步?那麼咱們來對比看一下什麼是同步什麼異步。
所謂異步操做是指多線程併發的操做,至關於各幹各的。 所謂同步操做是指有前後順序的操做,至關於你幹完我再幹。
而java中有一個關鍵字名爲:synchronized,該關鍵字是同步鎖,用於將某段代碼變爲同步操做,從而解決線程併發安全問題。
鎖機制
Java提供了一種內置的鎖機制來支持原子性:
同步代碼塊(synchronized 關鍵字 ),同步代碼塊包含兩部分:一個做爲鎖的對象的引用,一個做爲由這個鎖保護的代碼塊。
synchronized (同步監視器—鎖對象引用){ //代碼塊 }
若方法全部代碼都須要同步也能夠給方法直接加鎖。
每一個Java對象均可以用作一個實現同步的鎖,線程進入同步代碼塊以前會自動得到鎖,而且在退出同步代碼塊時釋放鎖,並且不管是經過正常路徑退出鎖仍是經過拋異常退出都同樣,得到內置鎖的惟一途徑就是進入由這個鎖保護的同步代碼塊或方法。
選擇合適的鎖對象
使用synchroinzed須要對一個鎖對象上鎖以保證線程同步。
那麼這個鎖對象應當注意:多個須要同步的線程在訪問該同步塊時,看到的應該是同一個鎖對象引用。不然達不到同步效果。 一般咱們會使用this來做爲鎖對象。
選擇合適的鎖範圍
在使用同步塊時,應當儘可能在容許的狀況下減小同步範圍,以提升併發的執行效率。
靜態方法鎖
當咱們對一個靜態方法加鎖,如:
public synchronized static void xxx(){ …. }
那麼該方法鎖的對象是類對象。每一個類都有惟一的一個類對象。獲取類對象的方式:類名.class。
靜態方法與非靜態方法同時聲明瞭synchronized,他們之間是非互斥關係的。緣由在於,靜態方法鎖的是類對象而非靜態方法鎖的是當前方法所屬對象。
wait和notify
多線程之間須要協調工做。
例如,瀏覽器的一個顯示圖片的 displayThread想要執行顯示圖片的任務,必須等待下載線程downloadThread將該圖片下載完畢。若是圖片尚未下載完,displayThread能夠暫停,當downloadThread完成了任務後,再通知displayThread「圖片準備完畢,能夠顯示了」,這時,displayThread繼續執行。
以上邏輯簡單的說就是:若是條件不知足,則等待。當條件知足時,等待該條件的線程將被喚醒。在Java中,這個機制的實現依賴於wait/notify。等待機制與鎖機制是密切關聯的。
示例代碼:
public static Object obj = new Object(); //用來標示圖片是否下載完畢的一個狀態 public static boolean isFinish; public static void main(String[] args) { final Thread download = new Thread(){ public void run(){ System.out.println("down:開始下載圖片..."); for(int i=1;i<=100;i++){ System.out.println("down:"+i+"%"); try { Thread.sleep(50); } catch (InterruptedException e) { } } System.out.println("down:圖片下載完畢!"); isFinish = true; /* * 當圖片下載完畢,就能夠通知顯示線程開始 * 顯示圖片 */ synchronized (obj) { obj.notify(); } //繼續下載附件 System.out.println("down:開始下載附件..."); for(int i=1;i<=100;i++){ System.out.println("down:"+i+"%"); try { Thread.sleep(50); } catch (InterruptedException e) { } } System.out.println("down:附件下載完畢!"); } }; Thread show = new Thread(){ public void run(){ System.out.println("show:開始顯示圖片!"); /* * 在這裏等待download將圖片下載完畢 */ try{ // download.join(); synchronized (obj) { obj.wait(); } }catch(InterruptedException e){ } if(!isFinish){ throw new RuntimeException("圖片沒有下載完畢!"); } System.out.println("show:顯示圖片完畢!"); } }; download.start(); show.start(); }
線程安全API與非線程安全API
以前學習的API中就有設計爲線程安全與非線程安全的類:
StringBuffer 是同步的 synchronized append(); StringBuilder 不是同步的 append();
相對而言StringBuffer在處理上稍遜於StringBuilder,可是其是線程安全的。當不存在併發時首選應當使用StringBuilder。
一樣的:
Vector 和 Hashtable 是線程安全的而ArrayList 和 HashMap則不是線程安全的。
對於集合而言,Collections提供了幾個靜態方法,能夠將集合或Map轉換爲線程安全的:
例如:
Collections.synchronizedList() :獲取線程安全的List集合 Collections.synchronizedMap():獲取線程安全的Map ... List<String> list = new ArrayList<String>(); list.add("A"); list.add("B"); list.add("C"); list = Collections.synchronizedList(list);//將ArrayList轉換爲線程安全的集合 System.out.println(list);//[A,B,C] 能夠看出,原集合中的元素也得以保留 ...
使用ExecutorService實現線程池
當一個程序中若建立大量線程,並在任務結束後銷燬,會給系統帶來過分消耗資源,以及過分切換線程的危險,從而可能致使系統崩潰。爲此咱們應使用線程池來解決這個問題。
ExecutorService是java提供的用於管理線程池的類。
線程池有兩個主要做用: 控制線程數量 重用線程
線程池的概念:首先建立一些線程,它們的集合稱爲線程池,當服務器接受到一個客戶請求後,就從線程池中取出一個空閒的線程爲之服務,服務完後不關閉該線程,而是將該線程還回到線程池中。
在線程池的編程模式下,任務是提交給整個線程池,而不是直接交給某個線程,線程池在拿到任務後,它就在內部找有無空閒的線程,再把任務交給內部某個空閒的線程,任務是提交給整個線程池,一個線程同時只能執行一個任務,但能夠同時向一個線程池提交多個任務
線程池有如下幾種實現策略: Executors.newCachedThreadPool() 建立一個可根據須要建立新線程的線程池,可是在之前構造的線程可用時將重用它們。 Executors.newFixedThreadPool(int nThreads) 建立一個可重用固定線程集合的線程池,以共享的無界隊列方式來運行這些線程。 Executors.newScheduledThreadPool(int corePoolSize) 建立一個線程池,它可安排在給定延遲後運行命令或者按期地執行。 Executors.newSingleThreadExecutor() 建立一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。
能夠根據實際需求來使用某種線程池。例如,建立一個有固定線程數量的線程池:
... ExecutorService threadPool = Executors.newFixedThreadPool(30);//建立具備30個線程的線程池 Runnable r1 = new Runable(){ public void run(){ //線程體 } }; threadPool.execute(r1);//將任務交給線程池,其會分配空閒線程來運行這個任務。 ...
使用BlockingQueue
BlockingQueue是雙緩衝隊列。
在多線程併發時,若須要使用隊列,咱們可使用Queue,可是要解決一個問題就是同步,但同步操做會下降併發對Queue操做的效率。
BlockingQueue內部使用兩條隊列,可容許兩個線程同時向隊列一個作存儲,一個作取出操做。在保證併發安全的同時提升了隊列的存取效率。
雙緩衝隊列有一下幾種實現:
ArrayBlockingDeque:規定大小的BlockingDeque,其構造函數必須帶一個int參數來指明其大小.其所含 的對象是以FIFO(先入先出)順序排序的。 LinkedBlockingDeque:大小不定的BlockingDeque,若其構造函數帶一個規定大小的參數,生成的 BlockingDeque有大小限制,若不帶大小參數,所生成的BlockingDeque的大小Integer.MAX_VALUE 來決定.其所含的對象是以FIFO(先入先出)順序排序的。 PriorityBlockingDeque:相似於LinkedBlockDeque,但其所含對象的排序不是FIFO,而是依據對象的 天然排序順序或者是構造函數的Comparator決定的順序。 SynchronousQueue:特殊的BlockingQueue,對其的操做必須是放和取交替完成的。
例如:
public static void main(String[] args) { BlockingQueue<String> queue = new LinkedBlockingDeque<String>(); try { //queue.offer("A");//當即向隊列末尾追加元素 /* * 向隊列末尾追加元素,指定可延遲5秒。 * 若5秒鐘內成功將元素加入隊列返回true * 若超時後元素仍然沒有加入隊列則返回flase */ queue.offer("A",5,TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(queue.poll()); }