Java高級部分
異常
l什麼是異常(Exception)
Java程序在編譯或運行時發生的錯誤,叫做異常。
l爲什麼進行異常處理
程序一旦發生異常,會中斷指令正常的運行。爲了保證程序在發生異常時,能夠繼續向下執行,所以,引入了異常處理機制。
l異常的類型
JVM異常
Java標準異常類:JDK提供的異常類。
自定義異常
l異常的層次結構
–Throwable類:Throwable 類是 Java 語言中所有錯誤或異常的超類
•Error類
–用於定義不指望程序能從其恢復過來的災難性故障。Java中,對這種錯誤,不進行處理。
•Exception類:指一些可以被捕獲且可能恢復的異常情況。
–編譯時異常(checked)
»在編譯時,必須進行處理;如果不處理,則無法通過編譯。
»除了RuntimeException類及其子類,其他類都是編譯時異常類。
–運行時異常(unchecked)
»Java編譯器允許程序不對其進行處理,直接由運行時系統來處理。
»RuntimeException及其子類都是運行時異常類。
l常見的編譯時異常和運行時異常
–常見的編譯時異常:
•ClassNotFoundException
•FileNotFoundException
•IOException
•ParseException
•SQLException
•InterruptedException
–常見的運行時異常:
•ArrayIndexOutOfBoundsException
•StringIndexOutOfBoundsException
•NullPointerException
•ClassCastException
•ArithmeticException
•NumberFormatException
常見運行時異常案例: //rt1- ArrayIndexOutOfBoundsException int[] arrs = {1, 2, 3}; System.out.println(arrs[3]); //rt2- StringIndexOutOfBoundsException String s = "abc"; System.out.println(s.charAt(3)); //rt3: NullPointerException 引用類型聲明的變量值爲null的時候,如果調用該類型的屬性或方法,拋出該異常。 String str = "abc"; str = null; str.toString(); //rt4: ClassCastException 父子類轉換的時候出現。 Object obj = new Object(); String str2 = (String)obj; //rt5: ArithmeticException int c = 10 / 0; //rt6: NumberFormatException String sss = "10a"; int x = Integer.parseInt(sss); |
常見編譯時異常案例: //nrt1: ClassNotFoundException Class.forName("java.lang.String1"); //nrt2: FileNotFoundException new FileInputStream("d:/hao.txt"); //nrt3: IOException FileInputStream fis = new FileInputStream("d:/hao.txt"); fis.read(); //nrt4: ParseException SimpleDateFormat sdf = new SimpleDateFormat("yyyy"); sdf.parse("1990"); //nrt5: SQLException Connection con = DriverManager.getConnection("jack", "xxx", "yyy"); //nrt6: InterruptedException Thread.sleep(100); |
lJava異常的處理機制(兩種)
n第一種: try...catch...finally
try
{
//可能拋出異常的代碼。一般某行語句拋出異常,相當於創建了一 個異常類對象。
}
catch(異常類型 e)//一個try塊後,可以有多個catch代碼塊。
{
//一旦try代碼塊中拋出異常,就會自動創建一個相應類型的異常對象,此時,該異常對象如果是catch代碼塊中,參數的類型,或是其子類,則就被該catch代碼塊捕獲。執行該catch代碼塊中異常的處理內容。
}
catch(異常類型 e)//如果有多個catch,則catch後的參數類型必須由小到大(子類在前,父類在後)
{}
…
finally //finally代碼塊可有可無
{
//無論try代碼塊中有無異常,都會執行該代碼塊。一般用於收尾工作。比如IO對象的關閉,JDBC中對象的關閉操作。
}
try...catch...finally異常處理結構。運行順序的幾種可能。
1- try代碼塊中沒有拋出異常。
此種情況,try塊中的語句全部執行, catch不會執行,finally代碼塊語句執行,異常處理外的語句執行。
2- try代碼塊中拋出異常,且能被catch捕獲
此種情況,try拋出異常行前的語句執行,捕獲到異常的catch語句執行,finally語句執行,異常處理外的語句執行。
3- try代碼塊中拋出異常,catch代碼塊無法捕獲
此種情況,try拋出異常前的語句執行,finally語句執行。程序結束。
l異常處理各部分詳解(瞭解)
•try代碼塊
–捕獲異常的第一步是用try{…}選定捕獲異常的範圍,由try所限定的代碼塊中的語句在執行過程中可能會生成異常對象並拋出。
•catch代碼塊
–每個try代碼塊可以伴隨一個或多個catch語句,用於處理try代碼塊中所生成的異常對象。catch語句只需要一個形式參數指明它所能夠捕獲的異常類型,這個類必須是Throwable的子類,運行時系統通過參數值把被拋出的異常對象傳遞給catch塊。
–在catch塊中是對異常對象進行處理的代碼,與訪問其它對象一樣,可以訪問一個異常對象的變量或調用它的方法。getMessage()是類Throwable所提供的方法,用來得到有關異常事件的信息,類Throwable還提供了方法printStackTrace( )用來跟蹤異常事件發生時執行堆棧的內容
–捕獲異常的順序和不同catch語句的順序有關,當捕獲到一個異常時,剩下的catch語句就不再進行匹配。因此,在安排catch語句的順序時,首先應該捕獲最特殊的異常,然後再逐漸一般化,也就是一般先安排子類,再安排父類
•finally代碼塊
–捕獲異常的最後一步是通過finally語句爲異常處理提供一個統一的出口,使得在控制流轉到程序的其它部分以前,能夠對程序的狀態作統一的管理。不論在try代碼塊中是否發生了異常事件,finally塊中的語句都會被執行。
–不論try塊中的代碼是否拋出異常及異常是否被捕獲,finally子句中的代碼一定會被執行:
•如果try塊中沒有拋出任何異常,當try塊中的代碼執行結束後,finally中的代碼將會被執行;
•如果try塊中拋出了一個異常且該異常被catch正常捕獲,那麼try塊中自拋出異常的代碼之後的所有代碼將會被跳過,程序接着執行與拋出異常類型匹配的catch子句中的代碼,最後執行finally子句中的代碼。
•如果try塊中拋出了一個不能被任何catch子句捕獲(匹配)的異常,try塊中剩下的代碼將會被跳過,程序接着執行finally子句中的代碼,未被捕獲的異常對象繼續拋出,沿調用堆棧順序傳遞
ltry…catch…finally關鍵字的組合情況
–try….catch…..
–try….catch….finally….
–try….finally….
lfinal, finally, finalize區別?
–final—修飾符(關鍵字)如果一個類被聲明爲final,意味着它不能再派生出新的子類,不能作爲父類被繼承。因此一個類不能既被聲明爲abstract的,又被聲明爲final的。將變量或方法聲明爲final,可以保證它們在使用中不被改變。被聲明爲final的變量必須在聲明時給定初值,而在以後的引用中只能讀取,不可修改。被聲明爲final的方法也同樣只能使用,不能重寫。
–finally—再異常處理時提供 finally 塊來執行任何清除操作。如果拋出一個異常,那麼相匹配的 catch 子句就會執行,然後控制就會進入finally 塊(如果有的話)簡單來說就是finally後的代碼塊無論如何都會被執行除非程序退出(System.exit(0))。
–finalize—方法名。Java 技術允許使用 finalize() 方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。這個方法是由垃圾收集器在確定這個對象沒有被引用時對這個對象調用的。它是在Object 類中定義的,因此所有的類都繼承了它。子類覆蓋 finalize()方法以整理系統資源或者執行其他清理工作。finalize() 方法是在垃圾收集器刪除對象之前對這個對象調用的。
n第二種異常處理方式- throws聲明異常
–如果一個方法中有異常,且該方法不進行異常捕獲,則需要將該方法中的異常聲明出去,讓使用該方法的地方處理異常。
–throws聲明異常的方式,比異常捕獲消極,所以,將異常捕獲叫做積極的異常處理方式;把throws聲明異常叫做消極的異常處理方式。
–throws注意點:
•1-用在方法的後面,表示聲明異常;該方法有異常且交給使用該方法的地方處理。
•2-一個方法可以throws多個異常;多個異常之間由逗號分隔,且從小到大順序排列。
•3-如果方法中可能產生的異常是編譯時異常,則必須要throws聲明出去;如果方法中可能產生的異常是運行時異常,則可以不必處理,也可以聲明出去。
–在方法中,如果有異常,經常使用throws聲明異常。
lthrow關鍵字
•用於手動拋出異常。若某方法中需要直接拋出某異常時,可使用throw語句實現。
•首先要生成異常對象,且拋出的異常必須是Throwable或其子類的實例。
•如果一個方法可能拋出多個必檢異常,那麼必須在方法的聲明部分一一列出,多個異常間使用逗號進行分隔
•throws和throw關鍵字?
throws用於聲明異常,在方法的後面出現。
throw手動拋出異常,一般在方法體中使用。
l自定義異常
–Java語言中允許用戶定義自己的異常類,但自定義異常類必須是Throwable的直接子類或間接子類。實際使用中,自定義異常常繼承Exception或RuntimeException。
•如果一個自定義異常類爲Exception子類,則該自定義異常類爲編譯時異常類;如果自定義異常類爲RuntimeException的子類,則該自定義異常類爲運行時異常類。
–自定義的異常類,一般只要聲明兩個構造方法,一個是不用參數的,另一個以字符串爲參數。作爲構造方法參數的字符串應當反映異常的信息。
–用戶定義的異常同樣要用try--catch捕獲,但必須由用戶自己拋出 throw new MyException()。
l使用異常的注意點:
–1、對於運行時異常,如果不能預測它何時發生,程序可以不做處理,而是讓Java虛擬機去處理它;如果可以預知它可能發生的地點和時間,則應該在程序中進行處理,而不應簡單的把它交給運行時系統。
–2、在自定義異常類時,如果它所對應的異常事件通常總是在運行時產生的,而且不容易預測它將在何時、何處發生,則可以把它定義爲運行時異常,否則應定義爲非運行時異常。
–3、方法可以不對異常進行捕獲而直接將其拋出,並在方法聲明中說明,最好將它們留給方法的調用者進行處理,這樣會增加程序的靈活性
–4、異常對象的實例化和其後續處理工作是非常消耗資源的,過度的使用異常會明顯影響程序的執行速度。所以,在使用異常處理時應該仔細考慮,只對有必要的異常情況使用異常,而不可以將異常泛化。
集合
l常見數據結構
–線性表
•順序線性表- 數組(一塊連續的內存空間):查詢快,增刪插入慢
•鏈式線性表:查詢慢,插入、刪除快
–單向鏈表
–雙向鏈表
–棧:LIFO=last in first out
–隊列:FIFO=first in first out
l什麼是集合?
–就是一組存放對象的容器。當需要對多個對象統一管理時,就需要集合對象。
l數組與集合
–數組:
•查詢塊,增刪慢。
•相同數據類型,且定長。
•數組元素既可以爲基本數據類型,也可以爲引用類型。
–集合
•集合可以爲不同類型(一般爲相同類型,或自動轉換),且長度自增。
•集合中的元素只能爲引用類型或者說Object及其子類。集合中不能有基本數據類型的元素。
l集合層次結構
–Collection
•List:有序的,可重複的,允許有多個null
–ArrayList//Vector(不經常使用)
–LinkedList
•Set:無序,不重複的,只允許有一個null
–HashSet
–TreeSet
–Map
•TreeMap
•HashMap / Hashtable
•Properties
•對於有層級結構的類或接口的關係,查看API時,最好從子類開始查找,如果子類沒有,則依次向父類查找。
lCollection接口
–Collection 層次結構中的根接口。Collection 表示一組對象,這些對象也稱爲 collection 的元素。一些 collection 允許有重複的元素,而另一些則不允許。一些 collection 是有序的,而另一些則是無序的。JDK 不提供此接口的任何直接 實現:它提供更具體的子接口(如 Set 和 List)實現。此接口通常用來傳遞 collection,並在需要最大普遍性的地方操作這些collection。
lList接口
–是Collection接口的子接口,該List接口中的元素,是有序的,可重複的,可多個爲null;且可以通過索引對List接口中的元素進行訪問。
–List除了繼承Collection中的方法,自己新增了與索引相關的方法。
–List特有的方法listIterator()方法。
•在迭代過程中,不能使用迭代對象和集合對象同時操作集合中的元素。否則會報ConcurrentModificationException。
•ListIterator對象中除了可以在迭代過程中,移除集合中的元素,還可以對集合進行添加,設置。還可以從下往上遍歷循環集合中的元素。
•Iterator接口只能從上向下遍歷集合中的元素,且在迭代過程中,僅可以移除集合中的元素。
lList接口實現類
–ArrayList-可變數組
•底層由數組實現,查詢速度快,刪插入速度慢。
•ArrayList類是不同步的,如果需要同步,可以使用Collections.synchronizedList(ArrayList對象)方法,使其同步。
–Vector-矢量(瞭解)
•底層也是由數組實現
•Vector類使用與ArrayList基本相同,以後使用ArrayList
•Vector類是同步的類。
•Vector類擴展了含有element的各種方法。
–LinkedList-鏈表
•底層有雙向循環鏈表實現。查詢速度慢,刪插入速度快。
•LinkedList除了實現父接口中的方法,還擴展了自己的方法。比如在列表的開頭及結尾 get、remove 和insert 元素提供了統一的命名方法。
•此實現不是同步的,想要實現同步,可以使用List list = Collections.synchronizedList(new LinkedList(...));
–Stack類
•JDK中提供了Stack類,但因爲其是Vector類的子類,所以,不符合堆棧的特點。
•自定義Stack類,實現堆棧的功能。
•作業:
–使用LinkedList實現隊列。
注意: ArrayList類與Vector類異同(瞭解)
–相同
•他們都是Collection或List接口的具體實現類。
•他們都能存儲任意類型的對象。但通常情況下,這些不同的對象都具有相同的父類或父接口。
•他們都不能存儲基本數據類型(primitive);集合對象.add(1),可以添加成功,但內部相當於int->Integer->Object.
•他們底層都是由可變數組實現的。容量都可以自動擴充。
–不同
•Vector是同步的,線程安全的,效率較低;ArrayList是不同步的,線程不安全,但效率高。
•Vector缺省情況下,自動增長爲原來的一倍空間;ArrayList缺省情況下,自動增長爲原來的一半。
–ArrayList, Vector, LinkedList真實開發中,如何選擇使用?
–根據各自類的特點,按照實際需求,選擇。
lIterator和ListIterator接口
iterator()方法在Collection接口;
listIterator()方法在List接口中;
ListIterator接口中提供了比Iterator接口更多,更豐富的方法。
lIterator與Enumeration兩個接口
–Iterator迭代器取代了 Java Collections Framework 中的Enumeration。其功能相同。
–迭代器與枚舉有兩點不同:
•迭代器允許調用者利用定義良好的語義在迭代期間從迭代器所指向的 collection 移除元素。
•方法名稱得到了改進。
lSet接口
–沒有擴展自己的方法,與Collection中的方法完全一致。
–HashSet
•此類實現 Set 接口,由哈希表(實際上是一個 HashMap實例)支持。它不保證 set 的迭代順序;特別是它不保證該順序恆久不變。此類允許使用 null 元素。
•此實現不是同步的,想實現同步,需要使用Set s = Collections.synchronizedSet(new HashSet(...));
–TreeSet
•使用元素的自然順序對元素進行排序,或者根據創建set 時提供的 Comparator 進行排序。
lSet接口實現類
nHashSet類
–此類不是同步的。
–底層是由數組實現。且使用了哈希算法進行存儲。
–當一個對象存入HashSet集合對象中時,會先比較hashCode值,如果hashCode值不同,則直接存入集合;如果hashCode值相同,再使用equals方法比較對象,如果爲true則不添加;如果爲false,則添加到該集合對象中。也就是說,hashcode值相同,則equals未必相同,equals相同的兩個對象,hashcode值一定相同。
–原理:
•當將一個對象存放入HashSet集合對象中時,會根據hashCode%n,得到數組的下標位置。數組中,如果該位置已經有元素,則根據equals方法,比較兩個對象。如果返回爲true,則該對象不添加到數組中。如果返回爲false,則該數組位置上的元素以鏈表的形式存放,新加入的對象放在鏈表頭,最先加入的元素放在鏈表尾。如果數組中,該位置沒有元素,則直接將元素放到該位置。
public class MySet { private LinkedList[] lls = new LinkedList[10]; public MySet(){} public boolean add(Object obj) { int hashcode = obj.hashCode(); int index = hashcode % lls.length; //第一次向數組中存放元素。 if(lls[index] == null) { lls[index] = new LinkedList(); lls[index].addFirst(obj); return true; } else//從第二次賦值開始 { //用obj對象使用equals方法與lls[index]表示的集合對象中每個元素比較屬性值是否相同 if(lls[index].contains(obj)) { return false; } lls[index].addFirst(obj); return true; } } } |
在使用HashSet時,如果添加對象後,對對象的屬性值進行修改了,可能會造成內存泄漏。以後使用HashSet存放數據時,儘量不要修改其中對象的屬性值。 |
–如果自定義類的對象,存放入HashSet對象中,必須要實現hashCode()方法和equals方法。
nTreeSet類
–對集合中的元素,默認按照自然順序自動排列。
–TreeSet底層使用是二叉樹。
–對於TreeSet對象來說,要求存放入其元素的類型必須具有比較性。當類實現Comparable接口,並重寫compareTo方法,該類的對象具有了比較性。把這種形式叫做按照自然順序排序。
–TreeSet對象,通過compareTo對存放入其中的元素進行排序,且也是通過該方法比較兩個對象是否相同。如果相同,則不會添加到TreeSet對象中。爲了更精確的表示兩個對象相等,當某個屬性比較爲0時,必須判斷其他屬性是否相等,當所有屬性都相等時,兩個對象真正相同。
如何判斷集合中的元素與某個對象是否相同? List體系:使用存放入該集合中對象所屬類的equals方法 ArrayList/ Vector:底層可變數組, 查詢快,刪除和插入慢 LinkedList:底層鏈表, 查詢慢,刪除和插入快 Set體系:不重複且無序 HashSet:底層哈希表(本質上數組),增刪改查都比較快。 先判斷hashcode值,hashcode相同,再使用equals判斷。 TreeSet:底層二叉樹,對存入其中的對象默認按照自然順序排序 通過實現Comparable接口,並重寫compareTo方法,此方法返回0,表示對象相同。 |
–Comparator比較器,實現對象的排序:
–創建一個類,實現Comparator接口,重寫compare()方法。
–比較器主要用於指定對象排序的規則。
–以後,在實際排序中,最好類先實現一個Comparable,如果有其他排序規則的話,再使用Comparator.
lComparable與Comparator區別?
–1-Comparable位於java.lang包中;Comparator位於java.util包中。
–2-Comparable需要重寫compareTo方法;Comparator需要重寫compare方法。
–3-Comparable只能對排序對象指定一個排序規則;Comparator,可以自己創建類,獨立的定義對象的排序規則,而且可以定義多個排序規則。
Comparable相當於比較的對象自身具有比較特點;Comparator比較器相當於爲集合對象設置比較規則。
• 作業:
–實現SMS系統,將學生的信息按照年齡排序(Comparable),打印輸出。另外實現按照名字的長度排序(Comparator)
–分析:
使用Collections.sort(List對象);//按照自然順序對List對象中的元素進行排序。Collections.sort(List對象, Comparator com);List對象按照比較器定義的規則進行排序。如果List對象中的元素實現自然順序且又指定了比較器排序,此時比較器優先。
lMap接口
表示集合,與Collection體系無關,Map存放的是key-value的鍵值對。
在Map中,key不能重複,所以key的集合爲Set;value的值可以重複,所以
value的集合可以看做Collection。
lMap內部結構示意圖
lMap接口實現類
–HashMap(JDK1.2)
–Hashtable(JDK1.0)
–TreeMap
•實現類用法及注意點:
–HashMap與Hashtable用法基本一致。Hashtable已經被HashMap取代。
– HashMap與Hashtable
•HashMap底層是哈希表,key和value可以爲null;不同步,線程不安全,效率較高。如果想實現HashMap的同步,則使用Collections.synchronizedMap方法。
•Hashtable底層是哈希表,key和value不能爲null;同步,線程安全,效率較低。
•注意: HashSet底層其實就是使用HashMap
•雖然Hashtable已經被HashMap取代,但Hashtable的子類Properties依然常用。
•Properties屬性列表中每個鍵及其對應值都是一個字符串
Properties pros = System.getProperties();得到JVM系統的屬性。
–TreeMap(瞭解即可)
•底層是二叉樹,對於放入該對象的鍵,默認按照自然順序排列。TreeSet是由TreeMap實現的。
•注意:
–Map在真實使用中,key很少使用自定義的類型,如果爲自定義的類型,則該類必須重寫hashCode和equals方法。一般key都是使用八種基本數據類型對應的包裝類或String類型。
–對於Map來說,key和value是一一對應的。如果出現一個key對應多個value的情況。比如巴塞羅那足球隊有多個球員。將所有的球員添加到一個集合中,球隊爲key,集合爲value。
•作業:
–1- 自定義一個類,將該類作Map對象的key。練習Map中的方法的使用
–2- 練習Hashtable方法。熟悉Hashtable的用法。
–3- 編寫一個方法,該方法用於統計一個字符串中,每個字符的個數。
集合總結:
Collection體系
Map體系
泛型
•集合中不使用泛型和使用泛型的比較。
泛型在集合中使用的好處:當集合對象指定泛型類型後
1-類型安全(一旦集合對象確定泛型,只能存放入該類型的對象)
2-使用集合元素時,無需強制轉換。
注意:集合框架中,廣泛的使用了泛型。掌握泛型在集合中使用。
•JDK1.5後,引入泛型;泛型就是對類型的參數化。主要用於解決類型安全問題。
案例:常用集合對象使用泛型案例(Map, Arraylist, Comparable, Comparator, Iterator...)
•泛型類定義形式
[public] class 類名<T[, X, Y….]>{}
泛型類:類名
類型參數:T...
ex:
泛型類的使用:T的具體值,在new的時候指定
–不指定泛型:類名對象名= new 類名();此時,T默認爲Object
–如果指定T的值:類名<type> 對象名= new 類名<type>();
泛型類對象之間的賦值
–1- 不同的泛型類型之間,不能互相賦值。
–2- 類名 對象名 = new 類名<type>();正確
–3-通配符?。表示不知道將來傳入的泛型是什麼類型,使用?表示什麼類型 都可以接收。
類名<?> 對象名 = new 類名<type>();正確
?可作爲方法的參數。
•泛型接口定義形式:和類相似
public interface 接口名<T, X...>{}
•繼承關係中,實現類與泛型類;實現關係中,實現類與泛型接口?
不管是繼承還是實現,如果實現類已經知道其父類或父接口中泛型是什麼類型,則可以如下表示。
public A extends Father<String>{ }
public A implements IFather<String>{ }
如果實現類不知道其父類或父接口中泛型是什麼類型,則可以如下表示
public A<T> extends Father<T>{ }
public A<T> implements IFather<T>{ }
•泛型方法定義:
•將泛型定義在方法上,叫泛型方法。泛型方法的泛型僅僅對該方法起作用。
•定義格式: 泛型方法的標識,必須在方法的返回值前定義<T>泛型
[public …] [修飾符] <T> 返回值[T] name(T 參數)
•注意點:
•在普通類或泛型類中都可以定義泛型方法。
•靜態方法也可以定義泛型
•靜態方法無法訪問泛型類定義的泛型標識。因爲靜態先加載,而泛型類是new時指定泛型類型。
•如果靜態方法方法在定義時,數據類型不確定,可以將其定義成泛型。
•泛型上限和下限
泛型上限: ? extends T:表示可以接受T類型或者T的子類。
泛型下限: ? super T:表示可以接受T類型或者T的父類。
lCollections類
–是一個工具類,類中所有的方法都是static。這些方法都是操作Collection體系的。
–Collections類中,主要對集合進行排序,查找,替換,填充等功能。
注意:
•Collections與Collection區別?
–Collection是集合中的頂層接口,其右兩個子接口List,Set.
–Collections是工具類,裏面提供了操作(Collection)集合對象的各種靜態方法。
l集合和數組之間互相轉換
–數組->集合
•通過Arrays.asList(T… obj)方法將數組轉換成List對象。但是主要,轉換後,不可以使用集合對象中的增刪方法(使用的話,會報UnsupportedOperationException)。因爲數組長度是固定的。但可以使用查詢,判斷等不修改數組長度的方法。
–集合->數組
•使用集合對象中的toArray()(該方法只能返回Object[]),toArray(T[] t)可以指定具體數組元素的類型。
•String[] objs2 = list.toArray(new String[10]);此時,T[]的數組的長度如果小於等於集合中元素的個數,JVM會自動以元素個數爲數組分配內存。如果超過集合對象中元素的個數,剩餘部分以數組元素類型默認值填充。
IO
lFile類
–File類表示文件或目錄在JVM中的一種抽象形式。
–File類對文件或目錄進行了封裝,提供對文件或目錄的操作方法,可以方便編程時使用。
–注意:
•創建File對象,File可以表示文件或目錄。
•不同的OS中,路徑的分隔符也不相同。windows中,路徑分隔符可以使用/或\\;Linux或unix中,路徑分隔符使用/;File類提供了一個靜態的路徑分隔符的屬性,該屬性根據JVM在不同的OS中,可以得到當前OS的路徑分隔符。
–File類中方法講解-參考API
•函數的遞歸:
–函數的遞歸調用,就是在函數中,調用自身。
–1-函數遞歸,必須要有一個能夠終止遞歸的出口。
–2-函數遞歸時,還要注意,如果遞歸的層次過多,可能造成內存溢出
•作業:
–打印輸出具有等級結構的目錄信息
–比如:
lIO流操作
–Java程序操作存儲設備中的數據(input,output)時,以流的形式傳輸的。
lIO流分類
•IO流的分類:
–IO流相對於JVM,根據傳輸方向劃分
•輸入流
•輸出流
–IO流根據流的傳輸數據類型
•字節流(按字節傳輸)
•字符流(按字符傳輸)
lIO流體系結構,均基於四個頂層的抽象類
–對於字節流來說
•InputStream:表示所有字節輸入流的父類。
–InputStream的子類,寫法都是XxxInputStream.
•OutputStream:表示所有字節輸出流的父類。
–OutputStream的子類,寫法均是XxxOutputStream.
–對於字符流來說
•Reader:表示所有字符輸入流的父類。
–Reader的子類,寫法都是XxxReader.
•Writer:表示所有字符輸出流的父類。
–Writer的子類,寫法都是XxxWriter.
–節點流和包裝流
•節點流:流對象來說,能夠直接操作數據源的。
•包裝流:以節點流或包裝流作爲構造方法的參數。無法直接訪問數據源。
注意:所有跨出JVM區域的操作對象,必須要關閉。
l字節流體系結構
nInputStream和OutputStream體系
–FileInputStream和FileOutputStream
•節點流,可操作數據源文件
–ByteArrayInputStream和ByteArrayOutputStream
•節點流,內存
–BufferedInputStream和BufferedOutputStream
•包裝流,實現緩衝
–DataInputStream和DataOutputStream
•包裝流,操作基本數據類型和String
–ObjectInputStream和ObjectOutputStream
•包裝流,操作基本數據類型和對象。
•實現序列化和反序列化
–PrintStream
•打印流,節點流,包裝流,自動刷新,方便打印輸出println方法。
l字節流詳解- 可以用於文本(較少),圖片,音頻,視頻等操作。
•InputStream和OutputStream
–InputStream
•1-對於輸入流中的read方法,如果爲接收鍵盤輸入時,該方法是阻塞方法。當沒有輸入時,程序會停在read方法,等待外部輸入。
•2-對於read方法讀取文件,如果已到達文件末尾,則返回 -1。
•3-如果指定的讀取文件不存在,則報FileNotFoundException異常。
–OutputStream
•1-從程序中向文件中寫信息時,如果文件不存在,則會自動創建該文件,並將數據寫入到文件中;如果文件已經存在,則會覆蓋已存在的文件。(覆蓋相當於將原文件刪除,新建文件,寫入數據)
•2-將數據寫入到文件時,如果使用緩衝區,一般數據會暫時存放在緩衝區中。如果緩衝區已滿,則自動將數據寫入到文件中;如果緩衝區未滿,則數據存放在緩衝區中。此時,必須手動的刷新緩衝區,將緩衝區中數據寫入到文件中。
lFileInputStream和FileOutputStream
–屬於節點流,此時數據源是文件系統中的文件。
–FileInputStream
•對文件內容讀取。
•包含主要方法
–int read(): 一次讀取一個字節,返回int類型
–int read(byte[] bs): 一次讀取多個字節,填充到bs中,返回的是真正填充到bs中的個數。
–void close():關閉對象
–int available():返回文件的字節數
–long skip(long n):忽略掉的字節數
–FileOutputStream
•創建該對象,如果指定的文件不存在,則會自動創建該文件;如果指定的文件存在,則根據構造方法中append的值,append爲false,表示覆蓋重名的文件。如果想在文件末尾追加,則手動設置爲true即可。
•包含主要方法:
–void close() 關閉文件輸出流對象。關閉前會刷新緩衝區
–void write(int c):一次寫入一個字節
–void write(byte[] bs):一次寫入一個字節數組。
–void write(byte[] bs, int index, int len):寫出bs數組中一部分字節
–void flush():刷新緩衝區。
•作業:實現圖片或音頻的拷貝。
注意:
1-異常處理
直接使用try...catch...finally...處理
在方法中可以使用如下形式聲明異常,且關閉對象。
public void show() throws Exception
{
try
{
異常代碼塊
}
finally
{
if(fis != null)
{
fis.close
}
}
}
2-帶緩衝區和不帶緩衝區運行速度的比較
lByteArrayInputStream和ByteArrayOutputStream
–屬於節點流,此時數據源是字節數組(緩衝區)。close無效。
–ByteArrayInputStream
•ByteArrayInputStream 包含一個內部緩衝區,該緩衝區包含從流中讀取的字節。內部計數器跟蹤 read 方法要提供的下一個字節。
•關閉 ByteArrayInputStream 無效。此類中的方法在關閉此流後仍可被調用,而不會產生任何 IOException。
–ByteArrayOutputStream
•此類實現了一個輸出流,其中的數據被寫入一個 byte數組。緩衝區會隨着數據的不斷寫入而自動增長。可使用 toByteArray()獲得字節數組(緩衝區)內容和toString() 獲取字節數組轉換成字符串數據,size()獲得緩衝區的大小。
•關閉 ByteArrayOutputStream 無效。此類中的方法在關閉此流後仍可被調用,而不會產生任何IOException。
lBufferedInputStream和BufferedOutputStream
–屬於包裝流,實現緩衝功能。
–BufferedInputStream
•創建 BufferedInputStream 時,會創建一個內部緩衝區數組 ,提供緩衝功能。
–BufferedOutputStream
•該類實現緩衝的輸出流。通過設置這種輸出流,應用程序就可以將各個字節寫入底層輸出流中,而不必針對每次字節寫入調用底層系統。
對於過濾流來說,只需要關閉最外層的流對象即可。
lDataInputStream和DataOutputStream
–屬於包裝流,實現對Java基本數據類型和String進行讀寫操作。
–DataInputStream
•數據輸入流允許應用程序以與機器無關方式從底層輸入流中讀取基本 Java 數據類型。應用程序可以使用數據輸出流寫入稍後由數據輸入流讀取的數據。
–DataOutputStream
•數據輸出流允許應用程序以適當方式將基本 Java 數據類型寫入輸出流中。然後,應用程序可以使用數據輸入流將數據讀入。
–注意:
•DataInputStream讀取必須與DataOutputStream寫入的順序相同。
l設計模式-裝飾設計模式
比如Data,Buffered就是裝飾模式。
裝飾設計模式(Decorator):
也叫做包裝模式(Wrapper),裝飾模式以對客戶端透明的方式擴展對象的功能,是繼承關係的一個替代方案。
使用裝飾模式,客戶端並不會覺得對象在裝飾前和裝飾後有什麼區別,只是在不創建更多的子類模式下,將對象功能加以擴展。
裝飾模式的構成:
1-抽象構建角色(Component)給出一個抽象的接口,以規範準備接受附加責任的對象。相當於IO流中的InputStream, OutputStream
2-具體構建角色(ConcreteComponent)定義一個將要接受附加責任的類。相當於IO流中的FileOutputStream和FileInputStream
3-裝飾角色(Docorator)持有一個抽象構建(Component)角色的引用,並定義一個與抽象構件一致的接口,相當於IO流中FilterOutputStream, FilterInputStream
4-具體的裝飾角色(ConcreteDecorator):負責給構建對象附加的責任。相當於IO流中的BufferedInputStream,DataOutputStream,ObjectInputStream...
ex
裝飾模式特點:
1-裝飾對象和真實對象具有相同的接口,這樣客戶端對象就可以以真實對象的相同的方式和裝飾對象交互
2-裝飾對象包含一個真實對象的引用(reference)
3-裝飾對象接受所有來自客戶端的請求,它把這些請求轉發給真實對象
4-裝飾對象可以在轉發這些請求以前或者以後,增加一些附加的功能。這樣能確保在運行時,不用修改給定對象結構就可以在外部增加附加功能。
注意:
在面向對象程序設計中,通常使用繼承的關係來擴展給定類的功能。
比較繼承擴展和裝飾模式擴展。
ex:
裝飾模式和繼承區別?
a.裝飾模式是對已經存在類進行的組合,比繼承更加靈活。
b.裝飾模式擴展的是對象的功能,不需要增加類的數量,而類繼承擴展的是類的功能。
c.裝飾模式不改變原類及原類的繼承結構的情況下,動態擴展一個對象的功能。提高了擴展性。
d.裝飾模式降低了類與類之間繼承體系的臃腫,提高了擴展性。
lObjectInputStream和ObjectOutputStream
–屬於包裝流,實現基本數據類型和對象的讀寫操作。
–這兩個類是實現序列化和反序列化
–序列化與反序列化
–什麼是序列化和反序列化
–序列化:將內存中的對象保存到文件系統中。
–反序列化:保存在文件系統中的對象讀取到內存中。
–如何實現序列化和反序列化
–通過ObjectInputStream和ObjectOutputStream類實現。
–實現序列化的步驟:
–1-序列化對象的類必須實現Seriablizable接口。
–2-通過ObjectOutputStream的writeObject方法實現。
–實現反序列化的步驟
–1-使用ObjectInputStream的readObject方法。
–2-要保證序列化和反序列化的類是同一個類,且seriaVersionUID要一致。
–serialVersionUID的含義:
–Java序列化機制是通過在運行時判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的 serialVersionUID與本地相應實體(類)的serialVersionUID進行比較,如果相同就認爲是一致的,可以進行反序列化,否則就 會出現序列化版本不一致的異常。(InvalidCastException)。
–serialVersionUID有兩種顯示的生成方式:
–一個是默認的1L,比如:private static final long serialVersionUID = 1L;
–一個是根據類名、接口名、成員方法及屬性等來生成一個64位的哈希字段,比如:private static final long serialVersionUID = xxxxL;
–序列化與反序列化注意點:
–1-基本數據類型可以直接序列化。
–2-對於引用數據類型,要實現序列化,該類型必須實現Serializable接口。
–3-靜態變量不會被序列化。
–4-如果序列化的對象所屬的類中,包含了其他的引用類型作爲其屬性,則這些屬性要麼實現Serializable接口,要麼忽略(transient)。
–5-只有當父類也實現Serializable接口時,序列化子類對象時,父類纔會被序列化(先有父類對象,纔有子類對象),如果父類沒有實現Seriablizable接口,則父類不會被序列化。當反序列化時,會調用父類無參的構造方法,所以,如果父類沒有實現Seriablizable接口,則父類必須提供無參的構造方法。反序列化時候,不會調用子類的構造犯法,因爲子類已經存在於文件中。
–作業:
–每次退出系統,自動將添加的學生對象序列化到指定文件中;每次成功登陸系統後,反序列化。
lPrintStream類-打印流,只能向外輸出。
–PrintStream,也是OutputStream的子類。既可以作爲節點流,又可以作爲包裝流。主要作用能夠方便地打印各種數據值表示形式。
•構造方法:
–PrintStream(File file)
–PrintStream(String fileName)
–PrintStream(OutputStream out)
–PrintStream(OutputStream out, boolean autoFlush)
»如果atuoFlush爲true,則每當寫入byte數組,調用println方法或寫入換行符或字節(‘\n’),都會自動刷新緩衝區。
•常用方法:
–print(XXX)
–println(XXX)
常用字節流
總結:
l標準的輸入,輸出
–標準輸出:
•System.out:表示將數據輸出到控制檯上。
•PrintStream ps = System.out使用PrintStream中的println,print等方法。
–標準輸入:
•System.in:表示從鍵盤接收數據。
•InputStream is = System.in; 使用InputStream中的read方法,獲得鍵盤輸入的數據。
•重定向
//重定向標準輸出。在此語句之後,使用System.out輸出的數據,就轉向了out.txt文件中 System.setOut(new PrintStream("d:/out.txt")); System.out.println("hello world"); //重定向標準輸入。默認是從鍵盤獲得輸入.此語句之後,使用System.in獲得數據,就是從hao.txt獲取的 System.setIn(new FileInputStream("d:/hao.txt")); InputStream is = System.in; int c = -1; while((c = is.read()) != -1) { System.out.println((char)c); } |
l字符流
對於IO其實都是以字節流形式操作。在實際使用中,文本數據比較常用,爲了方便操作文本數據,對字節流進行封裝,形成了字符流。
注意:
1- 字符流底層操作也是字節數據,只是對字節數據進行了處理。
2- 字符流對象中,含有碼錶。如果不指定,默認爲JVM中的字符編碼。
lReader和Writer體系
–FileReader和FileWriter
–BufferedReader和BufferedWriter
–InputStreamReader和OutputStreamWriter
–PrintWriter
lReader和Writer體系
–Reader類
•用於讀取字符流的抽象類
–Writer
•用於寫入字符流的抽象類
•含有寫入字符串的方法:write(String str)
lFileReader和FileWriter類
–屬於節點流,此時數據源是文件。
–與FileInputStream,FileOutputStream類似,不同之處在於一次讀取一個字符。
–FileReader和FileWriter類中的方法,都是繼承自其父類。
lBufferedReader和BufferedWriter
–屬於過濾流,用於提供緩衝功能。
–BufferedReader
•除了讀取的各個方法,還提供了readLine()方法,用於讀取一行字符。
–BufferedWriter
•除了寫入的各個方法,還提供了newLine()方法,用於換行。
•‘\r\n’用於換行,但不支持跨平臺。newLine()方法,可跨平臺。
lInputStreamReader和OutputStreamWriter
–轉換流,可以在byte<->char之間互相轉換;在轉換的過程中,可以指定字符編碼。
–InputStreamReader
•byte->char(解碼)
–OutputStreamWriter
•char->byte(編碼)
l編碼-解碼
–常見的編碼表:
•ASCII:美國標準信息交換碼;用一個字節的7位。
•ISO-8859-1:歐洲碼錶。用一個字節的8位表示。
•GB2312:中文編碼表:約表示6,7千常用漢字
•GBK:中文碼錶,表示更多中文文字。約2萬多
•GB18030:
•Unicode:萬國碼,所有文字用2個字節表示。Java的char型使用unicode編碼。浪費空間。
•UTF-8:用最多三個字節表示一個字符。(英文佔一個字節,中文佔2到3個字節)
–編碼-字符或字符串轉換成字節數組
•String->byte[]
–1-str.getBytes();//按照默認字符集編碼
–2-str.getBytes(“utf-8”);//使用指定字符集編碼
–解碼-字節數組轉換成字符或字符串
•byte[]->String
–1-new String(byte[]);//按照默認字符集解碼
–2-new String(byte[], “utf8”);//按照指定字符集解碼
–亂碼的產生,是因爲編碼和解碼使用的碼錶不一致導致。只要編碼和解碼碼錶一致即可。
lPrintWriter
–提供方便打印字符的各種方法。可以當做節點流,也可以當做過濾流。
–與 PrintStream 類不同,如果啓用了自動刷新,則只有在調用println、printf 或 format 的其中一個方法時纔可能完成此操作,而不是每當正好輸出換行符時才完成。
–構造方法:
•構造方法中可以有File, String, Writer(有刷新的重載構造方法),除此之外,還包含OutputStream(含有刷新的重載構造方法)(因爲中間使用了OutputStreamWriter轉換流)。
•println,print方法。
IO總結:
如何使用IO流?
1-分析使用字節流還是字符流
圖片、音頻、視頻等使用字節流
字符,文本使用字符流
2-操作哪個設備(文件, 內存, 網絡...)
3-使用哪些流對象。
分析功能,需要使用哪些流對象。要求對各個流對象的特點熟練掌握。
lProperties類
–位於java.util包中,是Hashtable的子類。該類中存放鍵值對,且該類對象中的鍵值對都是String類型。
–Properties常用來讀取配置信息。當需要鍵值對及IO操作時,就可以考慮使用Properties。
–Properties類通過load方法,將配置文件信息加載到該類的對象中。可以通過Put,在對象中存儲新的鍵值對;通過store方法,可以將新的鍵值對重寫寫入指定的文件中。
Properties config = new Properties(); //將配置文件加載到Properties對象中。此時,load方法會將 //配置文件中的key=value,保存成Properties對象的鍵值對 config.load(new FileInputStream("d:/conf.txt")); config.list(System.out); Set<String> keys = config.stringPropertyNames(); for(String key : keys) { System.out.println(key + "=" + config.getProperty(key)); } //如果從內存中,將一個key=value保存到文件中 config.setProperty("name", "root"); config.setProperty("password", "root"); config.setProperty("dburl", "com.mysql.url"); config.store(new FileOutputStream("d:/db.conf"), "這是我的配置文件"); //讀取配置文件 config.load(new FileInputStream("d:/count.txt")); String value = config.getProperty("count"); if(value == null) { config.setProperty("count", 1 + ""); } else { config.setProperty("count", Integer.parseInt(value) + 1 + ""); } config.store(new FileOutputStream("d:/count.txt"), "login count"); |
–作業:
•SMS系統,通過Properties,實現登錄信息次數的限制。如果輸入用戶名或密碼錯誤超過三次,則提示錯誤信息。
lRandomAccessFile類
–RandomAccessFile類,不屬於字節流或字符流的體系結構。它的父類是Object。因爲該類可以讀寫文件內容,所以,它具備IO的特點,位於java.io包中。
–RandomAccessFile類,可以對文件進行隨機的讀取和寫入操作。
–RandomAccessFile類隨機訪問文件的原理?
•隨機訪問文件的行爲類似存儲在文件系統中的一個大型byte 數組。存在指向該隱含數組的光標或索引,稱爲文件指針;輸入操作從文件指針開始讀取字節,並隨着對字節的讀取而前移此文件指針。如果隨機訪問文件以讀取/寫入模式創建,則輸出操作也可用;輸出操作從文件指針開始寫入字節,並隨着對字節的寫入而前移此文件指針。寫入隱含數組的當前末尾之後的輸出操作導致該數組擴展。該文件指針可以通過 getFilePointer 方法讀取,並通過 seek 方法設置。
•通常,如果此類中的所有讀取例程在讀取所需數量的字節之前已到達文件末尾,則拋出 EOFException(是一種 IOException)。如果由於某些原因無法讀取任何字節,而不是在讀取所需數量的字節之前已到達文件末尾,則拋出 IOException,而不是EOFException。需要特別指出的是,如果流已被關閉,則可能拋出 IOException。
–構造函數:
•RandomAccessFile(File file, String mode)
–mode 參數指定用以打開文件的訪問模式。允許的值及其含意爲 :
–"r" 以只讀方式打開。調用結果對象的任何 write 方法都將導致拋出IOException。
–"rw" 打開以便讀取和寫入。如果該文件尚不存在,則嘗試創建該文件。
–常用方法:
•read,write,操作的都是字節,與字節流中的方法完全一致。
•readXxx,writeXxx:用於操作基本數據類型和字符串。
•readLine:每次讀取一行信息
•getFilePointer();返回long,表示文件指針的偏移量
•length();返回long,表示指向的文件的長度。
•seek(long pos) :表示設置文件指針的偏移量。
•int skipBytes(int n) :嘗試跳過輸入的 n 個字節以丟棄跳過的字節 ,返回跳過的字節數。只能向後跳過。
lIO流作業
- 創建程序從標準輸入讀入文本,在每行前加入行號後寫入文件中。文件名由命令行參數指定。
標準輸入: System.in 鍵盤。
命令行參數:
分析:
使用字符流
輸入的設備:鍵盤;輸出設備:文件。
BufferedReader readLine.
new BufferedReader(new InputStreamReader(System.in));
BufferedWriter寫入到文件中。
- 打印一個目錄下的所有的文件,包括目錄中的目錄裏包括的文件。
- 查找一個目錄中文件名裏所有包含abc字符的文件.
- 編寫一個final類,從配置文件db.conf中讀入各個配置參數,並提供讀取配置參數的方法。配置文件格式如下(#開始的行表示註釋):Properties直接加載即可。
#database type, such as sybase, oracle DB_TYPE = sybase
#the host where db installed DB_HOST = 192.168.0.100
#the port of db serves DB_PORT = 6666
#usename and password of db access USER_NAME = waytojob PASSWORD = tomorrow
|
6. 試改進練習2(文件拷貝),每次讀寫2K字節,以加快處理速度。比較讀寫單個字節和讀寫2K字節的效率
7. 打開一個文本文件,每次讀取一行內容。將每行作爲一個String讀入。按相反的順序打印出的所有行
8 如在E盤下有一個Test.txt文本文件內容是
#4
#劉德華#89#77#60
#張信哲#76#97#70
#周杰倫#89#88#90
#隨便啦#87#70#80
要求通過java讀取文本並把文本中的每一行後面的三個成績相加,
最後按從大到小的順序輸出到另一個文本文件中.
輸出後的文本文件內容應爲:
#4
#周杰倫#267
#張信哲#243
#隨便啦#237
#劉德華#226
0s-1
1s-2
2s-3
I s-1-i
9. all.txt 是一個GBK編碼的文件,要求寫一個程序,將這個文件轉換一下,改成utf-8編碼的。改好後的效果是在utf-8編碼的項目裏打開可以直接閱讀不亂碼。
線程
l程序、進程、線程
程序:使用編程語言編寫的有序的代碼集合,靜態。program
進程:運行的程序叫進程,動態。process
線程:線程是進程中一個獨立的控制單元。也可以說,線程是進程中,一段代碼的執行流。線程控制進程的執行。 thread
每個進程,至少要有一個線程。一個進程中,可以同時運行多個線程,即多線程。
l主線程
以前所學程序從main方法開始執行,即一個主線程在執行。
–獲得主線程的名稱:
•Thread.currentThread.getName();
l多線程同時運行
•線程的執行和調度
–線程只有獲得CPU的使用權或時間片,該線程才能執行。
–線程優先級不同的,採用搶佔式機制;優先級相同呢,分時調度。
lJava中創建線程的2種方式
–1-第一種創建線程的方式:
•1-自定義一個類,該類繼承Thread類,並重寫Thread類中的run方法。(該類叫線程類,run方法中爲線程體)。
•2-創建該線程類的對象。
•3-調用start方法,啓動線程,並執行run方法。
–2-第二種創建線程的方式:
•1-自定義一個類,實現Runnable接口,並重寫run方法。(該類不是線程類,僅定義了線程執行內容)。
•2-創建自定義類的對象
•3-創建Thread類(創建線程類對象),並指定線程運行的方法(自定義類的對象)。
•4-線程類對象調用start方法,啓動線程並執行run方法中的線程體。
–兩種實現線程的比較
•第二種方式線程對象與線程執行的代碼分離,比較靈活。
•在Java中,類是單繼承的。所以,第一種方式,繼承一個類以後,不能再繼承其他類了;而第二種方式,實現一個接口,還可以繼承其他類。
•建議使用第二種方式定義線程。
l線程中,調用start方法和調用run方法的區別?
–假如說,在main方法中,通過線程類對象,調用start方法和run方法。當調用run方法時,相當於普通的方法調用,此時,run方法中的內容,是main線程執行的;如果調用的是start方法,此時main線程運行到start方法後,程序中,啓動了一個新的線程(此時,至少存在兩個線程,main和新線程)。之後,main繼續向下執行,而同時啓動的線程執行run方法中的代碼。
l課堂練習
實現兩個線程,一個打印*,一個打印#
l線程的狀態和生命週期
Java中用Thread.State枚舉表示線程的狀態。
lThread類的方法
–start()
•啓動線程並開始執行run 方法。
–run()
•線程執行代碼存放在該方法中。
–sleep(long millis)
•在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行)
–join()
•停止當前正在執行的線程,直到加入的線程執行完畢後,再執行當前線程。
–yield()
•當前正在執行線程釋放CPU,回到與其他線程同等機會競爭CPU使用權。
–Java中線程的優先級
•Java優先級只表示獲得CPU的使用權機率增多。
•在java中,對線程的優先級進行查看和設置。getPriority();setPriority(int level)。
•不同的平臺,優先級也不同。不利於跨平臺。
•Thread中有三個常量,表示優先級
–MAX_PRIORITY
–MIN_PRIORITY
–NORM_PRIORITY
–用戶線程和守護線程
•守護線程也叫精靈線程。Java中通過setDaemon(true)設置線程爲守護線程。
•守護線程一般是在後臺爲其他線程提供服務的線程,它一般應該是一個獨立的線程。守護線程中run方法內,是一個死循環。
•守護線程一般在其他線程啓動前啓動,當進程中只有守護線程時,進程結束。
•用戶線程和守護線程區別?
–如果守護線程是進程中唯一運行的線程,則程序會自動退出。比如JVM中隱藏的垃圾收集線程。
l線程組
–每一個線程都屬於一個線程組。在main方法中,創建的線程,如果不爲其指定所屬的組,默認屬於main線程組。
–如何設置自定義的線程組
l作業:
–1- 實現一個線程類,能夠拷貝文件。同時啓動三個實例,拷貝3個文件。
–2- 多線程拷貝一個文件。RandomAccessFile。
l線程的互斥和同步(多線程安全問題)
1- 通過售票案例,可以知道,當多個線程訪問共享數據時,可能會出現數據的不完整性和不正確性。
2- 如何解決?
要保證多線程中共享數據的正確性;需要在某一時刻,僅讓一個線程訪問共享數據即可。
3- 如何實現
使用同步鎖機制,解決多線程共享數據的問題。
什麼是同步鎖機制
–synchronized關鍵字和對象的互斥鎖,實現多線程訪問共享數據,保證數據的完整性和正確性。
對象互斥鎖:
–默認情況下,每個對象除了擁有屬性和方法,還有一個互斥鎖。該鎖是對象的一部分。每個鎖,還有一個鎖池的概念。
•Object obj = new Object();obj對象就有一把互斥鎖,且有鎖池。
•String s = new String();s對象上也有一個互斥鎖,且有鎖池。
–互斥鎖和synchronized一起使用,就可以保證某一時刻,只有一個線程訪問共享數據。前提是,多個線程使用的是同一把互斥鎖。
實現同步鎖機制的兩種形式
–1-使用同步代碼塊
synchronized(互斥鎖)
{
//操作共享數據的代碼
}
–2-使用同步方法
public synchronized void xxx()//使用同步方法時,默認的鎖是this。
{
//操作共享數據的代碼。
}
注意:
1- 如果一個線程,即使獲得CPU時間片,但沒有獲得鎖,該線程也無法執行同步代碼塊中的代碼。
2- 如果多個線程運行到同步代碼塊,此時,沒有獲得鎖的線程會在鎖池中等待。
3- 多個線程必須使用同一把鎖,才能實現同步。
4- synchronized同步是通過程序執行的效率換取共享數據一致性。所以,非常消耗資源。synchronized代碼塊不擴大範圍。最好使用方法。
5- synchronized關鍵字不能修飾構造方法和抽象方法。
6- 一個線程可以獲得多個鎖,但一個鎖只能被一個線程得到。
7- synchronized修飾的非靜態方法,默認使用的鎖是this對象;如果同步方法是靜態方法時,比如下面靜態方法位於Test.java類中。
public static synchronized void xxx(){},此時的鎖是當前類.class對象,即Test.class對象(字節碼文件加載到內存中對應的Class對象)。
使用synchronized弊端:
雖然可以解決多線程共享數據的問題,但大大降低程序執行的效率
以前知識重提:
StringBuilder, StringBuffer
ArrayList, vector
不同步,線程不安全,效率較高 同步,線程安全的,效率低
l懶漢式單態模式
public class Singleton{ private static Singleton instance = null; private Singleton(){} //如果使用懶漢式,延遲加載。 /* 當多個線程執行該段代碼時,線程A判斷instance,此時爲null,符合條件。線程A就進入到X處。如果此時A失去了CPU使用權。B線程又執行該段代碼,且判斷instance,也是爲null。B也進入到X處。兩個線程會分別創建一個對象。此時就不符合單態模式規則了。*/ public static Singleton getInstance(){ if(instance == null){ //X //延遲到此處創建對象 instance = new Singleton(); } return instance; }
/* * 解決上述問題,可以在方法前面加上synchronzied關鍵字 * 注意,此時的鎖對象爲Singleton.class的對象。 * 使用同步,解決了多線程問題。如果多個線程執行到如下方法, * 執行效率會非常低。 */ public synchronized static Singleton getInstance1(){ if(instance == null){ instance = new Singleton(); } return instance; } /* 使用雙重判斷,提高執行效率。(畫流程圖表示) * 如下形式,可以提高多線程訪問的速度。 * 使用雙重判斷。A線程執行時當1處判斷,第一次執行到此處,instance爲null,此時,A進入到X處。此時A釋放CPU,B線程在1處判斷,進入到X位置。這個時候,X有兩個線程。A獲得同步塊鎖的時候,進入到同步代碼塊中。2處判斷,判斷是否已經創建了instance對象。 因爲是第一次執行,instance爲null。A就創建了instance對象。A釋放鎖後,B進入到同步塊中。此時intance已經不爲null。所以,僅僅創建了一個對象。 2處判斷,主要用於多個線程第一次進入到1判斷內部時,防止創建多個instance對象。 1處判斷,當第一次instance對象創建完畢後,其他多個線程,不需要執行同步代碼塊了,此時會提高線程執行速度。 */ public static Singleton getInstance2() { //BCDEFG if(instance == null) {//1 //X B synchronized(Singleton.class){ if(instance == null) {//2 instance = new Singleton(); } } } return instance; } } |
l線程的狀態和生命週期
l死鎖
–當兩個線程各自擁有自己的鎖對象,但彼此又希望獲得對方的鎖對象,此時兩個線程互相等待,造成死鎖。
–爲什麼會造成死鎖?
•因爲頻繁的使用synchronized。通常是同步代碼塊中嵌套同步代碼塊
–如何解決?
•減少線程同步資源定義
public class LockThread implements Runnable{ static ArrayList al1 = new ArrayList(); static ArrayList al2 = new ArrayList(); boolean flag ;
public LockThread(boolean flag) { this.flag = flag; }
@Override public void run(){ if(flag){ synchronized(al1) { System.out.println("獲得al1對象的鎖"); try{ Thread.sleep(5000); } catch (InterruptedException e){ e.printStackTrace(); } synchronized(al2){ System.out.println("在線程al1同步塊中,獲得al2對象的鎖"); } } } else{ synchronized(al2){ System.out.println("獲得al2對象的鎖"); try{ Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(al1) { System.out.println("在線程al2同步塊中,獲得al1對象的鎖"); } } } } } Main LockThread lock1 = new LockThread(false); LockThread lock2 = new LockThread(true); Thread th1 = new Thread(lock1, "A"); Thread th2 = new Thread(lock2, "B"); th1.start(); th2.start(); |
l線程通信機制
線程間的通信,是通過Object類中的wait()和notify()/notifyAll()方法實現的。
wait和notify()/notifyAll()機制
1- 當synchronized方法中的wait方法被調用時,當前線程會釋放該線程的所有的鎖,並在等待池中等待。要從等待池中喚醒某一線程或所有等待線程,則其他線程必須調用同一個對象上的notify()/notifyAll()方法。
2- 一旦喚醒等待池中的線程,則這些被喚醒的線程進入鎖池,等待獲得鎖。
3- 如果被喚醒的線程獲得了鎖,則線程進入可執行狀態。獲得時間片之後,該線程從wait之後的代碼開始執行。
注意:
1- wait()和notify()/notifyAll()方法必須在synchronized修飾的代碼塊或方法中。
2- 每個Java對象,除了默認擁有屬性和方法,還有鎖,鎖池,等待池。
線程間進行通信典型案例:生產者和消費者。
public class Factory{ private int sum = 0; public synchronized void produce() { while(sum == 1)//倉庫已經存滿,此時生產線程在等待池中等待{ try { System.out.println(Thread.currentThread().getName() + "-------" + "將要進入等待池"); this.wait();//生產線程運行到次方法,就到this對象的等待池中等待了。 System.out.println(Thread.currentThread().getName() + "-------" + "被喚醒,繼續執行"); if(sum == 1){ return; } } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "-------" + "生產產品"); sum = 1;//生產產品。之後,通知等待池中等待的線程,過來消費 System.out.println(Thread.currentThread().getName() + "-------" + "喚醒等待池中的所有線程"); this.notifyAll();//喚醒this等待池中所有等待的線程。 System.out.println("生產線程喚醒後的代碼****************************"); } public synchronized void consume() { while(sum == 0)//表示倉庫空,讓消費線程等待 { try{ System.out.println(Thread.currentThread().getName() + "-------" + "將要進入等待池"); this.wait(); System.out.println(Thread.currentThread().getName() + "-------" + "被喚醒,繼續執行"); if(sum == 0) { return; } }catch (InterruptedException e){ e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "-------" + "消費產品"); sum = 0;//消費產品,產品消費後,通知生產線程,生產產品 System.out.println(Thread.currentThread().getName() + "-------" + "喚醒等待池中的所有線程"); this.notifyAll();//通知this等待池中的線程。 System.out.println("消費線程喚醒後的代碼~~~~~~~~~~~~~~~~~~~~~~~~~~~"); } } |
public class Consumer implements Runnable{ private Factory f = null; public Consumer(Factory f){ this.f = f; } @Override public void run() { while(true){ try{ Thread.sleep(1000); } catch (InterruptedException e){ e.printStackTrace(); } f.consume(); } } } |
public class Producer implements Runnable{ private Factory f = null; public Producer(Factory f){ this.f = f; } @Override public void run(){ while(true){ try{ Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } f.produce(); } } } |
l線程的狀態和生命週期
l作業
–體會並實現通過繼承Thread類創建線程,並實現下述輸出結果:一個線程連續輸出26個大寫字母A-Z,另一個線程輸出26個小寫字母a-z;
–創建兩個線程,一個每3秒打印出線程名和當前時間,另一個每1秒打印出線程名和當前時間
–創建兩個線程的實例,分別將一個數組從小到大和從大到小排列.輸出結果
–編寫兩個線程,共享數據StringBuffer。一個向StringBuffer添加數據,一個從StringBuffer讀取數據,如果StringBuffer中沒有數據則等待
GUI圖形界面
l軟件分類
–單機軟件
•office;畫圖;PS;CAD
–分佈式軟件
•CS
–Client/Server:客戶端/服務器
–QQ,MSN,SKYPE….
–下載客戶端,安裝。
–特點:
»升級維護,麻煩
»Server端承擔大部分業務,客戶端承擔一部分業務。
»處理速度快。
•BS
–Browser/Server:瀏覽器/服務器
–163.com; sohu.com; qq.com
–只需要有一個瀏覽器
–特點:
»維護,升級簡單。
»Server幾乎承擔所有的業務,瀏覽器幾乎不承擔壓力。
»處理速度較慢。
lGUI
–Graphical User Interface
–AWT
•abstract window toolkit,抽象窗口工具集。
•重量級組件,調用底層系統的方法,顯示不獨立於平臺。
•java.awt.*; java.awt.event.*
–Swing
•輕量級組件,獨立於平臺,由Java語言開發。
•javax.swing.*;
–SWT
•eclipse
l組件類的層次結構
lSwing
–Swing是建立在AWT基礎上的一種增強型的Java GUI組件(工具集,工具包),主要是使用輕量組件替代AWT中絕大多數的重量組件。界面組件的渲染完全由Java自身完成,而不是調用操作系統的界面組件實現,是由Java自己繪製完成的。
–這樣做的好處是程序對組件的調用上完全Java化,較少摻合其它語言,有利於跨平臺(即Swing界面在不同的平臺上外觀完全一樣,真正做到了平臺獨立)。而AWT是調用操作系統的對等界面組件,因此在某些時候會受到平臺的限制。
–Swing中的每一個輕量級組件必須出現在重量級容器中,所以Swing的小應用程序Japplet,窗體,窗口,對話框都必須是重量組件,以提供繪製Swing輕量級組件的窗口。
–Swing組件由40多個,一部分爲AWT組件的替代品,另一部分是提供給用戶開發圖形用戶界面增添的組件。
–Swing包括javax.swing包及其子包。
–Swing組件除了AbstractButton類之外,都以J開頭
lSwing組件類的體系結構
l創建窗體的兩種形式
–直接創建JFrame。
–繼承JFrame,創建子類。
備註:講解JFrame控件
l窗體顯示在屏幕中間
l佈局管理器-LayoutManager
–佈局管理器用於佈局容器中各種組件存放的形式。
–所有的佈局都繼承自LayoutManager接口
–常見的5種佈局
•FlowLayout(流式佈局)
•BorderLayout(邊界佈局)
•GridLayout(網格佈局)
•CardLayout(卡片佈局)
•GridBagLayout(網格包佈局)
lBorderLayout
n邊框佈局管理器
u將整個容器的區域劃分成5部分:分別爲North北部;South南部;East東部;West西部;Center中部。
uWindow,JFrame,JDialog默認的佈局管理器是BorderLayout
u備註:講解JFrame,JDialog
lFlowLayout
–流式佈局管理器:
•FlowLayout將添加到容器中的組件,按添加順序從上到下,從左到右依次排列。
•FlowLayout是JPanel容器的默認佈局管理器。
•組件在容器中排列時,組件和組件之間默認的間距是5px,默認的對齊方式居中對齊。
•容器調用setLayout方法,用來設置佈局管理器。
備註:講解JPanel,講解設置佈局管理器
lGridLayout
–網格包佈局管理器
•網格佈局管理器對象創建時,可以指定行和列。
•容器中每個組件佔據一個網格,大小完全相同。
備註:講解JButton
lCardLayout(卡片佈局)
項目中講解
l絕對定位
使用控件的setBounds方法。使用前,通過setLayout(null)
案例:登錄界面,講解JLabel,JTextField,JPassword
l事件處理機制
–包含三方面
•事件源:組件。
•事件:對組件的某一種操作,就會自動產生一個事件對象。
•事件處理者:當對組件進行操作時,會自動產生相應的事件對象,一旦產生該對象,就由相應的事件處理者處理該事件。一個組件可以委託多個事件處理者。
l事件處理機制編寫步驟
–1-對於某種類型的事件XxxEvent,要想接收並處理這類事件,必須定義相應的事件監聽器類,該類需要實現與該事件相對應的監聽接口XxxListener.
–2-事件源通過addXxxListener(XxxListener對象)方法,將以後可能發生在自己身上的XxxEvent事件,委託給XxxListener對象。
事件處理案例:三種形式
1-本類實現監聽
2-其他類實現監聽
3-匿名內部類實現監聽
MouseEvent->MouseListener->addMouseListener()
觀察者設計模式。
作業:
l適配器類
–如果使用監聽接口,必須實現接口中定義的所有方法,但大多數情況下只是需要使用其中的一個或幾個方法。
–爲了避免麻煩,AWT中提供了多個相應的適配器類,這些適配器類實現了響應接口的所有方法的空操作。
–當使用時只需要擴展適配器類並且覆蓋我們需要實現的方法即可,而無需實現原來接口中所有的方法。
–注意:
•當接口中,僅有一個方法時,AWT中並不提供適配器類。
•所有的適配器類都是抽象類。
–注意:
•XxxEventàXxxListeneràaddXxxListeneràXxxAdapter類
lSwing組建講解
l作業:
1- 註冊界面
2- 實現記事本程序
3- 統計一個文件夾中所有.java文件的代碼行數、不包括空行、註釋行。如下圖
4- 統計一個文件夾中所有.java文件中代碼行,空行,註釋行信息,按照圖示顯示。
JDBC
l什麼是JDBC
JDBC是Java DataBase Connectivity的縮寫,它是連接Java程序和數據庫服務器的紐帶。
JDBC的實現封裝了與各種數據庫服務器通信的細節。Java程序通過JDBC API來訪問數據庫。
Java程序通過JDBC API訪問數據庫
lJDBC組成
1:JDBC的實現
包含3部分
1-JDBC驅動管理器
java.sql.DriverManager類,由SUN公司實現,負責註冊特定JDBC驅動器,以及根據特定驅動器建立與數據庫的連接。
2-JDBC驅動器API
由SUN公司制定,java.sql.Driver接口是其最主要接口。當數據庫供應商或者其他第三方工具提供商爲特定數據庫創建JDBC驅動器時,該驅動器必須實現JDBC驅動器API。即實現Driver接口。
3-JDBC驅動器
由數據庫供應商或者其他第三方工具提供商創建(因爲只有他們才最瞭解與特定數據庫通信細節,有能力對特定數據庫的驅動器進行優化),也稱爲JDBC驅動程序。JDBC驅動程序實現了JDBC驅動器API,負責與特定的數據庫連接,以及處理通信細節,它可以註冊到JDBC驅動管理器中。
2:JDBC API:
Java程序通過JDBC API訪問各種數據庫。
JDBC的實現
如上圖,JDBC驅動器纔是真正的連接Java應用程序與特定數據庫的紐帶。Java影城程序如果希望訪問某種數據庫,必須先獲得相應的JDBC驅動器的類庫,然後再把它註冊到JDBC驅動管理器中。
lJDBC驅動器分爲4類
n第一類驅動器:JDBC-ODBC驅動器
ODBC(Open DataBase Connectivity, 開放數據庫互連)是微軟公司爲應用程序提供的訪問任何一種數據庫的標準API。JDBC-ODBC驅動器爲Java程序與ODBC之間建立了橋樑,使得Java程序可以間接地訪問ODBC API。
JDBC-ODBC驅動器是唯一由SUN公司實現的驅動器,屬於JDK的一部分,在默認情況下,該驅動器已經在JDBC驅動管理器中註冊了。
JDBC-ODBC連接數據庫的速度比較慢,不提倡使用。
n第二類驅動器:由部分Java代碼和部分本地代碼組成,用於與數據庫的客戶端API通信。
使用該驅動器時,不僅需要安裝相關的Java類庫,還要安裝一些與平臺相關的本地代碼。
n第三類驅動器:完全由Java語言編寫的類庫
它用一種與具體數據庫服務器無關的協議將請求發送給服務器的特定組建,再由該組建按照特定數據庫協議對請求進行翻譯,並把翻譯後的內容發送給數據庫服務器。
n第四類驅動器:完全由Java語言編寫的類庫
它直接按照特定數據庫的協議,把請求發送給服務器數據庫。
以上四種驅動器訪問數據庫的速度由快到慢,依次爲4,3,2,1。大部分數據庫供應商都爲他們的數據庫產品提供了第三類,第四類驅動器。
Java應用程序應該優先考慮使用第三類和第四類驅動器,如果某數據庫不存在,則把第一類和第二類驅動器作爲暫時的替代品。
注意:
Java程序必須通過JDBC驅動器訪問數據庫,因爲DriverManager類,才實現Java程序與各種不同的JDBC驅動器通信。DriverManager類使用橋樑設計模式,成爲連接Java應用程序和各種JDBC驅動器的橋樑。
Java應用程序只和JDBC API打交道。JDBC API依賴DriverManager類來管理JDBC驅動器。如果程序向一個數據庫提交一條SQL語句,DriverManger類就會委派對應的數據庫的驅動器執行這個任務。
lJava程序使用JDBC訪問數據庫案例
查詢
增刪改
lJava程序使用JDBC連接數據庫的步驟:
–1- 加載並註冊數據庫的驅動。
•Class.forName(“包名.類名”);
–2-建立與數據庫的連接
•DriverManager.getConnection(url, user, password);
–url:連接數據庫的地址
–user:數據庫的登錄名
–password:數據庫的密碼
–3-創建Statement/PreparedStatement/CallableStatement對象
•conn.createStatement();
–4-執行SQL語句,並返回執行的結果。
•4.1-如果是查詢,使用stat.executeQuery(sql);,返回結果集
•4.2-增刪改,stat.executeUpdate(sql);返回受影響的行數
–5-關閉相關對象。
•數據庫連接非常消耗資源,一定要關閉。
lJDBC接口和類詳解
nDriver接口
所有JDBC驅動器都必須實現Driver接口,JDBC驅動器由數據庫廠商或者第三方提供。
nDriverManager類
DriverManger類用來建立和數據庫的連接以及管理JDBC驅動器。
DriverManager類方法都是靜態的:
registerDriver(Driver driver):在DriverManager類中註冊JDBC驅動器
getConnection(url, user, pwd):建立和數據庫的連接,並返回Connection對象
注意:
JDBC-ODBC驅動器是JDK自帶的,默認已經註冊。有些驅動器的Driver類在被加載的時候,自動創建本身實例,然後調用DriverManager.registerDriver()方法註冊自身。所以,在Java程序中,只要通過Class.forName()方法加載數據庫驅動類即可,而不必註冊。
nConnection接口
表示Java程序和數據庫的連接。包含以下方法
getMetaDate():返回表示數據庫的元數據的DatabaseMetaData對象。元數據包含了描述數據庫的相關信息。
createStatement():創建並返回Statement對象
prepareStatement(String sql):創建並返回PreparedStatement對象
nStatement接口
提供了三個執行SQL語句的方法:
execute(String sql):執行各種SQL語句。返回一個boolean類型值。爲true,表示執行的SQL語句具有查詢結果,可通過Statement對象getResultSet()方法獲取查詢結果。比較少用。
executeUpdate(String sql):執行SQL的insert, update, delete語句。返回int類型的值,表示數據庫中受SQL語句影響的記錄數目
executeQuery(String sql):執行SQL的select語句。返回一個查詢結果ResultSet對象。
nPreparedStatement接口
繼承自Statement接口,用於執行預編譯的SQL語句。
•PreparedStatement
–是Statement的子接口。
–表示預編譯的 SQL 語句的對象。 SQL 語句被預編譯並存儲在 PreparedStatement 對象中。然後可以使用此對象多次高效地執行該語句。
–執行動態SQL語句。
•PreparedStatement 與Statement相比,優點
–1-具有預編譯功能。提高執行效率
–2-執行動態SQL語句。提高靈活性
–3-因爲可以執行動態的SQL語句,防止SQL注入。
–
nCallableStatement接口
繼承自Statement接口,用於執行存儲過程。
nResultSet接口:
表示select查詢語句得到的結果集。
lJava數據類型和SQL數據類型之間關係
lJDBC模版模式、
l作業:完善SMS系統。實現登錄界面和主界面。如下圖
登錄判斷說明:
使用Select password from user where name = ?;查詢,返回ResultSet對象。對ResultSet rs進行遍歷如果沒有結果,表示該用戶名有錯誤。如果有結果,則表示用戶名存在,此時,可以得到從數據庫中查出的密碼。使用索引,提高執行效率。
dbPassword.equals(new String(password.getPassowrd()))
如果該語句返回true,表示輸入的密碼正確,成功登陸
如果爲false,輸入的密碼錯誤,則提示信息
主界面圖:講解JscrollPane, Jtable使用。
註冊界面:
l事務(Transaction)
實際情況分析:
事務:
–數據庫的事務是保證數據完整性的一種機制。
–事務是一組原子操作單元.一組Sql要麼全部成功,有一條失敗,則全部回退。簡而言之,事務就是多行語句要麼全部執行,要麼全部不執行。
–事務的特性
•原子性(atomicity):組成事務處理的語句形成了一個邏輯單元,不能只執行其中的一部分。
•一致性(consistency):在事務處理執行前後,數據庫是一致的(兩個賬戶要麼都變,或者都不變)。
•隔離性(isolcation):一個事務處理對另一個事務處理沒有影響。
•持續性(durability):事務處理的效果能夠被永久保存下來 。
使用事務:
Connection接口中,3個方法用於控制事務。
setAutoCommit(boolean autoCommit):設置是否自動提交事務
在JDBC API中,默認爲自動提交事務。即:每一條操縱數據庫的SQL語句代表一個事務,如果操作成功,數據庫系統將自動提交事務,否則就撤銷事務。通過將該方法設置爲false,禁止自動提交事務,纔可以把多條操縱數據庫的SQL語句作爲一個事務。
commit():提交事務
rollback():撤銷事務
事務保存點:
SavePoint sp = null;//聲明保存點 try{ …….. sp = conn.setSavepoint();//設置保存點 …….. }catch(Exception e){ if(conn != null && sp != null){ conn.rollback(sp); conn.commit();提交以前的執行。 } } |
案例:
Connection conn = null; ResultSet rs = null; PreparedStatement ps = null; Savepoint sp = null; String sql = "update user set money = money - 100 where id = 1"; try{ conn = DBUtil.getConnection(); conn.setAutoCommit(false); ps = conn.prepareStatement(sql); ps.executeUpdate(); sp = conn.setSavepoint();
sql = "update user set money = money - 1000 where id = 3"; ps.executeUpdate(sql);
sql = "select money from user where id = 2"; rs = ps.executeQuery();
float money = 0.0f;
if(rs.next()){ money = rs.getFloat("money"); }
if(money > 400){ throw new RuntimeException("超值了"); }
sql = "update user set money = money + 10 where id = 2"; ps.executeUpdate();
conn.commit(); }catch(RuntimeException e){ if(conn != null && sp != null){ conn.rollback(sp); conn.commit(); } throw e; } catch (SQLException e){ if(conn != null){ conn.rollback(); throw e; } } finally{ DBUtil.close(rs, ps, conn); } } } |
l批處理
–每次insert、update, delete效率比較低,在一條連接上,一次發送一批SQL語句,可以大幅提升增刪改效率。
–客戶端打包不能太多,可能會溢出。
–通常在執行批處理之前關閉事務的自動提交功能。這樣可避免執行批量任務的過程中產生的錯誤對其他操作產生的影響。
案例:
Connection conn = null; PreparedStatement ps = null; String sql = "update bank set sum = sum + ? where name = ?"; int[] rows = null; try { conn = DBUtil.getConnection(); // 執行批處理,關閉自動提交功能。 conn.setAutoCommit(false); ps = conn.prepareStatement(sql); ps.setInt(1, 5000); ps.setString(2, "a"); ps.addBatch(); ps.setInt(1, -5000); ps.setString(2, "b"); ps.addBatch(); //執行批處理 rows = ps.executeBatch();
} catch (SQLException e){ // TODO Auto-generated catch block e.printStackTrace(); } finally{ DBUtil.close(null, ps, conn); } System.out.println(Arrays.toString(rows)); |
l事務與批處理
–事務處理:底層是在數據庫方存儲SQL(沒有提交事務的數據放在數據庫的臨時表空間),最後一次把臨時表空間的數據提交到數據庫服務器執行實現。(消耗數據庫服務器內存)。
–SQL批處理:底層是在客戶端把SQL存儲起來,最後一次把客戶端存儲的數據發送到數據庫服務器執行實現。(消耗客戶端的內存)。較少使用。
l元數據
l使用JDBC調用存儲過程
–存儲過程(Stored Procedure)是一組爲了完成特定功能的SQL語句集,經編譯後存儲在數據庫中。
–用戶通過指定存儲過程的名字並給出參數(如果該存儲過程帶有參數)來執行它。
–存儲過程是數據庫中的一個重要對象,任何一個設計良好的數據庫應用程序都應該用到存儲過程
–優點:
•存儲過程允許標準組件式編程
•存儲過程能夠實現較快的執行速度,提高性能
•存儲過程能夠減少網絡流量
•存儲過程可被作爲一種安全機制來充分利用
兩層架構中,存儲過程使用較多;三層結構較少使用存儲過程。
案例:
l三大範式:
–第一範式:關係模式中,每個屬性不可再分。屬性原子性。
•列不可再分
–非主屬性完全依賴於主屬性,即消除非主屬性對主屬性的部分函數依賴關係。
•滿足第一範式的基礎上,所有非主屬性必須與主鍵完全依賴。
–第三範式:非主屬性對主屬性不存在傳遞函數依賴關係。
•第三範式總結:滿足第二範式基礎上,確保非主屬性與主鍵直接相關,而不是間接的相關。
l 關係模型和對象模型
關係模型: 數據庫中,表與表之間存在着一定的聯繫。通過外鍵來表示這種關聯,而外鍵僅僅引用其他表或自己表中的主鍵,而不是一行數據。 表和表之間三種關係: 一對一:較少見 比如:wife表和husband表,此時設計表時,必須找出主從關係。 create table husband( hid int primary key ) create table wife( wid int primary key hid int unique, constraint fk_one foreign key(hid) references husband(hid) ) 或 crete table wife( wid int primary key, constraint fk_one foreign key(wid) references husband(hid) ) 一對多或多對一:最常見 比如:footballer表和team表。此時一般在多的一方建外鍵約束 create table team ( tid int primary key, tname varchar2(30) ) create table footballer ( fid int primary key, name varchar(30) not null, tid int, constraint fk_multi foreign key(tid) references team(tid) ) 多對多:較常見 比如student表和course表。此時一般會建立一箇中間表處理多對多的關係。 create table student( sno int primary key, name varchar(30) not null, ) create table course( cid int primary key, cname varchar(30) not null ) create table sc( sno int, cno int, constraint fk_sno foreign key(sno) references student(sno), constraint fk_cno foreign key(cno) references course(cno) ) |
對象模型: Java中的類。操作數據庫時,一般一個數據表會對應一個JavaBean(類)。此時如果表與表之間存在以上三種關係,則對應的類也要體現出這種關係。表與表之間通過外鍵引用即可,而類與類之間,引用的爲對象。 類與類之間三種關係 一對一: class Husband{} class Wife{ private Husband h; } 或 class Wife{} class Husband{ private Wife w; } 一對多或多對一: class Team{} class Footballer{//多方關聯一方 private Team t; } 或 class Footballer{} class Team{//一方關聯多方 private List<Footballer> t; } 多對多: 多方關聯多方 class Student{ private List<Course> c; } class Course{ private List<Student> s; } |
網絡編程
l網絡基礎知識
1- TCP/IP四層協議
2- IP地址
IP:唯一標識網絡中的計算機。
IP由什麼組成:
ip地址是由32位的二進制組成的。並且ip地址分爲網絡號和主機號
又因爲32位的二進制,太難記憶,也難於使用。所以,我們把IP地址通過每八位劃分,劃分爲四部分。00000000(0)~11111111(255)。我們把劃分後的IP地址每一部分用十進制標識,且在每部分之間通過“.”劃分。劃分成如下表示形式
X1 . X2 . X3 . X4--------IP地址的 “點分十進制”。
IP地址又分爲IPV4和IPV6。
•X1.X2.X3.X4
•_ _ _ _ _ _ _ _ = X1
•IP = 網絡號+主機號
特殊IP:
1:127網段:用於迴環測試。127.0.0.1表示本機
表示本機:IP,localhost,127.0.0.1, . 主機名
2:10.x.x.x:用於表示局域網。
3:172.16.x.x~172.31.x.x:用於局域網
4:192.168.x.x:用於局域網
3-網關代理
4- DNS
DNS=Domain Name System
5- Port
–網絡通信中,是兩個應用程序通信。首先,先要找到對方電腦,然後再找電腦中的應用程序。
–如圖,兩臺電腦QQ通信:1-A先找到B(IP);2-A中的QQ要找到B中的QQ(port)
–Port是標識電腦中的應用程序。
–端口號是用一個16位的整數來表達的,其範圍爲0~65535,其中0~1023爲系統所保留,專門給那些通用的服務(well-known services),如http服務的端口號爲80,telnet服務的端口號爲21,ftp服務的端口爲23,…因此,當我們編寫通信程序時,應選擇一個大於1023的數作爲端口號,以免發生衝突.
–IP地址和端口號組成了所謂的Socket,Socket是網絡上運行的程序之間雙向通信鏈路的最後終結點,它是TCP和UDP的基礎。
lUDP和TCP
–UDP=user datagram protocal用戶數據報協議
•不面向連接,提供不可靠服務
•具有無序性
•效率較高
•Ex:發短信
–TCP=transfer control protocal傳輸控制協議
•面向連接,提供可靠服務
•具有有序性
•效率較低
•Ex:打電話
•UDP和TCP如何選擇?
–TCP協議和UDP協議各有各的用處。當對所傳輸的數據具有時序性和可靠性等要求時,應使用TCP協議;當傳輸的數據比較簡單、對時序等無要求時,UDP協議能發揮更好的作用。
lInetAddress
–主要用於封裝IP地址
lUDP編程
l練習:
lUDP發送端:
1-創建Socket服務端點,可以指定端口,也可以不指定。
DatagramSocket ds = new DatagramSocket();
2-創建發送的數據報,一定要指定發送的數據,數據的長度,目的IP,目的端口。
DatagramPacket dp = new DatagramPacket(data, data.length,InetAddress,port);
3-將數據報發送出去:
ds.send(DatagramPacket dp);
4-關閉ds對象
ds.close();
lUDP接受端:
1-創建Socket服務端點,一定要指定端口號。
DatagramSocket ds = new DatagramSocket(8888);
2-創建接受的數據報
DatagramPacket dp = new DatagramPacket(data, data.length);
3-將數據報發送出去:
ds.receive(DatagramPacket dp);
3.1-可以通過dp對象,得到客戶端的信息
IP, PORT, DATA, LENGTH;
4-關閉ds對象
ds.close();
lTCP編程
lTCP編程-服務器端
1-創建ServerSocket對象,並指定端口號
ServerSocket ss = new ServerSocket(port);
2-等待客戶端連接 accept();且指定一個Socket對象處理客戶端的通信。如果服務器端處理多個客戶端時,需要使用線程。
Socket s = ss.accept();
3-通過socket對象,得到相對於客戶端的輸入流和輸出流,與客戶端進行通信。
s.getInputStream()和s.getOutputStream()
4-關閉對象
lTCP編程-客戶端
1-創建Socket對象,且連接服務器
Socket s = new Socket(ip, port);
2-根據Socket對象,得到輸入流和輸出流,與服務器進行通信。
s.getInputStream()和s.getOutputStream()
3-關閉對象
lURL
–URL(Uniform Resource Locator)是統一資源定位器的簡稱,表示Internet上某一資源的地址。 Internet上的資源包括HTML文件、圖象文件、聲音文件、動畫文件以及其他任何內容
–通過URL,就可以訪問Internet。瀏覽器或其他程序通過解析給定的URL就可以在網絡上查找相應的文件或其他資源。
–http://www.youku.com:80/index.html
–屬於應用層
lURLConnection
–抽象類 URLConnection 是所有類的超類,它代表應用程序和URL 之間的通信鏈接。
– 屬於應用層
lURLConnection和URL
•URL表示一個統一資源定位符;只能接受服務器上資源的信息,只能讀。(openStream方法內部,使用還是URLConnection中的內容)
•URLConnection:表示一個連接。可以與服務器進行讀寫通信。
反射
l什麼是反射
–反射的概念是由Smith在1982年首次提出的,主要是指程序可以訪問、檢測和修改它本身狀態或行爲的一種能力。
–JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。
lClass
–Class<?>:表示一個正在運行的 Java 應用程序中的類和接口,是Reflection的起源。
–Class 沒有公共構造方法。Class 對象是在加載類時由 Java 虛擬機以及通過調用類加載器中的 defineClass 方法自動構造的,因此不能顯式地聲明一個Class對象。
–字節碼文件只有在使用到它時,才加載到JVM.
獲得Class對象三種方式
/1-直接通過類名.class得到Class對象
Class clazz1 = String.class;
//2-通過對象.getClass()方法,得到Class對象
String str = "aaa";
Class clazz3 = str.getClass();
//3-通過Class.forName(包名.類名);,此方法,常用。
Class clazz4 = Class.forName("java.lang.String");
lJava反射機制主要提供了以下功能:
–在運行時判斷任意一個對象所屬的類;
–在運行時構造任意一個類的對象;
–在運行時判斷任意一個類所具有的成員變量和方法;
–在運行時調用任意一個對象的方法;
–生成動態代理。
注意:
Class對象是反射的基礎。如果要獲得Field,Method,Constructor這些對象,必須先有Class對象。
lConstructor類
–1-得到Class對象;Class clazz = 類.class;
–2-通過Class對象得到指定的構造方法的對象
Constructor con = clazz.getDeclaredConstructor(class…paramType);
–3-通過構造方法的對象,創建類的對象
Object Con.newInstance(Object… objs);
–通過反射創建指定類的對象方式?
•類型.class.newInstance();根據空的構造函數,創建對象
•類型.class.getConstructor(Class..paramType).newInstance(Object… objs)//既可以是空的構造函數,也可以有參數的構造函數。
lField類
–1-創建Class對象 :Class clazz = 類型.class;
–2-通過該對象,得到具體某個屬性對象.
Field f = clazz.getDeclaredField(name)
–3-設置屬性值和得到屬性值
f.set(Object, value);
f.get(Object);
–如果想訪問私有屬性,則只需f.setAccessible(true)
lMethod類
–1-創建Class對象 :Class clazz = 類型.class;
–2-通過該對象,得到具體某個方法對象.
Method m = clazz.getDeclaredMethod(name, Class…paramTypes)
–3-調用方法
m.invoke(object, Object … param)
–如果想訪問私有方法,則只需m.setAccessible(true)
lArray
Array 類提供了動態創建和訪問 Java 數組的方法。
•Arrays和Array?
•此類包含用來操作數組(比如排序和搜索)的各種方法。此類還包含一個允許將數組作爲列表來查看的靜態工廠
•Array 類提供了動態創建和訪問 Java 數組的方法。
l作業:
–通過反射找出java.lang.Math 這個類的構造函數、屬性和方法。