正如每一個Java文檔所描述的那樣,CountDownLatch是一個同步工具類,它容許一個或多個線程一直等待,直到其餘線程的操做執行完後再執行。在Java併發中,countdownlatch的概念是一個常見的面試題,因此必定要確保你很好的理解了它。在這篇文章中,我將會涉及到在Java併發編 程中跟CountDownLatch相關的如下幾點:
目錄
- CountDownLatch是什麼?
- CountDownLatch如何工做?
- 在實時系統中的應用場景
- 應用範例
- 常見的面試題
CountDownLatch是什麼
CountDownLatch是在java1.5被引入的,跟它一塊兒被引入的併發工具類還有CyclicBarrier、Semaphore、ConcurrentHashMap和BlockingQueue,它們都存在於java.util.concurrent包下。CountDownLatch這個類可以使一個線程等待其餘線程完成各自的工做後再執行。例如,應用程序的主線程但願在負責啓動框架服務的線程已經啓動全部的框架服務以後再執行。
CountDownLatch是經過一個計數器來實現的,計數器的初始值爲線程的數量。每當一個線程完成了本身的任務後,計數器的值就會減1。當計數器值到達0時,它表示全部的線程已經完成了任務,而後在閉鎖上等待的線程就能夠恢復執行任務。
CountDownLatch的僞代碼以下所示:
1
2
3
4
5
6
|
//Main thread start
//Create CountDownLatch for N threads
//Create and start N threads
//Main thread wait on latch
//N threads completes there tasks are returns
//Main thread resume execution
|
CountDownLatch如何工做
CountDownLatch.java類中定義的構造函數:
1
2
|
//Constructs a CountDownLatch initialized with the given count.
public
void
CountDownLatch(
int
count) {...}
|
構造器中的計數值(count)實際上就是閉鎖須要等待的線程數量。這個值只能被設置一次,並且CountDownLatch沒有提供任何機制去從新設置這個計數值。
與CountDownLatch的第一次交互是主線程等待其餘線程。主線程必須在啓動其餘線程後當即調用CountDownLatch.await()方法。這樣主線程的操做就會在這個方法上阻塞,直到其餘線程完成各自的任務。
其餘N 個線程必須引用閉鎖對象,由於他們須要通知CountDownLatch對象,他們已經完成了各自的任務。這種通知機制是經過 CountDownLatch.countDown()方法來完成的;每調用一次這個方法,在構造函數中初始化的count值就減1。因此當N個線程都調 用了這個方法,count的值等於0,而後主線程就能經過await()方法,恢復執行本身的任務。
在實時系統中的使用場景
讓咱們嘗試羅列出在java實時系統中CountDownLatch都有哪些使用場景。我所羅列的都是我所能想到的。若是你有別的可能的使用方法,請在留言裏列出來,這樣會幫助到你們。
- 實現最大的並行性:有時咱們想同時啓動多個線程,實現最大程度的並行性。例如,咱們想測試一個單例類。若是咱們建立一個初始計數爲1的CountDownLatch,並讓全部線程都在這個鎖上等待,那麼咱們能夠很輕鬆地完成測試。咱們只需調用 一次countDown()方法就可讓全部的等待線程同時恢復執行。
- 開始執行前等待n個線程完成各自任務:例如應用程序啓動類要確保在處理用戶請求前,全部N個外部系統已經啓動和運行了。
- 死鎖檢測:一個很是方便的使用場景是,你能夠使用n個線程訪問共享資源,在每次測試階段的線程數目是不一樣的,並嘗試產生死鎖。
CountDownLatch使用例子
在這個例子中,我模擬了一個應用程序啓動類,它開始時啓動了n個線程類,這些線程將檢查外部系統並通知閉鎖,而且啓動類一直在閉鎖上等待着。一旦驗證和檢查了全部外部服務,那麼啓動類恢復執行。
BaseHealthChecker.java:這個類是一個Runnable,負責全部特定的外部服務健康的檢測。它刪除了重複的代碼和閉鎖的中心控制代碼。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
public
abstract
class
BaseHealthChecker
implements
Runnable {
private
CountDownLatch _latch;
private
String _serviceName;
private
boolean
_serviceUp;
//Get latch object in constructor so that after completing the task, thread can countDown() the latch
public
BaseHealthChecker(String serviceName, CountDownLatch latch)
{
super
();
this
._latch = latch;
this
._serviceName = serviceName;
this
._serviceUp =
false
;
}
@Override
public
void
run() {
try
{
verifyService();
_serviceUp =
true
;
}
catch
(Throwable t) {
t.printStackTrace(System.err);
_serviceUp =
false
;
}
finally
{
if
(_latch !=
null
) {
_latch.countDown();
}
}
}
public
String getServiceName() {
return
_serviceName;
}
public
boolean
isServiceUp() {
return
_serviceUp;
}
//This methos needs to be implemented by all specific service checker
public
abstract
void
verifyService();
}
|
NetworkHealthChecker.java:這個類繼承了BaseHealthChecker,實現了verifyService()方法。DatabaseHealthChecker.java和CacheHealthChecker.java除了服務名和休眠時間外,與NetworkHealthChecker.java是同樣的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public
class
NetworkHealthChecker
extends
BaseHealthChecker
{
public
NetworkHealthChecker (CountDownLatch latch) {
super
(
"Network Service"
, latch);
}
@Override
public
void
verifyService()
{
System.out.println(
"Checking "
+
this
.getServiceName());
try
{
Thread.sleep(
7000
);
}
catch
(InterruptedException e)
{
e.printStackTrace();
}
System.out.println(
this
.getServiceName() +
" is UP"
);
}
}
|
ApplicationStartupUtil.java:這個類是一個主啓動類,它負責初始化閉鎖,而後等待,直到全部服務都被檢測完。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
public
class
ApplicationStartupUtil
{
//List of service checkers
private
static
List<BaseHealthChecker> _services;
//This latch will be used to wait on
private
static
CountDownLatch _latch;
private
ApplicationStartupUtil()
{
}
private
final
static
ApplicationStartupUtil INSTANCE =
new
ApplicationStartupUtil();
public
static
ApplicationStartupUtil getInstance()
{
return
INSTANCE;
}
public
static
boolean
checkExternalServices()
throws
Exception
{
//Initialize the latch with number of service checkers
_latch =
new
CountDownLatch(
3
);
//All add checker in lists
_services =
new
ArrayList<BaseHealthChecker>();
_services.add(
new
NetworkHealthChecker(_latch));
_services.add(
new
CacheHealthChecker(_latch));
_services.add(
new
DatabaseHealthChecker(_latch));
//Start service checkers using executor framework
Executor executor = Executors.newFixedThreadPool(_services.size());
for
(
final
BaseHealthChecker v : _services)
{
executor.execute(v);
}
//Now wait till all services are checked
_latch.await();
//Services are file and now proceed startup
for
(
final
BaseHealthChecker v : _services)
{
if
( ! v.isServiceUp())
{
return
false
;
}
}
return
true
;
}
}
|
如今你能夠寫測試代碼去檢測一下閉鎖的功能了。
1
2
3
4
5
6
7
8
9
10
11
12
|
public
class
Main {
public
static
void
main(String[] args)
{
boolean
result =
false
;
try
{
result = ApplicationStartupUtil.checkExternalServices();
}
catch
(Exception e) {
e.printStackTrace();
}
System.out.println(
"External services validation completed !! Result was :: "
+ result);
}
}
|
1
2
3
4
5
6
7
8
9
|
Output
in
console:
Checking Network Service
Checking Cache Service
Checking Database Service
Database Service is UP
Cache Service is UP
Network Service is UP
External services validation completed !! Result was ::
true
|
常見面試題
能夠爲你的下次面試準備如下一些CountDownLatch相關的問題:
- 解釋一下CountDownLatch概念?
- CountDownLatch 和CyclicBarrier的不一樣之處?
- 給出一些CountDownLatch使用的例子?
- CountDownLatch 類中主要的方法?
BlockingQueue接口定義了一種阻塞的FIFO queue,每個BlockingQueue都有一個容量,讓容量滿時往BlockingQueue中添加數據時會形成阻塞,當容量爲空時取元素操做會阻塞。
ArrayBlockingQueue是一個由數組支持的有界阻塞隊列。在讀寫操做上都須要鎖住整個容器,所以吞吐量與通常的實現是類似的,適合於實現「生產者消費者」模式。
基於鏈表的阻塞隊列,同ArrayListBlockingQueue相似,其內部也維持着一個數據緩衝隊列(該隊列由一個鏈表構成),當生產者往隊列中放入一個數據時,隊列會從生產者手中獲取數據,並緩存在隊列內部,而生產者當即返回;只有當隊列緩衝區達到最大值緩存容量時(LinkedBlockingQueue能夠經過構造函數指定該值),纔會阻塞生產者隊列,直到消費者從隊列中消費掉一份數據,生產者線程會被喚醒,反之對於消費者這端的處理也基於一樣的原理。而LinkedBlockingQueue之因此可以高效的處理併發數據,還由於其對於生產者端和消費者端分別採用了獨立的鎖來控制數據同步,這也意味着在高併發的狀況下生產者和消費者能夠並行地操做隊列中的數據,以此來提升整個隊列的併發性能。
ArrayBlockingQueue和LinkedBlockingQueue的區別:
1. 隊列中鎖的實現不一樣
ArrayBlockingQueue實現的隊列中的鎖是沒有分離的,即生產和消費用的是同一個鎖;
LinkedBlockingQueue實現的隊列中的鎖是分離的,即生產用的是putLock,消費是takeLock
2. 在生產或消費時操做不一樣
ArrayBlockingQueue實現的隊列中在生產和消費的時候,是直接將枚舉對象插入或移除的;
LinkedBlockingQueue實現的隊列中在生產和消費的時候,須要把枚舉對象轉換爲Node<E>進行插入或移除,會影響性能
3. 隊列大小初始化方式不一樣
ArrayBlockingQueue實現的隊列中必須指定隊列的大小;
LinkedBlockingQueue實現的隊列中能夠不指定隊列的大小,可是默認是Integer.MAX_VALUE
- public class BlockingQueueTest {
- private static ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(5, true); //最大容量爲5的數組堵塞隊列
- //private static LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>(5);
- private static CountDownLatch producerLatch; //倒計時計數器
- private static CountDownLatch consumerLatch;
- public static void main(String[] args) {
- BlockingQueueTest queueTest = new BlockingQueueTest();
- queueTest.test();
- }
- private void test(){
- producerLatch = new CountDownLatch(10); //state值爲10
- consumerLatch = new CountDownLatch(10); //state值爲10
- Thread t1 = new Thread(new ProducerTask());
- Thread t2 = new Thread(new ConsumerTask());
- //啓動線程
- t1.start();
- t2.start();
- try {
- System.out.println("producer waiting...");
- producerLatch.await(); //進入等待狀態,直到state值爲0,再繼續往下執行
- System.out.println("producer end");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- try {
- System.out.println("consumer waiting...");
- consumerLatch.await(); //進入等待狀態,直到state值爲0,再繼續往下執行
- System.out.println("consumer end");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- //結束線程
- t1.interrupt();
- t2.interrupt();
- System.out.println("end");
- }
- //生產者
- class ProducerTask implements Runnable{
- private Random rnd = new Random();
- @Override
- public void run() {
- try {
- while(true){
- queue.put(rnd.nextInt(100)); //若是queue容量已滿,則當前線程會堵塞,直到有空間再繼續
- //offer方法爲非堵塞的
- //queue.offer(rnd.nextInt(100), 1, TimeUnit.SECONDS); //等待1秒後還不能加入隊列則返回失敗,放棄加入
- //queue.offer(rnd.nextInt(100));
- producerLatch.countDown(); //state值減1
- //TimeUnit.SECONDS.sleep(2); //線程休眠2秒
- }
- } catch (InterruptedException e) {
- //e.printStackTrace();
- } catch (Exception ex){
- ex.printStackTrace();
- }
- }
- }
- //消費者
- class ConsumerTask implements Runnable{
- @Override
- public void run() {
- try {
- while(true){
- Integer value = queue.take(); //若是queue爲空,則當前線程會堵塞,直到有新數據加入
- //poll方法爲非堵塞的
- //Integer value = queue.poll(1, TimeUnit.SECONDS); //等待1秒後尚未數據可取則返回失敗,放棄獲取
- //Integer value = queue.poll();
- System.out.println("value = " + value);
- consumerLatch.countDown(); //state值減1
- TimeUnit.SECONDS.sleep(2); //線程休眠2秒
- }
- } catch (InterruptedException e) {
- //e.printStackTrace();
- } catch (Exception ex){
- ex.printStackTrace();
- }
- }
- }
- }
28. wait()和sleep()的區別。
對於sleep()方法,咱們首先要知道該方法是屬於Thread類中的。而wait()方法,則是屬於Object類中的。
sleep()方法致使了程序暫停執行指定的時間,讓出cpu該其餘線程,可是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。
在調用sleep()方法的過程當中,線程不會釋放對象鎖。
而當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備
獲取對象鎖進入運行狀態。
Java中常常會用到迭代列表數據的狀況,本文針對幾種經常使用的寫法進行效率比較。雖然網上已經有了相似的文章,可是對他們的結論並不認同。
常見的實現方法:
1.for循環:
- for(int i = 0; i < list.size(); i++)
- for(int i = 0, size = list.size(); i < size; i++)
2.foreach:
- for(Object obj : list)
這是一種簡潔的寫法,只能對列表進行讀取,沒法修改。
3.while:
- int size = list.size();
- while(size-- > 0)
4.迭代:
- Object iter = list.iterator();
- while(iter.hasNext()) {
- iter.next();
- }
測試代碼:
針對以上幾種方法編寫的測試代碼。- public static void main(String[] args) {
- List<Integer> list = new ArrayList<Integer>();
- int runTime = 1000;//執行次數
- for (int i = 0; i < 1000 * 1000; i++) {
- list.add(i);
- }
- int size = list.size();
- long currTime = System.currentTimeMillis();//開始分析前的系統時間
- //基本的for
- for(int j = 0; j < runTime; j++) {
- for (int i = 0; i < size; i++) {
- list.get(i);
- }
- }
- long time1 = System.currentTimeMillis();
- //foreach
- for(int j = 0; j < runTime; j++) {
- for (Integer integer : list) {
- }
- }
- long time2 = System.currentTimeMillis();
- for(int j = 0; j < runTime; j++) {
- //while
- int i = 0 ;
- while(i < size){
- list.get(i++);
- }
- }
- long time3 = System.currentTimeMillis();
- for(int j = 0; j < runTime; j++) {//普通for循環
- for (int i = 0; i < list.size(); i++) {
- list.get(i);
- }
- }
- long time4 = System.currentTimeMillis();
- for(int j = 0; j < runTime; j++) {//迭代
- Iterator<Integer> iter = list.iterator();
- while(iter.hasNext()) {
- iter.next();
- }
- }
- long time5 = System.currentTimeMillis();
- long time = time1 - currTime ;
- System.out.print("use for:" + time);
- time = time2 - time1;
- System.out.print("\tuse foreach:" + time);
- time = time3 - time2;
- System.out.print("\tuse while:" + time);
- time = time4 - time3;
- System.out.print("\tuse for2:" + time);
- time = time5 - time4;
- System.out.print("\tuse iterator:" + time);
- System.out.println();
- }
輸出結果(JDK1.6):
1.
use for:8695 use foreach:17091 use while:6867 use for2:7741 use iterator:14144
2.
use for:8432 use foreach:18126 use while:6905 use for2:7893 use iterator:13976
3.
use for:8584 use foreach:17177 use while:6875 use for2:7707 use iterator:14345
結論:
1.針對列表的 foreach的效率是最低:
耗時是普通for循環的2倍以上。我的理解它的實現應該和iterator類似。
2. list.size()的開銷很小:
list.size()次數多少對效率基本沒有影響。查看ArrayList的實現就會發現,size()方法的只是返回了對象內的長度屬性,並無其它計算,因此只存在函數調用的開銷。
對數組的測試:
應該主要是檢測數據合法性時產生的。
將執行次數增長100萬倍,這時能夠看出結果基本相等,並無明顯的差別。說明:
4. 數組length也沒有開銷
可見數組長度並非每次執行的時候都要計算的。聯想一下Java建立數組的時候要求必須指定數組的長度,編譯處理的時候顯然沒有把這個值拋棄掉。
30. Java IO與NIO。
內核空間、用戶空間、計算機體系結構、計算機組成原理、……確實有點兒深奧。
個人新書《代碼之謎》會有專門的章節講解相關知識,如今寫個簡短的科普文:
就速度來講 CPU > 內存 > 硬盤
I
- 就是從硬盤到內存O
- 就是從內存到硬盤
第一種方式:我從硬盤讀取數據,而後程序一直等,數據讀完後,繼續操做。這種方式是最簡單的,叫阻塞IO。
第二種方式:我從硬盤讀取數據,而後程序繼續向下執行,等數據讀取完後,通知當前程序(對硬件來講叫中斷,對程序來講叫回調),而後此程序能夠當即處理數據,也能夠執行完當前操做在讀取數據。
在之前的 Java IO 中,都是阻塞式 IO,NIO 引入了非阻塞式 IO。
還有一種就是同步 IO 和異步 IO。常常說的一個術語就是「異步非阻塞」,好象異步和非阻塞是同一回事,這大概是一個誤區吧。
至於 Java NIO 的 Selector,在舊的 Java IO 系統中,是基於 Stream 的,即「流」,流式 IO。
當程序從硬盤往內存讀取數據的時候,操做系統使用了 2 個「小伎倆」來提升性能,那就是預讀,若是我讀取了第一扇區的第三磁道的內容,那麼你頗有可能也會使用第二磁道和第四磁道的內容,因此操做系統會把附近磁道的內容提早讀取出來,放在內存中,即緩存。
(PS:以上過程簡化了)
經過上面能夠看到,操做系統是按塊 Block從硬盤拿數據,就如同一個大臉盆,一會兒就放入了一盆水。可是,當 Java 使用的時候,舊的 IO 確實基於 流 Stream的,也就是雖然操做系統給我了一臉盆水,可是我得用吸管慢慢喝。
因而,NIO 橫空出世。
總的來講,java中的IO和NIO主要有三點區別:
IO | NIO |
面向流 | 面向緩衝 |
阻塞IO | 非阻塞IO |
無 | 選擇器(Selectors) |
1.面向流與面向緩衝
Java NIO和IO之間第一個最大的區別是,IO是面向流的,NIO是面向緩衝區的。 Java IO面向流意味着每次從流中讀一個或多個字節,直至讀取全部字節,它們沒有被緩存在任何地方。此外,它不能先後移動流中的數據。若是須要先後移動從流中讀取的數據,須要先將它緩存到一個緩衝區。 Java NIO的緩衝導向方法略有不一樣。數據讀取到一個它稍後處理的緩衝區,須要時可在緩衝區中先後移動。這就增長了處理過程當中的靈活性。可是,還須要檢查是否該緩衝區中包含全部您須要處理的數據。並且,需確保當更多的數據讀入緩衝區時,不要覆蓋緩衝區裏還沒有處理的數據。
2.阻塞與非阻塞IO
Java IO的各類流是阻塞的。這意味着,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據徹底寫入。該線程在此期間不能再幹任何事情了。 Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,可是它僅能獲得目前可用的數據,若是目前沒有數據可用時,就什麼都不會獲取。而不是保持線程阻塞,因此直至數據變的能夠讀取以前,該線程能夠繼續作其餘的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不須要等待它徹底寫入,這個線程同時能夠去作別的事情。 線程一般將非阻塞IO的空閒時間用於在其它通道上執行IO操做,因此一個單獨的線程如今能夠管理多個輸入和輸出通道(channel)。
3.選擇器(Selectors)
Java NIO的選擇器容許一個單獨的線程來監視多個輸入通道,你能夠註冊多個通道使用一個選擇器,而後使用一個單獨的線程來「選擇」通道:這些通道里已經有能夠處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。
下面着重談一下阻塞和非阻塞各自的優點:
一般的,對一個文件描述符指定的文件或設備, 有兩種工做方式: 阻塞與非阻塞。所謂阻塞方式的意思是指, 當試圖對該文件描述符進行讀寫時, 若是當時沒有東西可讀,或者暫時不可寫, 程序就進入等待狀態, 直到有東西可讀或者可寫爲止。而對於非阻塞狀態, 若是沒有東西可讀, 或者不可寫, 讀寫函數立刻返回, 而不會等待。
傳統的socket IO中,須要爲每一個鏈接建立一個線程,當併發的鏈接數量很是巨大時,線程所佔用的棧內存和CPU線程切換的開銷將很是巨大。這種方式具備很高的響應速度,而且控制起來也很簡單,在鏈接數較少的時候很是有效,可是若是對每個鏈接都產生一個線程的無疑是對系統資源的一種浪費,若是鏈接數較多將會出現資源不足的狀況。
使用NIO,再也不須要爲每一個線程建立單獨的線程,能夠用一個含有限數量線程的線程池,甚至一個線程來爲任意數量的鏈接服務。因爲線程數量小於鏈接數量,因此每一個線程進行IO操做時就不能阻塞,若是阻塞的話,有些鏈接就得不處處理,NIO提供了這種非阻塞的能力。
小量的線程如何同時爲大量鏈接服務呢,答案就是就緒選擇。這就比如到餐廳吃飯,每來一桌客人,都有一個服務員專門爲你服務,從你到餐廳到結賬走人,這樣方式的好處是服務質量好,一對一的服務,VIP啊,但是缺點也很明顯,成本高,若是餐廳生意好,同時來100桌客人,就須要100個服務員,那老闆發工資的時候得心痛死了,這就是傳統的一個鏈接一個線程的方式。
老闆是什麼人啊,精着呢。這老闆就得捉摸怎麼能用10個服務員同時爲100桌客人服務呢,老闆就發現,服務員在爲客人服務的過程當中並非一直都忙着,客人點完菜,上完菜,吃着的這段時間,服務員就閒下來了,但是這個服務員仍是被這桌客人佔用着,不能爲別的客人服務,用華爲領導的話說,就是工做不飽滿。那怎麼把這段閒着的時間利用起來呢。這餐廳老闆就想了一個辦法,讓一個服務員(前臺)專門負責收集客人的需求,登記下來,好比有客人進來了、客人點菜了,客人要結賬了,都先記錄下來按順序排好。每一個服務員到這裏領一個需求,好比點菜,就拿着菜單幫客人點菜去了。點好菜之後,服務員立刻回來,領取下一個需求,繼續爲別人客人服務去了。這種方式服務質量就不如一對一的服務了,當客人數據不少的時候可能須要等待。但好處也很明顯,因爲在客人正吃飯着的時候服務員不用閒着了,服務員這個時間內能夠爲其餘客人服務了,原來10個服務員最多同時爲10桌客人服務,如今可能爲50桌,60客人服務了。
這種服務方式跟傳統的區別有兩個:
一、增長了一個角色,要有一個專門負責收集客人需求的人。NIO裏對應的就是Selector。
二、由阻塞服務方式改成非阻塞服務了,客人吃着的時候服務員不用一直侯在客人旁邊了。傳統的IO操做,好比read(),當沒有數據可讀的時候,線程一直阻塞被佔用,直到數據到來。NIO中沒有數據可讀時,read()會當即返回0,線程不會阻塞。
NIO 設計背後的基石:反應器(Reactor)模式,用於事件多路分離和分派的體系結構模式。NIO中,客戶端建立一個鏈接後,先要將鏈接註冊到Selector,至關於客人進入餐廳後,告訴前臺你要用餐,前臺會告訴你你的桌號是幾號,而後你就可能到那張桌子坐下了,SelectionKey就是桌號。當某一桌須要服務時,前臺就記錄哪一桌須要什麼服務,好比1號桌要點菜,2號桌要結賬,服務員從前臺取一條記錄,根據記錄提供服務,完了再來取下一條。這樣服務的時間就被最有效的利用起來了。
J2SE1.4以上版本中發佈了全新的I/O類庫。本文將經過一些實例來簡單介紹NIO庫提供的一些新特性:非阻塞I/O,字符轉換,緩衝以及通道。
一. 介紹NIO
NIO包(java.nio.*)引入了四個關鍵的抽象數據類型,它們共同解決傳統的I/O類中的一些問題。
1. Buffer:它是包含數據且用於讀寫的線形表結構。其中還提供了一個特殊類用於內存映射文件的I/O操做。
2. Charset:它提供Unicode字符串影射到字節序列以及逆影射的操做。
3. Channels:包含socket,file和pipe三種管道,它其實是雙向交流的通道。
4. Selector:它將多元異步I/O操做集中到一個或多個線程中(它能夠被當作是Unix中select()函數或Win32中WaitForSingleEvent()函數的面向對象版本)。
二. 回顧傳統
在介紹NIO以前,有必要了解傳統的I/O操做的方式。以網絡應用爲例,傳統方式須要監聽一個ServerSocket,接受請求的鏈接爲其提供服務(服務一般包括了處理請求併發送響應)圖一是服務器的生命週期圖,其中標有粗黑線條的部分代表會發生I/O阻塞。
圖一
能夠分析建立服務器的每一個具體步驟。首先建立ServerSocket
ServerSocket server=new ServerSocket(10000);
而後接受新的鏈接請求
Socket newConnection=server.accept();
對於accept方法的調用將形成阻塞,直到ServerSocket接受到一個鏈接請求爲止。一旦鏈接請求被接受,服務器能夠讀客戶socket中的請求。
InputStream in = newConnection.getInputStream();
InputStreamReader reader = new InputStreamReader(in);
BufferedReader buffer = new BufferedReader(reader);
Request request = new Request();
while(!request.isComplete()) {
String line = buffer.readLine();
request.addLine(line);
}
這樣的操做有兩個問題,首先BufferedReader類的readLine()方法在其緩衝區未滿時會形成線程阻塞,只有必定數據填滿了緩衝區或者客戶關閉了套接字,方法纔會返回。其次,它回產生大量的垃圾,BufferedReader建立了緩衝區來從客戶套接字讀入數據,可是一樣建立了一些字符串存儲這些數據。雖然BufferedReader內部提供了StringBuffer處理這一問題,可是全部的String很快變成了垃圾須要回收。
一樣的問題在發送響應代碼中也存在
Response response = request.generateResponse();
OutputStream out = newConnection.getOutputStream();
InputStream in = response.getInputStream();
int ch;
while(-1 != (ch = in.read())) {
out.write(ch);
}
newConnection.close();
相似的,讀寫操做被阻塞並且向流中一次寫入一個字符會形成效率低下,因此應該使用緩衝區,可是一旦使用緩衝,流又會產生更多的垃圾。
傳統的解決方法
一般在Java中處理阻塞I/O要用到線程(大量的線程)。通常是實現一個線程池用來處理請求,如圖二
圖二
線程使得服務器能夠處理多個鏈接,可是它們也一樣引起了許多問題。每一個線程擁有本身的棧空間而且佔用一些CPU時間,耗費很大,並且不少時間是浪費在阻塞的I/O操做上,沒有有效的利用CPU。
三. 新I/O
1. Buffer
傳統的I/O不斷的浪費對象資源(一般是String)。新I/O經過使用Buffer讀寫數據避免了資源浪費。Buffer對象是線性的,有序的數據集合,它根據其類別只包含惟一的數據類型。
java.nio.Buffer 類描述
java.nio.ByteBuffer 包含字節類型。 能夠從ReadableByteChannel中讀在 WritableByteChannel中寫
java.nio.MappedByteBuffer 包含字節類型,直接在內存某一區域映射
java.nio.CharBuffer 包含字符類型,不能寫入通道
java.nio.DoubleBuffer 包含double類型,不能寫入通道
java.nio.FloatBuffer 包含float類型
java.nio.IntBuffer 包含int類型
java.nio.LongBuffer 包含long類型
java.nio.ShortBuffer 包含short類型
能夠經過調用allocate(int capacity)方法或者allocateDirect(int capacity)方法分配一個Buffer。特別的,你能夠建立MappedBytesBuffer經過調用FileChannel.map(int mode,long position,int size)。直接(direct)buffer在內存中分配一段連續的塊並使用本地訪問方法讀寫數據。非直接(nondirect)buffer經過使用Java中的數組訪問代碼讀寫數據。有時候必須使用非直接緩衝例如使用任何的wrap方法(如ByteBuffer.wrap(byte[]))在Java數組基礎上建立buffer。
2. 字符編碼
向ByteBuffer中存放數據涉及到兩個問題:字節的順序和字符轉換。ByteBuffer內部經過ByteOrder類處理了字節順序問題,可是並無處理字符轉換。事實上,ByteBuffer沒有提供方法讀寫String。
Java.nio.charset.Charset處理了字符轉換問題。它經過構造CharsetEncoder和CharsetDecoder將字符序列轉換成字節和逆轉換。
3. 通道(Channel)
你可能注意到現有的java.io類中沒有一個可以讀寫Buffer類型,因此NIO中提供了Channel類來讀寫Buffer。通道能夠認爲是一種鏈接,能夠是到特定設備,程序或者是網絡的鏈接。通道的類等級結構圖以下
圖三
圖中ReadableByteChannel和WritableByteChannel分別用於讀寫。
GatheringByteChannel能夠從使用一次將多個Buffer中的數據寫入通道,相反的,ScatteringByteChannel則能夠一次將數據從通道讀入多個Buffer中。你還能夠設置通道使其爲阻塞或非阻塞I/O操做服務。
爲了使通道可以同傳統I/O類相容,Channel類提供了靜態方法建立Stream或Reader
4. Selector
在過去的阻塞I/O中,咱們通常知道何時能夠向stream中讀或寫,由於方法調用直到stream準備好時返回。可是使用非阻塞通道,咱們須要一些方法來知道何時通道準備好了。在NIO包中,設計Selector就是爲了這個目的。SelectableChannel能夠註冊特定的事件,而不是在事件發生時通知應用,通道跟蹤事件。而後,當應用調用Selector上的任意一個selection方法時,它查看註冊了的通道看是否有任何感興趣的事件發生。圖四是selector和兩個已註冊的通道的例子
圖四
並非全部的通道都支持全部的操做。SelectionKey類定義了全部可能的操做位,將要用兩次。首先,當應用調用SelectableChannel.register(Selector sel,int op)方法註冊通道時,它將所需操做做爲第二個參數傳遞到方法中。而後,一旦SelectionKey被選中了,SelectionKey的readyOps()方法返回全部通道支持操做的數位的和。SelectableChannel的validOps方法返回每一個通道容許的操做。註冊通道不支持的操做將引起IllegalArgumentException異常。下表列出了SelectableChannel子類所支持的操做。
ServerSocketChannel OP_ACCEPT
SocketChannel OP_CONNECT, OP_READ, OP_WRITE
DatagramChannel OP_READ, OP_WRITE
Pipe.SourceChannel OP_READ
Pipe.SinkChannel OP_WRITE
四. 舉例說明
1. 簡單網頁內容下載
這個例子很是簡單,類SocketChannelReader使用SocketChannel來下載特定網頁的HTML內容。
package examples.nio;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.net.InetSocketAddress;
import java.io.IOException;
public class SocketChannelReader{
private Charset charset=Charset.forName("UTF-8");//建立UTF-8字符集
private SocketChannel channel;
public void getHTMLContent(){
try{
connect();
sendRequest();
readResponse();
}catch(IOException e){
System.err.println(e.toString());
}finally{
if(channel!=null){
try{
channel.close();
}catch(IOException e){}
}
}
}
private void connect()throws IOException{//鏈接到CSDN
InetSocketAddress socketAddress=
new InetSocketAddress("http://www.csdn.net",80/);
channel=SocketChannel.open(socketAddress);
//使用工廠方法open建立一個channel並將它鏈接到指定地址上
//至關與SocketChannel.open().connect(socketAddress);調用
}
private void sendRequest()throws IOException{
channel.write(charset.encode("GET "
+"/document"
+"\r\n\r\n"));//發送GET請求到CSDN的文檔中心
//使用channel.write方法,它須要CharByte類型的參數,使用
//Charset.encode(String)方法轉換字符串。
}
private void readResponse()throws IOException{//讀取應答
ByteBuffer buffer=ByteBuffer.allocate(1024);//建立1024字節的緩衝
while(channel.read(buffer)!=-1){
buffer.flip();//flip方法在讀緩衝區字節操做以前調用。
System.out.println(charset.decode(buffer));
//使用Charset.decode方法將字節轉換爲字符串
buffer.clear();//清空緩衝
}
}
public static void main(String [] args){
new SocketChannelReader().getHTMLContent();
}
2. 簡單的加法服務器和客戶機
服務器代碼
package examples.nio;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.net.InetSocketAddress;
import java.io.IOException;
/**
* SumServer.java
*
*
* Created: Thu Nov 06 11:41:52 2003
*
* @author starchu1981
* @version 1.0
*/
public class SumServer {
private ByteBuffer _buffer=ByteBuffer.allocate(8);
private IntBuffer _intBuffer=_buffer.asIntBuffer();
private SocketChannel _clientChannel=null;
private ServerSocketChannel _serverChannel=null;
public void start(){
try{
openChannel();
waitForConnection();
}catch(IOException e){
System.err.println(e.toString());
}
}
private void openChannel()throws IOException{
_serverChannel=ServerSocketChannel.open();
_serverChannel.socket().bind(new InetSocketAddress(10000));
System.out.println("服務器通道已經打開");
}
private void waitForConnection()throws IOException{
while(true){
_clientChannel=_serverChannel.accept();
if(_clientChannel!=null){
System.out.println("新的鏈接加入");
processRequest();
_clientChannel.close();
}
}
}
private void processRequest()throws IOException{
_buffer.clear();
_clientChannel.read(_buffer);
int result=_intBuffer.get(0)+_intBuffer.get(1);
_buffer.flip();
_buffer.clear();
_intBuffer.put(0,result);
_clientChannel.write(_buffer);
}
public static void main(String [] args){
new SumServer().start();
}
} // SumServer
客戶代碼
package examples.nio;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.channels.SocketChannel;
import java.net.InetSocketAddress;
import java.io.IOException;
/**
* SumClient.java
*
*
* Created: Thu Nov 06 11:26:06 2003
*
* @author starchu1981
* @version 1.0
*/
public class SumClient {
private ByteBuffer _buffer=ByteBuffer.allocate(8);
private IntBuffer _intBuffer;
private SocketChannel _channel;
public SumClient() {
_intBuffer=_buffer.asIntBuffer();
} // SumClient constructor
public int getSum(int first,int second){
int result=0;
try{
_channel=connect();
sendSumRequest(first,second);
result=receiveResponse();
}catch(IOException e){System.err.println(e.toString());
}finally{
if(_channel!=null){
try{
_channel.close();
}catch(IOException e){}
}
}
return result;
}
private SocketChannel connect()throws IOException{
InetSocketAddress socketAddress=
new InetSocketAddress("localhost",10000);
return SocketChannel.open(socketAddress);
}
private void sendSumRequest(int first,int second)throws IOException{
_buffer.clear();
_intBuffer.put(0,first);
_intBuffer.put(1,second);
_channel.write(_buffer);
System.out.println("發送加法請求 "+first+"+"+second);
}
private int receiveResponse()throws IOException{
_buffer.clear();
_channel.read(_buffer);
return _intBuffer.get(0);
}
public static void main(String [] args){
SumClient sumClient=new SumClient();
System.out.println("加法結果爲 :"+sumClient.getSum(100,324));
}
} // SumClient
3. 非阻塞的加法服務器
首先在openChannel方法中加入語句
_serverChannel.configureBlocking(false);//設置成爲非阻塞模式
重寫WaitForConnection方法的代碼以下,使用非阻塞方式
private void waitForConnection()throws IOException{
Selector acceptSelector = SelectorProvider.provider().openSelector();
/*在服務器套接字上註冊selector並設置爲接受accept方法的通知。
這就告訴Selector,套接字想要在accept操做發生時被放在ready表
上,所以,容許多元非阻塞I/O發生。*/
SelectionKey acceptKey = ssc.register(acceptSelector,
SelectionKey.OP_ACCEPT);
int keysAdded = 0;
/*select方法在任何上面註冊了的操做發生時返回*/
while ((keysAdded = acceptSelector.select()) > 0) {
// 某客戶已經準備好能夠進行I/O操做了,獲取其ready鍵集合
Set readyKeys = acceptSelector.selectedKeys();
Iterator i = readyKeys.iterator();
// 遍歷ready鍵集合,並處理加法請求
while (i.hasNext()) {
SelectionKey sk = (SelectionKey)i.next();
i.remove();
ServerSocketChannel nextReady =
(ServerSocketChannel)sk.channel();
// 接受加法請求並處理它
_clientSocket = nextReady.accept().socket();
processRequest();
_clientSocket.close();
}
}
}
JAVA反射(放射)機制:「程序運行時,容許改變程序結構或變量類型,這種語言稱爲動態語言」。從這個觀點看,Perl,Python,Ruby是動態語言,C++,Java,C# 不是動態語言。可是JAVA有着一個很是突出的動態相關機制:Reflection,用在Java身上指的是咱們能夠於運行時加載、探知、使用編譯期間完 全未知的classes。換句話說,Java程序能夠加載一個運行時才得知名稱的class,獲悉其完整構造(但不包括methods定義),並生成其對 象實體、或對其fields設值、或喚起其methods。 用途:Java反射機制主要提供瞭如下功能: 在運行時判斷任意一個對象所屬的類;在運行時構造任意一個類的對象;在運行時判斷任意一個類所具備的成員變量和方法;在運行時調用任意一個對象的方法;生成動態代理。 有時候咱們說某個語言具備很強的動態性,有時候咱們會區分動態和靜態的不一樣技術與做法。咱們朗朗上口動態綁定(dynamic binding)、動態連接(dynamic linking)、動態加載(dynamic loading)等。然而「動態」一詞其實沒有絕對而廣泛適用的嚴格定義,有時候甚至像面向對象當初被導入編程領域同樣,一人一把號,各吹各的調。 通常而言,開發者社羣說到動態語言,大體認同的一個定義是:「程序運行時,容許改變程序結構或變量類型,這種語言稱爲動態語言」。從這個觀點看,Perl,Python,Ruby是動態語言,C++,Java,C#不是動態語言。 儘管在這樣的定義與分類下Java不是動態語言,它卻有着一個很是突出的動態相關機制:Reflection。 這個字的意思是「反射、映象、倒影」,用在Java身上指的是咱們能夠於運行時加載、探知、使用編譯期間徹底未知的classes。換句話說,Java程 序能夠加載一個運行時才得知名稱的class,獲悉其完整構造(但不包括methods定義),並生成其對象實體、或對其fields設值、或喚起其 methods。這種「看透class」的能力(the ability of the program to examine itself)被稱爲introspection(內省、內觀、檢討)。Reflection和introspection是常被並提的兩個術語。 Java如何可以作出上述的動態特性呢?這是一個深遠話題,本文對此只簡單介紹一些概念。整個篇幅最主要仍是介紹 Reflection APIs,也就是讓讀者知道如何探索class的結構、如何對某個「運行時才獲知名稱的class」生成一份實體、爲其fields設值、調用其 methods。本文將談到java.lang.Class,以及java.lang.reflect中的Method、Field、Constructor等等classes。
32. 泛型經常使用特色,List<String>可否轉爲List<Object>。
所謂泛型,就是變量類型的參數化。
泛型是JDK1.5中一個最重要的特徵。經過引入泛型,咱們將得到編譯時類型的安全和運行時更小的拋出ClassCastException的可能。
在JDK1.5中,你能夠聲明一個集合將接收/返回的對象的類型。
使用泛型時若是不指明參數類型,即泛型類沒有參數化,會提示警告,此時類型爲Object。
爲何使用泛型
使用泛型的典型例子,是在集合中的泛型使用。
在使用泛型前,存入集合中的元素能夠是任何類型的,當從集合中取出時,全部的元素都是Object類型,須要進行向下的強制類型轉換,轉換到特定的類型。
好比:
List myIntList = new LinkedList(); // 1 myIntList.add(new Integer(0)); // 2 Integer x = (Integer) myIntList.iterator().next(); // 3
第三行的這個強制類型轉換可能會引發運行時的錯誤。
泛型的思想就是由程序員指定類型,這樣集合就只能容納該類型的元素。
使用泛型:
List<Integer> myIntList = new LinkedList<Integer>(); // 1' myIntList.add(new Integer(0)); // 2' Integer x = myIntList.iterator().next(); // 3'
將第三行的強制類型轉換變爲了第一行的List類型說明,編譯器會爲咱們檢查類型的正確性。這樣,代碼的可讀性和健壯性也會加強。
泛型使用基礎
例如:
public interface List <E> { void add(E x); Iterator<E> iterator(); } public interface Iterator<E> { E next(); boolean hasNext(); }
尖括號中包含的是形式類型參數(formal type parameters),它們就如同通常的類型同樣,能夠在整個類的聲明中被使用。
當類被使用時,會使用具體的實際類型參數(actual type argument)代替。
好比前面的例子中的List<Integer>,那麼全部的E將會被Integer類型所代替。
泛型類型參數只能被類或接口類型賦值,不能被原生數據類型賦值,原生數據類型須要使用對應的包裝類。
形式類型參數的命名:儘可能使用單個的大寫字母(有時候多個泛型類型時會加上數字,好比T1,T2),好比許多容器集合使用E,表明element(元素),Map中用K表明鍵keys,V表明值。
泛型容器的實現討論
不能用new的形式來建立一個泛型數組。
以下:
public class SimpleCollection<T> { private T[] objArr; private int index = 0; public SimpleCollection() { //Error: Cannot create a generic array of T objArr = new T[10]; } }
會報錯。
如何建立一個數組讓它接受全部可能的類型呢?
public class SimpleCollection<T> { private T[] objArr; private int index = 0; public SimpleCollection() { //Error: Cannot create a generic array of T //objArr = new T[10]; //Warning: Unchecked cast from Object[] to T[] objArr = (T[]) new Object[10]; } }
這個形式雖然能夠作到,可是會產生一個警告。
查看ArrayList中的實現,能夠發現它是使用了一個Object類型的數組:
private transient Object[] elementData;
在取出的時候(get方法中)使用了類型轉換:
(E) elementData[index];
泛型和子類
List<String> ls = new ArrayList<String>(); // 1 List<Object> lo = ls; // 2
一個String類型的List是一個Object類的List嗎?
不能夠,Java編譯器將會在第二行產生一個編譯錯誤,由於它們的類型不匹配。
這樣就避免了若是lo引入加入Object類型的對象,而ls引用試圖將其轉換爲String類型而引起錯誤。因此編譯器阻止了這種可能。
繼承泛型類別
直接用例子說明:
父類:
public class Parent<T1,T2> { private T1 foo1; private T2 foo2; public T1 getFoo1() { return foo1; } public void setFoo1(T1 foo1) { this.foo1 = foo1; } public T2 getFoo2() { return foo2; } public void setFoo2(T2 foo2) { this.foo2 = foo2; } }
子類繼承父類:
public class Child<T1, T2, T3> extends Parent<T1, T2> { private T3 foo3; public T3 getFoo3() { return foo3; } public void setFoo3(T3 foo3) { this.foo3 = foo3; } }
實現泛型接口
見例子:
泛型接口:
public interface ParentInterface<T1,T2> { public void setFoo1(T1 foo1); public void setFoo2(T2 foo2); public T1 getFoo1(); public T2 getFoo2(); }
子類實現泛型接口:
public class ChildClass<T1,T2> implements ParentInterface<T1, T2> { private T1 foo1; private T2 foo2; @Override public void setFoo1(T1 foo1) { this.foo1 = foo1; } @Override public void setFoo2(T2 foo2) { this.foo2 = foo2; } @Override public T1 getFoo1() { return this.foo1; } @Override public T2 getFoo2() { return this.foo2; } }
一. 泛型概念的提出(爲何須要泛型)?
首先,咱們看下下面這段簡短的代碼:
1 public class GenericTest { 2
3 public static void main(String[] args) { 4 List list = new ArrayList(); 5 list.add("qqyumidi"); 6 list.add("corn"); 7 list.add(100); 8
9 for (int i = 0; i < list.size(); i++) { 10 String name = (String) list.get(i); // 1
11 System.out.println("name:" + name); 12 } 13 } 14 }
定義了一個List類型的集合,先向其中加入了兩個字符串類型的值,隨後加入一個Integer類型的值。這是徹底容許的,由於此時list默認的類型爲Object類型。在以後的循環中,因爲忘記了以前在list中也加入了Integer類型的值或其餘編碼緣由,很容易出現相似於//1中的錯誤。由於編譯階段正常,而運行時會出現「java.lang.ClassCastException」異常。所以,致使此類錯誤編碼過程當中不易發現。
在如上的編碼過程當中,咱們發現主要存在兩個問題:
1.當咱們將一個對象放入集合中,集合不會記住此對象的類型,當再次從集合中取出此對象時,改對象的編譯類型變成了Object類型,但其運行時類型任然爲其自己類型。
2.所以,//1處取出集合元素時須要人爲的強制類型轉化到具體的目標類型,且很容易出現「java.lang.ClassCastException」異常。
那麼有沒有什麼辦法能夠使集合可以記住集合內元素各種型,且可以達到只要編譯時不出現問題,運行時就不會出現「java.lang.ClassCastException」異常呢?答案就是使用泛型。
二.什麼是泛型?
泛型,即「參數化類型」。一提到參數,最熟悉的就是定義方法時有形參,而後調用此方法時傳遞實參。那麼參數化類型怎麼理解呢?顧名思義,就是將類型由原來的具體的類型參數化,相似於方法中的變量參數,此時類型也定義成參數形式(能夠稱之爲類型形參),而後在使用/調用時傳入具體的類型(類型實參)。
看着好像有點複雜,首先咱們看下上面那個例子採用泛型的寫法。
1 public class GenericTest { 2
3 public static void main(String[] args) { 4 /*
5 List list = new ArrayList(); 6 list.add("qqyumidi"); 7 list.add("corn"); 8 list.add(100); 9 */
10
11 List<String> list = new ArrayList<String>(); 12 list.add("qqyumidi"); 13 list.add("corn"); 14 //list.add(100); // 1 提示編譯錯誤
15
16 for (int i = 0; i < list.size(); i++) { 17 String name = list.get(i); // 2
18 System.out.println("name:" + name); 19 } 20 } 21 }
採用泛型寫法後,在//1處想加入一個Integer類型的對象時會出現編譯錯誤,經過List<String>,直接限定了list集合中只能含有String類型的元素,從而在//2處無須進行強制類型轉換,由於此時,集合可以記住元素的類型信息,編譯器已經可以確認它是String類型了。
結合上面的泛型定義,咱們知道在List<String>中,String是類型實參,也就是說,相應的List接口中確定含有類型形參。且get()方法的返回結果也直接是此形參類型(也就是對應的傳入的類型實參)。下面就來看看List接口的的具體定義:
1 public interface List<E> extends Collection<E> { 2
3 int size(); 4
5 boolean isEmpty(); 6
7 boolean contains(Object o); 8
9 Iterator<E> iterator(); 10
11 Object[] toArray(); 12
13 <T> T[] toArray(T[] a); 14
15 boolean add(E e); 16
17 boolean remove(Object o); 18
19 boolean containsAll(Collection<?> c); 20
21 boolean addAll(Collection<? extends E> c); 22
23 boolean addAll(int index, Collection<? extends E> c); 24
25 boolean removeAll(Collection<?> c); 26
27 boolean retainAll(Collection<?> c); 28
29 void clear(); 30
31 boolean equals(Object o); 32
33 int hashCode(); 34
35 E get(int index); 36
37 E set(int index, E element); 38
39 void add(int index, E element); 40
41 E remove(int index); 42
43 int indexOf(Object o); 44
45 int lastIndexOf(Object o); 46
47 ListIterator<E> listIterator(); 48
49 ListIterator<E> listIterator(int index); 50
51 List<E> subList(int fromIndex, int toIndex); 52 }
咱們能夠看到,在List接口中採用泛型化定義以後,<E>中的E表示類型形參,能夠接收具體的類型實參,而且此接口定義中,凡是出現E的地方均表示相同的接受自外部的類型實參。
天然的,ArrayList做爲List接口的實現類,其定義形式是:
1 public class ArrayList<E> extends AbstractList<E>
2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable { 3
4 public boolean add(E e) { 5 ensureCapacityInternal(size + 1); // Increments modCount!!
6 elementData[size++] = e; 7 return true; 8 } 9
10 public E get(int index) { 11 rangeCheck(index); 12 checkForComodification(); 13 return ArrayList.this.elementData(offset + index); 14 } 15
16 //...省略掉其餘具體的定義過程
17
18 }
由此,咱們從源代碼角度明白了爲何//1處加入Integer類型對象編譯錯誤,且//2處get()到的類型直接就是String類型了。
三.自定義泛型接口、泛型類和泛型方法
從上面的內容中,你們已經明白了泛型的具體運做過程。也知道了接口、類和方法也均可以使用泛型去定義,以及相應的使用。是的,在具體使用時,能夠分爲泛型接口、泛型類和泛型方法。
自定義泛型接口、泛型類和泛型方法與上述Java源碼中的List、ArrayList相似。以下,咱們看一個最簡單的泛型類和方法定義:
1 public class GenericTest { 2
3 public static void main(String[] args) { 4
5 Box<String> name = new Box<String>("corn"); 6 System.out.println("name:" + name.getData()); 7 } 8
9 } 10
11 class Box<T> { 12
13 private T data; 14
15 public Box() { 16
17 } 18
19 public Box(T data) { 20 this.data = data; 21 } 22
23 public T getData() { 24 return data; 25 } 26
27 }
在泛型接口、泛型類和泛型方法的定義過程當中,咱們常見的如T、E、K、V等形式的參數經常使用於表示泛型形參,因爲接收來自外部使用時候傳入的類型實參。那麼對於不一樣傳入的類型實參,生成的相應對象實例的類型是否是同樣的呢?
1 public class GenericTest { 2
3 public static void main(String[] args) { 4
5 Box<String> name = new Box<String>("corn"); 6 Box<Integer> age = new Box<Integer>(712); 7
8 System.out.println("name class:" + name.getClass()); // com.qqyumidi.Box
9 System.out.println("age class:" + age.getClass()); // com.qqyumidi.Box
10 System.out.println(name.getClass() == age.getClass()); // true
11
12 } 13
14 }
由此,咱們發現,在使用泛型類時,雖然傳入了不一樣的泛型實參,但並無真正意義上生成不一樣的類型,傳入不一樣泛型實參的泛型類在內存上只有一個,即仍是原來的最基本的類型(本實例中爲Box),固然,在邏輯上咱們能夠理解成多個不一樣的泛型類型。
究其緣由,在於Java中的泛型這一律念提出的目的,致使其只是做用於代碼編譯階段,在編譯過程當中,對於正確檢驗泛型結果後,會將泛型的相關信息擦出,也就是說,成功編譯事後的class文件中是不包含任何泛型信息的。泛型信息不會進入到運行時階段。
對此總結成一句話:泛型類型在邏輯上看以當作是多個不一樣的類型,實際上都是相同的基本類型。
四.類型通配符
接着上面的結論,咱們知道,Box<Number>和Box<Integer>實際上都是Box類型,如今須要繼續探討一個問題,那麼在邏輯上,相似於Box<Number>和Box<Integer>是否能夠當作具備父子關係的泛型類型呢?
爲了弄清這個問題,咱們繼續看下下面這個例子:
1 public class GenericTest { 2
3 public static void main(String[] args) { 4
5 Box<Number> name = new Box<Number>(99); 6 Box<Integer> age = new Box<Integer>(712); 7
8 getData(name); 9
10 //The method getData(Box<Number>) in the type GenericTest is 11 //not applicable for the arguments (Box<Integer>)
12 getData(age); // 1 13
14 } 15
16 public static void getData(Box<Number> data){ 17 System.out.println("data :" + data.getData()); 18 } 19
20 }
咱們發現,在代碼//1處出現了錯誤提示信息:The method getData(Box<Number>) in the t ype GenericTest is not applicable for the arguments (Box<Integer>)。顯然,經過提示信息,咱們知道Box<Number>在邏輯上不能視爲Box<Integer>的父類。那麼,緣由何在呢?
1 public class GenericTest { 2
3 public static void main(String[] args) { 4
5 Box<Integer> a = new Box<Integer>(712); 6 Box<Number> b = a; // 1
7 Box<Float> f = new Box<Float>(3.14f); 8 b.setData(f); // 2
9
10 } 11
12 public static void getData(Box<Number> data) { 13 System.out.println("data :" + data.getData()); 14 } 15
16 } 17
18 class Box<T> { 19
20 private T data; 21
22 public Box() { 23
24 } 25
26 public Box(T data) { 27 setData(data); 28 } 29
30 public T getData() { 31 return data; 32 } 33
34 public void setData(T data) { 35 this.data = data; 36 } 37
38 }
這個例子中,顯然//1和//2處確定會出現錯誤提示的。在此咱們能夠使用反證法來進行說明。
假設Box<Number>在邏輯上能夠視爲Box<Integer>的父類,那麼//1和//2處將不會有錯誤提示了,那麼問題就出來了,經過getData()方法取出數據時究竟是什麼類型呢?Integer? Float? 仍是Number?且因爲在編程過程當中的順序不可控性,致使在必要的時候必需要進行類型判斷,且進行強制類型轉換。顯然,這與泛型的理念矛盾,所以,在邏輯上Box<Number>不能視爲Box<Integer>的父類。
好,那咱們回過頭來繼續看「類型通配符」中的第一個例子,咱們知道其具體的錯誤提示的深層次緣由了。那麼如何解決呢?總部能再定義一個新的函數吧。這和Java中的多態理念顯然是違背的,所以,咱們須要一個在邏輯上能夠用來表示同時是Box<Integer>和Box<Number>的父類的一個引用類型,由此,類型通配符應運而生。
類型通配符通常是使用 ? 代替具體的類型實參。注意了,此處是類型實參,而不是類型形參!且Box<?>在邏輯上是Box<Integer>、Box<Number>...等全部Box<具體類型實參>的父類。由此,咱們依然能夠定義泛型方法,來完成此類需求。
1 public class GenericTest { 2
3 public static void main(String[] args) { 4
5 Box<String> name = new Box<String>("corn"); 6 Box<Integer> age = new Box<Integer>(712); 7 Box<Number> number = new Box<Number>(314); 8
9 getData(name); 10 getData(age); 11 getData(number); 12 } 13
14 public static void getData(Box<?> data) { 15 System.out.println("data :" + data.getData()); 16 } 17
18 }
有時候,咱們還可能聽到類型通配符上限和類型通配符下限。具體有是怎麼樣的呢?
在上面的例子中,若是須要定義一個功能相似於getData()的方法,但對類型實參又有進一步的限制:只能是Number類及其子類。此時,須要用到類型通配符上限。
1 public class GenericTest { 2
3 public static void main(String[] args) { 4
5 Box<String> name = new Box<String>("corn"); 6 Box<Integer> age = new Box<Integer>(712); 7 Box<Number> number = new Box<Number>(314); 8
9 getData(name); 10 getData(age); 11 getData(number); 12
13 //getUpperNumberData(name); // 1
14 getUpperNumberData(age); // 2
15 getUpperNumberData(number); // 3
16 } 17
18 public static void getData(Box<?> data) { 19 System.out.println("data :" + data.getData()); 20 } 21
22 public static void getUpperNumberData(Box<? extends Number> data){ 23 System.out.println("data :" + data.getData()); 24 } 25
26 }
此時,顯然,在代碼//1處調用將出現錯誤提示,而//2 //3處調用正常。
類型通配符上限經過形如Box<? extends Number>形式定義,相對應的,類型通配符下限爲Box<? super Number>形式,其含義與類型通配符上限正好相反,在此不做過多闡述了。
五.話外篇
本文中的例子主要是爲了闡述泛型中的一些思想而簡單舉出的,並不必定有着實際的可用性。另外,一提到泛型,相信你們用到最多的就是在集合中,其實,在實際的編程過程當中,本身能夠使用泛型去簡化開發,且能很好的保證代碼質量。而且還要注意的一點是,Java中沒有所謂的泛型數組一說。
對於泛型,最主要的仍是須要理解其背後的思想和目的。
33. 解析XML的幾種方式的原理與特色:DOM、SAX、PULL。
在android開發中,常常用到去解析xml文件,常見的解析xml的方式有一下三種:SAX、Pull、Dom解析方式。最近作了一個android版的CSDN閱讀器,用到了其中的兩種(sax,pull),今天對android解析xml的這三種方式進行一次總結。
今天解析的xml示例(channels.xml)以下:
<?xml version="1.0" encoding="utf-8"?>
<channel>
<item id="0" url="http://www.baidu.com">百度</item>
<item id="1" url="http://www.qq.com">騰訊</item>
<item id="2" url="http://www.sina.com.cn">新浪</item>
<item id="3" url="http://www.taobao.com">淘寶</item>
</channel>
1、使用sax方式解析
基礎知識:
這種方式解析是一種基於事件驅動的api,有兩個部分,解析器和事件處理器,解析器就是XMLReader接口,負責讀取XML文檔,和向事件處理器發送事件(也是事件源),事件處理器ContentHandler接口,負責對發送的事件響應和進行XML文檔處理。
下面是ContentHandler接口的經常使用方法
public abstract void characters (char[] ch, int start, int length)
這個方法來接收字符塊通知,解析器經過這個方法來報告字符數據塊,解析器爲了提升解析效率把讀到的全部字符串放到一個字符數組(ch)中,做爲參數傳遞給character的方法中,若是想獲取本次事件中讀取到的字符數據,須要使用start和length屬性。
public abstract void startDocument () 接收文檔開始的通知
public abstract void endDocument () 接收文檔結束的通知
public abstract void startElement (String uri, String localName, String qName, Attributes atts) 接收文檔開始的標籤
public abstract void endElement (String uri, String localName, String qName) 接收文檔結束的標籤
在通常使用中爲了簡化開發,在org.xml.sax.helpers提供了一個DefaultHandler類,它實現了ContentHandler的方法,咱們只想繼承DefaultHandler方法便可。
另外SAX解析器提供了一個工廠類:SAXParserFactory,SAX的解析類爲SAXParser 能夠調用它的parser方法進行解析。
看了些基礎之後開始上代碼吧(核心代碼,下載代碼在附件)
1 public class SAXPraserHelper extends DefaultHandler {
2
3 final int ITEM = 0x0005;
4
5 List<channel> list;
6 channel chann;
7 int currentState = 0;
8
9 public List<channel> getList() {
10 return list;
11 }
12
13 /*
14 * 接口字符塊通知
15 */
16 @Override
17 public void characters(char[] ch, int start, int length)
18 throws SAXException {
19 // TODO Auto-generated method stub
20 // super.characters(ch, start, length);
21 String theString = String.valueOf(ch, start, length);
22 if (currentState != 0) {
23 chann.setName(theString);
24 currentState = 0;
25 }
26 return;
27 }
28
29 /*
30 * 接收文檔結束通知
31 */
32 @Override
33 public void endDocument() throws SAXException {
34 // TODO Auto-generated method stub
35 super.endDocument();
36 }
37
38 /*
39 * 接收標籤結束通知
40 */
41 @Override
42 public void endElement(String uri, String localName, String qName)
43 throws SAXException {
44 // TODO Auto-generated method stub
45 if (localName.equals("item"))
46 list.add(chann);
47 }
48
49 /*
50 * 文檔開始通知
51 */
52 @Override
53 public void startDocument() throws SAXException {
54 // TODO Auto-generated method stub
55 list = new ArrayList<channel>();
56 }
57
58 /*
59 * 標籤開始通知
60 */
61 @Override
62 public void startElement(String uri, String localName, String qName,
63 Attributes attributes) throws SAXException {
64 // TODO Auto-generated method stub
65 chann = new channel();
66 if (localName.equals("item")) {
67 for (int i = 0; i < attributes.getLength(); i++) {
68 if (attributes.getLocalName(i).equals("id")) {
69 chann.setId(attributes.getValue(i));
70 } else if (attributes.getLocalName(i).equals("url")) {
71 chann.setUrl(attributes.getValue(i));
72 }
73 }
74 currentState = ITEM;
75 return;
76 }
77 currentState = 0;
78 return;
79 }
80 }
1 private List<channel> getChannelList() throws ParserConfigurationException, SAXException, IOException
2 {
3 //實例化一個SAXParserFactory對象
4 SAXParserFactory factory=SAXParserFactory.newInstance();
5 SAXParser parser;
6 //實例化SAXParser對象,建立XMLReader對象,解析器
7 parser=factory.newSAXParser();
8 XMLReader xmlReader=parser.getXMLReader();
9 //實例化handler,事件處理器
10 SAXPraserHelper helperHandler=new SAXPraserHelper();
11 //解析器註冊事件
12 xmlReader.setContentHandler(helperHandler);
13 //讀取文件流
14 InputStream stream=getResources().openRawResource(R.raw.channels);
15 InputSource is=new InputSource(stream);
16 //解析文件
17 xmlReader.parse(is);
18 return helperHandler.getList();
19 }
從第二部分代碼,能夠看出使用SAX解析XML的步驟:
一、實例化一個工廠SAXParserFactory
二、實例化SAXPraser對象,建立XMLReader 解析器
三、實例化handler,處理器
四、解析器註冊一個事件
四、讀取文件流
五、解析文件
2、使用pull方式解析
基礎知識:
在android系統中,不少資源文件中,不少都是xml格式,在android系統中解析這些xml的方式,是使用pul解析器進行解析的,它和sax解析同樣(我的感受要比sax簡單點),也是採用事件驅動進行解析的,當pull解析器,開始解析以後,咱們能夠調用它的next()方法,來獲取下一個解析事件(就是開始文檔,結束文檔,開始標籤,結束標籤),當處於某個元素時能夠調用XmlPullParser的getAttributte()方法來獲取屬性的值,也可調用它的nextText()獲取本節點的值。
其實以上描述,就是對整個解析步驟的一個描述,看看代碼吧
1 private List<Map<String, String>> getData() {
2 List<Map<String, String>> list = new ArrayList<Map<String, String>>();
3 XmlResourceParser xrp = getResources().getXml(R.xml.channels);
4
5 try {
6 // 直到文檔的結尾處
7 while (xrp.getEventType() != XmlResourceParser.END_DOCUMENT) {
8 // 若是遇到了開始標籤
9 if (xrp.getEventType() == XmlResourceParser.START_TAG) {
10 String tagName = xrp.getName();// 獲取標籤的名字
11 if (tagName.equals("item")) {
12 Map<String, String> map = new HashMap<String, String>();
13 String id = xrp.getAttributeValue(null, "id");// 經過屬性名來獲取屬性值
14 map.put("id", id);
15 String url = xrp.getAttributeValue(1);// 經過屬性索引來獲取屬性值
16 map.put("url", url);
17 map.put("name", xrp.nextText());
18 list.add(map);
19 }
20 }
21 xrp.next();// 獲取解析下一個事件
22 }
23 } catch (XmlPullParserException e) {
24 // TODO Auto-generated catch block
25 e.printStackTrace();
26 } catch (IOException e) {
27 // TODO Auto-generated catch block
28 e.printStackTrace();
29 }
30
31 return list;
32 }
3、使用Dom方式解析
基礎知識:
最後來看看Dom解析方式,這種方式解析本身以前也沒有用過(在j2ee開發中比較常見,沒有作過這方面的東西),在Dom解析的過程當中,是先把dom所有文件讀入到內存中,而後使用dom的api遍歷全部數據,檢索想要的數據,這種方式顯然是一種比較消耗內存的方式,對於像手機這樣的移動設備來說,內存是很是有限的,因此對於比較大的XML文件,不推薦使用這種方式,可是Dom也有它的優勢,它比較直觀,在一些方面比SAX方式比較簡單。在xml文檔比較小的狀況下也能夠考慮使用dom方式。
Dom方式解析的核心代碼以下:
1 public static List<channel> getChannelList(InputStream stream)
2 {
3 List<channel> list=new ArrayList<channel>();
4
5 //獲得 DocumentBuilderFactory 對象, 由該對象能夠獲得 DocumentBuilder 對象
6 DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
7
8 try {
9 //獲得DocumentBuilder對象
10 DocumentBuilder builder=factory.newDocumentBuilder();
11 //獲得表明整個xml的Document對象
12 Document document=builder.parse(stream);
13 //獲得 "根節點"
14 Element root=document.getDocumentElement();
15 //獲取根節點的全部items的節點
16 NodeList items=root.getElementsByTagName("item");
17 //遍歷全部節點
18 for(int i=0;i<items.getLength();i++)
19 {
20 channel chann=new channel();
21 Element item=(Element)items.item(i);
22 chann.setId(item.getAttribute("id"));
23 chann.setUrl(item.getAttribute("url"));
24 chann.setName(item.getFirstChild().getNodeValue());
25 list.add(chann);
26 }
27
28 } catch (ParserConfigurationException e) {
29 // TODO Auto-generated catch block
30 e.printStackTrace();
31 } catch (SAXException e) {
32 // TODO Auto-generated catch block
33 e.printStackTrace();
34 } catch (IOException e) {
35 // TODO Auto-generated catch block
36 e.printStackTrace();
37 }
38
39 return list;
40 }
總結一下Dom解析的步驟(和sax相似)
一、調用 DocumentBuilderFactory.newInstance() 方法獲得 DOM 解析器工廠類實例。
二、調用解析器工廠實例類的 newDocumentBuilder() 方法獲得 DOM 解析器對象
三、調用 DOM 解析器對象的 parse() 方法解析 XML 文檔獲得表明整個文檔的 Document 對象。
4、總結
除以上三種外還有不少解析xml的方法,好比DOM4J、JDOM等等。但其基本的解析方式包含兩種,一種是事件驅動的(表明SAX),另外一種方式是基於文檔結構(表明DOM)。其餘的只不過語法不同而已。
附(本文示例運行截屏):
34. Java與C++對比。
(1) 最大的障礙在於速度:解釋過的Java要比C的執行速度慢上約20倍。不管什麼都不能阻止Java語言進行編譯。寫做本書的時候,剛剛出現了一些準實時編譯器,它們能顯著加快速度。固然,咱們徹底有理由認爲會出現適用於更多流行平臺的純固有編譯器,但倘若沒有那些編譯器,因爲速度的限制,必須有些問題是Java不能解決的。
(2) 和C++同樣,Java也提供了兩種類型的註釋。
(3) 全部東西都必須置入一個類。不存在全局函數或者全局數據。若是想得到與全局函數等價的功能,可考慮將static方法和static數據置入一個類裏。注意沒有象結構、枚舉或者聯合這一類的東西,一切只有「類」(Class)!
(4) 全部方法都是在類的主體定義的。因此用C++的眼光看,彷佛全部函數都已嵌入,但實情並不是如何(嵌入的問題在後面講述)。
(5) 在Java中,類定義採起幾乎和C++同樣的形式。但沒有標誌結束的分號。沒有class foo這種形式的類聲明,只有類定義。
class aType()
void aMethod() {/* 方法主體 */}
}
(6) Java中沒有做用域範圍運算符「::」。Java利用點號作全部的事情,但能夠不用考慮它,由於只能在一個類裏定義元素。即便那些方法定義,也必須在一個類的內部,因此根本沒有必要指定做用域的範圍。咱們注意到的一項差別是對static方法的調用:使用ClassName.methodName()。除此之外,package(包)的名字是用點號創建的,並能用import關鍵字實現C++的「#include」的一部分功能。例以下面這個語句:
import java.awt.*;
(#include並不直接映射成import,但在使用時有相似的感受。)
(7) 與C++相似,Java含有一系列「主類型」(Primitive type),以實現更有效率的訪問。在Java中,這些類型包括boolean,char,byte,short,int,long,float以及double。全部主類型的大小都是固有的,且與具體的機器無關(考慮到移植的問題)。這確定會對性能形成必定的影響,具體取決於不一樣的機器。對類型的檢查和要求在Java裏變得更苛刻。例如:
■條件表達式只能是boolean(布爾)類型,不可以使用整數。
■必須使用象X+Y這樣的一個表達式的結果;不能僅僅用「X+Y」來實現「反作用」。
(8) char(字符)類型使用國際通用的16位Unicode字符集,因此能自動錶達大多數國家的字符。
(9) 靜態引用的字串會自動轉換成String對象。和C及C++不一樣,沒有獨立的靜態字符數組字串可供使用。
(10) Java增添了三個右移位運算符「>>>」,具備與「邏輯」右移位運算符相似的功用,可在最末尾插入零值。「>>」則會在移位的同時插入符號位(即「算術」移位)。
(11) 儘管表面上相似,但與C++相比,Java數組採用的是一個頗爲不一樣的結構,並具備獨特的行爲。有一個只讀的length成員,經過它可知道數組有多大。並且一旦超過數組邊界,運行期檢查會自動丟棄一個異常。全部數組都是在內存「堆」裏建立的,咱們可將一個數組分配給另外一個(只是簡單地複製數組句柄)。數組標識符屬於第一級對象,它的全部方法一般都適用於其餘全部對象。
(12) 對於全部不屬於主類型的對象,都只能經過new命令建立。和C++不一樣,Java沒有相應的命令能夠「在堆棧上」建立不屬於主類型的對象。全部主類型都只能在堆棧上建立,同時不使用new命令。全部主要的類都有本身的「封裝(器)」類,因此可以經過new建立等價的、之內存「堆」爲基礎的對象(主類型數組是一個例外:它們可象C++那樣經過集合初始化進行分配,或者使用new)。
(13) Java中沒必要進行提早聲明。若想在定義前使用一個類或方法,只需直接使用它便可——編譯器會保證使用恰當的定義。因此和在C++中不一樣,咱們不會碰到任何涉及提早引用的問題。
(14) Java沒有預處理機。若想使用另外一個庫裏的類,只需使用import命令,並指定庫名便可。不存在相似於預處理機的宏。
(15) Java用包代替了命名空間。因爲將全部東西都置入一個類,並且因爲採用了一種名爲「封裝」的機制,它能針對類名進行相似於命名空間分解的操做,因此命名的問題再也不進入咱們的考慮之列。數據包也會在單獨一個庫名下收集庫的組件。咱們只需簡單地「import」(導入)一個包,剩下的工做會由編譯器自動完成。
(16) 被定義成類成員的對象句柄會自動初始化成null。對基本類數據成員的初始化在Java裏獲得了可靠的保障。若不明確地進行初始化,它們就會獲得一個默認值(零或等價的值)。可對它們進行明確的初始化(顯式初始化):要麼在類內定義它們,要麼在構建器中定義。採用的語法比C++的語法更容易理解,並且對於static和非static成員來講都是固定不變的。咱們沒必要從外部定義static成員的存儲方式,這和C++是不一樣的。
(17) 在Java裏,沒有象C和C++那樣的指針。用new建立一個對象的時候,會得到一個引用(本書一直將其稱做「句柄」)。例如:
String s = new String("howdy");
然而,C++引用在建立時必須進行初始化,並且不可重定義到一個不一樣的位置。但Java引用並不必定侷限於建立時的位置。它們可根據狀況任意定義,這便消除了對指針的部分需求。在C和C++裏大量採用指針的另外一個緣由是爲了能指向任意一個內存位置(這同時會使它們變得不安全,也是Java不提供這一支持的緣由)。指針一般被看做在基本變量數組中四處移動的一種有效手段。Java容許咱們以更安全的形式達到相同的目標。解決指針問題的終極方法是「固有方法」(已在附錄A討論)。將指針傳遞給方法時,一般不會帶來太大的問題,由於此時沒有全局函數,只有類。並且咱們可傳遞對對象的引用。Java語言最開始聲稱本身「徹底不採用指針!」但隨着許多程序員都質問沒有指針如何工做?因而後來又聲明「採用受到限制的指針」。你們可自行判斷它是否「真」的是一個指針。但無論在何種狀況下,都不存在指針「算術」。
(18) Java提供了與C++相似的「構建器」(Constructor)。若是不本身定義一個,就會得到一個默認構建器。而若是定義了一個非默認的構建器,就不會爲咱們自動定義默認構建器。這和C++是同樣的。注意沒有複製構建器,由於全部自變量都是按引用傳遞的。
(19) Java中沒有「破壞器」(Destructor)。變量不存在「做用域」的問題。一個對象的「存在時間」是由對象的存在時間決定的,並不是由垃圾收集器決定。有個finalize()方法是每個類的成員,它在某種程度上相似於C++的「破壞器」。但finalize()是由垃圾收集器調用的,並且只負責釋放「資源」(如打開的文件、套接字、端口、URL等等)。如需在一個特定的地點作某樣事情,必須建立一個特殊的方法,並調用它,不能依賴finalize()。而在另外一方面,C++中的全部對象都會(或者說「應該」)破壞,但並不是Java中的全部對象都會被看成「垃圾」收集掉。因爲Java不支持破壞器的概念,因此在必要的時候,必須謹慎地建立一個清除方法。並且針對類內的基礎類以及成員對象,須要明確調用全部清除方法。
(20) Java具備方法「過載」機制,它的工做原理與C++函數的過載幾乎是徹底相同的。
(21) Java不支持默認自變量。
(22) Java中沒有goto。它採起的無條件跳起色制是「break 標籤」或者「continue 標準」,用於跳出當前的多重嵌套循環。
(23) Java採用了一種單根式的分級結構,所以全部對象都是從根類Object統一繼承的。而在C++中,咱們可在任何地方啓動一個新的繼承樹,因此最後每每看到包含了大量樹的「一片森林」。在Java中,咱們不管如何都只有一個分級結構。儘管這表面上看彷佛形成了限制,但因爲咱們知道每一個對象確定至少有一個Object接口,因此每每能得到更強大的能力。C++目前彷佛是惟一沒有強制單根結構的惟一一種OO語言。
(24) Java沒有模板或者參數化類型的其餘形式。它提供了一系列集合:Vector(向量),Stack(堆棧)以及Hashtable(散列表),用於容納Object引用。利用這些集合,咱們的一系列要求可獲得知足。但這些集合並不是是爲實現象C++「標準模板庫」(STL)那樣的快速調用而設計的。Java 1.2中的新集合顯得更加完整,但仍不具有正宗模板那樣的高效率使用手段。
(25) 「垃圾收集」意味着在Java中出現內存漏洞的狀況會少得多,但也並不是徹底不可能(若調用一個用於分配存儲空間的固有方法,垃圾收集器就不能對其進行跟蹤監視)。然而,內存漏洞和資源漏洞可能是因爲編寫不當的finalize()形成的,或是因爲在已分配的一個塊尾釋放一種資源形成的(「破壞器」在此時顯得特別方便)。垃圾收集器是在C++基礎上的一種極大進步,使許多編程問題消彌於無形之中。但對少數幾個垃圾收集器力有不逮的問題,它倒是不大適合的。但垃圾收集器的大量優勢也使這一處缺點顯得微不足道。
(26) Java內建了對多線程的支持。利用一個特殊的Thread類,咱們可經過繼承建立一個新線程(放棄了run()方法)。若將synchronized(同步)關鍵字做爲方法的一個類型限制符使用,相互排斥現象會在對象這一級發生。在任何給定的時間,只有一個線程能使用一個對象的synchronized方法。在另外一方面,一個synchronized方法進入之後,它首先會「鎖定」對象,防止其餘任何synchronized方法再使用那個對象。只有退出了這個方法,纔會將對象「解鎖」。在線程之間,咱們仍然要負責實現更復雜的同步機制,方法是建立本身的「監視器」類。遞歸的synchronized方法能夠正常運做。若線程的優先等級相同,則時間的「分片」不能獲得保證。
(27) 咱們不是象C++那樣控制聲明代碼塊,而是將訪問限定符(public,private和protected)置入每一個類成員的定義裏。若未規定一個「顯式」(明確的)限定符,就會默認爲「友好的」(friendly)。這意味着同一個包裏的其餘元素也能夠訪問它(至關於它們都成爲C++的「friends」——朋友),但不可由包外的任何元素訪問。類——以及類內的每一個方法——都有一個訪問限定符,決定它是否能在文件的外部「可見」。private關鍵字一般不多在Java中使用,由於與排斥同一個包內其餘類的訪問相比,「友好的」訪問一般更加有用。然而,在多線程的環境中,對private的恰當運用是很是重要的。Java的protected關鍵字意味着「可由繼承者訪問,亦可由包內其餘元素訪問」。注意Java沒有與C++的protected關鍵字等價的元素,後者意味着「只能由繼承者訪問」(之前可用「private protected」實現這個目的,但這一對關鍵字的組合已被取消了)。
(28) 嵌套的類。在C++中,對類進行嵌套有助於隱藏名稱,並便於代碼的組織(但C++的「命名空間」已使名稱的隱藏顯得多餘)。Java的「封裝」或「打包」概念等價於C++的命名空間,因此再也不是一個問題。Java 1.1引入了「內部類」的概念,它祕密保持指向外部類的一個句柄——建立內部類對象的時候須要用到。這意味着內部類對象也許能訪問外部類對象的成員,毋需任何條件——就好象那些成員直接隸屬於內部類對象同樣。這樣便爲回調問題提供了一個更優秀的方案——C++是用指向成員的指針解決的。
(29) 因爲存在前面介紹的那種內部類,因此Java裏沒有指向成員的指針。
(30) Java不存在「嵌入」(inline)方法。Java編譯器也許會自行決定嵌入一個方法,但咱們對此沒有更多的控制權力。在Java中,可爲一個方法使用final關鍵字,從而「建議」進行嵌入操做。然而,嵌入函數對於C++的編譯器來講也只是一種建議。
(31) Java中的繼承具備與C++相同的效果,但採用的語法不一樣。Java用extends關鍵字標誌從一個基礎類的繼承,並用super關鍵字指出準備在基礎類中調用的方法,它與咱們當前所在的方法具備相同的名字(然而,Java中的super關鍵字只容許咱們訪問父類的方法——亦即分級結構的上一級)。經過在C++中設定基礎類的做用域,咱們可訪問位於分級結構較深處的方法。亦可用super關鍵字調用基礎類構建器。正如早先指出的那樣,全部類最終都會從Object裏自動繼承。和C++不一樣,不存在明確的構建器初始化列表。但編譯器會強迫咱們在構建器主體的開頭進行所有的基礎類初始化,並且不容許咱們在主體的後面部分進行這一工做。經過組合運用自動初始化以及來自未初始化對象句柄的異常,成員的初始化可獲得有效的保證。
public class Foo extends Bar {
public Foo(String msg) {
super(msg); // Calls base constructor
}
public baz(int i) { // Override
super.baz(i); // Calls base method
}
}
(32) Java中的繼承不會改變基礎類成員的保護級別。咱們不能在Java中指定public,private或者protected繼承,這一點與C++是相同的。此外,在衍生類中的優先方法不能減小對基礎類方法的訪問。例如,假設一個成員在基礎類中屬於public,而咱們用另外一個方法代替了它,那麼用於替換的方法也必須屬於public(編譯器會自動檢查)。
(33) Java提供了一個interface關鍵字,它的做用是建立抽象基礎類的一個等價物。在其中填充抽象方法,且沒有數據成員。這樣一來,對於僅僅設計成一個接口的東西,以及對於用extends關鍵字在現有功能基礎上的擴展,二者之間便產生了一個明顯的差別。不值得用abstract關鍵字產生一種相似的效果,由於咱們不能建立屬於那個類的一個對象。一個abstract(抽象)類可包含抽象方法(儘管並不要求在它裏面包含什麼東西),但它也能包含用於具體實現的代碼。所以,它被限制成一個單一的繼承。經過與接口聯合使用,這一方案避免了對相似於C++虛擬基礎類那樣的一些機制的須要。
爲建立可進行「例示」(即建立一個實例)的一個interface(接口)的版本,需使用implements關鍵字。它的語法相似於繼承的語法,以下所示:
public interface Face {
public void smile();
}
public class Baz extends Bar implements Face {
public void smile( ) {
System.out.println("a warm smile");
}
}
(34) Java中沒有virtual關鍵字,由於全部非static方法都確定會用到動態綁定。在Java中,程序員沒必要自行決定是否使用動態綁定。C++之因此採用了virtual,是因爲咱們對性能進行調整的時候,可經過將其省略,從而得到執行效率的少許提高(或者換句話說:「若是不用,就不必爲它付出代價」)。virtual常常會形成必定程度的混淆,並且得到使人不快的結果。final關鍵字爲性能的調整規定了一些範圍——它向編譯器指出這種方法不能被取代,因此它的範圍可能被靜態約束(並且成爲嵌入狀態,因此使用C++非virtual調用的等價方式)。這些優化工做是由編譯器完成的。
(35) Java不提供多重繼承機制(MI),至少不象C++那樣作。與protected相似,MI表面上是一個很不錯的主意,但只有真正面對一個特定的設計問題時,才知道本身須要它。因爲Java使用的是「單根」分級結構,因此只有在極少的場合才須要用到MI。interface關鍵字會幫助咱們自動完成多個接口的合併工做。
(36) 運行期的類型標識功能與C++極爲類似。例如,爲得到與句柄X有關的信息,可以使用下述代碼:
X.getClass().getName();
爲進行一個「類型安全」的緊縮造型,可以使用:
derived d = (derived)base;
這與舊式風格的C造型是同樣的。編譯器會自動調用動態造型機制,不要求使用額外的語法。儘管它並不象C++的「new casts」那樣具備易於定位造型的優勢,但Java會檢查使用狀況,並丟棄那些「異常」,因此它不會象C++那樣容許壞造型的存在。
(37) Java採起了不一樣的異常控制機制,由於此時已經不存在構建器。可添加一個finally從句,強制執行特定的語句,以便進行必要的清除工做。Java中的全部異常都是從基礎類Throwable裏繼承而來的,因此可確保咱們獲得的是一個通用接口。
public void f(Obj b) throws IOException {
myresource mr = b.createResource();
try {
mr.UseResource();
} catch (MyException e) {
// handle my exception
} catch (Throwable e) {
// handle all other exceptions
} finally {
mr.dispose(); // special cleanup
}
}
(38) Java的異常規範比C++的出色得多。丟棄一個錯誤的異常後,不是象C++那樣在運行期間調用一個函數,Java異常規範是在編譯期間檢查並執行的。除此之外,被取代的方法必須遵照那一方法的基礎類版本的異常規範:它們可丟棄指定的異常或者從那些異常衍生出來的其餘異常。這樣一來,咱們最終獲得的是更爲「健壯」的異常控制代碼。
(39) Java具備方法過載的能力,但不容許運算符過載。String類不能用+和+=運算符鏈接不一樣的字串,並且String表達式使用自動的類型轉換,但那是一種特殊的內建狀況。
(40) 經過事先的約定,C++中常常出現的const問題在Java裏已獲得了控制。咱們只能傳遞指向對象的句柄,本地副本永遠不會爲咱們自動生成。若但願使用相似C++按值傳遞那樣的技術,可調用clone(),生成自變量的一個本地副本(儘管clone()的設計依然尚顯粗糙——參見第12章)。根本不存在被自動調用的副本構建器。爲建立一個編譯期的常數值,可象下面這樣編碼:
static final int SIZE = 255
static final int BSIZE = 8 * SIZE
(41) 因爲安全方面的緣由,「應用程序」的編程與「程序片」的編程之間存在着顯著的差別。一個最明顯的問題是程序片不容許咱們進行磁盤的寫操做,由於這樣作會形成從遠程站點下載的、不明來歷的程序可能胡亂改寫咱們的磁盤。隨着Java 1.1對數字簽名技術的引用,這一狀況已有所改觀。根據數字簽名,咱們可確切知道一個程序片的所有做者,並驗證他們是否已得到受權。Java 1.2會進一步加強程序片的能力。
(42) 因爲Java在某些場合可能顯得限制太多,因此有時不肯用它執行象直接訪問硬件這樣的重要任務。Java解決這個問題的方案是「固有方法」,容許咱們調用由其餘語言寫成的函數(目前只支持C和C++)。這樣一來,咱們就確定可以解決與平臺有關的問題(採用一種不可移植的形式,但那些代碼隨後會被隔離起來)。程序片不能調用固有方法,只有應用程序才能夠。
(43) Java提供對註釋文檔的內建支持,因此源碼文件也能夠包含它們本身的文檔。經過一個單獨的程序,這些文檔信息能夠提取出來,並從新格式化成HTML。這無疑是文檔管理及應用的極大進步。
(44) Java包含了一些標準庫,用於完成特定的任務。C++則依靠一些非標準的、由其餘廠商提供的庫。這些任務包括(或不久就要包括):
■連網
■數據庫鏈接(經過JDBC)
■多線程
■分佈式對象(經過RMI和CORBA)
■壓縮
■商貿
因爲這些庫簡單易用,並且很是標準,因此能極大加快應用程序的開發速度。
(45) Java 1.1包含了Java Beans標準,後者可建立在可視編程環境中使用的組件。因爲遵照一樣的標準,因此可視組件可以在全部廠商的開發環境中使用。因爲咱們並不依賴一家廠商的方案進行可視組件的設計,因此組件的選擇餘地會加大,並可提升組件的效能。除此以外,Java Beans的設計很是簡單,便於程序員理解;而那些由不一樣的廠商開發的專用組件框架則要求進行更深刻的學習。
(46) 若訪問Java句柄失敗,就會丟棄一次異常。這種丟棄測試並不必定要正好在使用一個句柄以前進行。根據Java的設計規範,只是說異常必須以某種形式丟棄。許多C++運行期系統也能丟棄那些因爲指針錯誤形成的異常。
(47) Java一般顯得更爲健壯,爲此採起的手段以下:
■對象句柄初始化成null(一個關鍵字)
■句柄確定會獲得檢查,並在出錯時丟棄異常
■全部數組訪問都會獲得檢查,及時發現邊界違例狀況
■自動垃圾收集,防止出現內存漏洞
■明確、「傻瓜式」的異常控制機制
■爲多線程提供了簡單的語言支持
■對網絡程序片進行字節碼校驗
1.jdk7語法上
1.1二進制變量的表示,支持將整數類型用二進制來表示,用0b開頭。
1.2 Switch語句支持string類型
1.3 Try-with-resource語句
注意:實現java.lang.AutoCloseable接口的資源均可以放到try中,跟final裏面的關閉資源相似; 按照聲明逆序關閉資源 ;Try塊拋出的異常經過Throwable.getSuppressed獲取
1.4 Catch多個異常 說明:Catch異常類型爲final; 生成Bytecode 會比多個catch小; Rethrow時保持異常類型
1.5 數字類型的下劃線表示 更友好的表示方式,不過要注意下劃線添加的一些標準
1.6 泛型實例的建立能夠經過類型推斷來簡化 能夠去掉後面new部分的泛型類型,只用<>就能夠了
1.7在可變參數方法中傳遞非具體化參數,改進編譯警告和錯誤
1.8 信息更豐富的回溯追蹤 就是上面try中try語句和裏面的語句同時拋出異常時,異常棧的信息
2. NIO2的一些新特性
1.java.nio.file 和java.nio.file.attribute包 支持更詳細屬性,好比權限,全部者
2. symbolic and hard links支持
3. Path訪問文件系統,Files支持各類文件操做
4.高效的訪問metadata信息
5.遞歸查找文件樹,文件擴展搜索
6.文件系統修改通知機制
7.File類操做API兼容
8.文件隨機訪問加強 mapping a region,locl a region,絕對位置讀取
9. AIO Reactor(基於事件)和Proactor
2.1IO and New IO 監聽文件系統變化通知
經過FileSystems.getDefault().newWatchService()獲取watchService,而後將須要監聽的path目錄註冊到這個watchservice中,對於這個目錄的文件修改,新增,刪除等實踐能夠配置,而後就自動能監聽到響應的事件。
2.2 IO and New IO遍歷文件樹 ,經過繼承SimpleFileVisitor類,實現事件遍歷目錄樹的操做,而後經過Files.walkFileTree(listDir, opts, Integer.MAX_VALUE, walk);這個API來遍歷目錄樹
2.3 AIO異步IO 文件和網絡 異步IO在java
NIO2實現了,都是用AsynchronousFileChannel,AsynchronousSocketChanne等實現,關於同步阻塞IO,同步非阻塞IO,異步阻塞IO和異步非阻塞IO。Java NIO2中就實現了操做系統的異步非阻塞IO。
3. JDBC 4.1
3.1.能夠使用try-with-resources自動關閉Connection, ResultSet, 和 Statement資源對象
3.2. RowSet 1.1:引入RowSetFactory接口和RowSetProvider類,能夠建立JDBC driver支持的各類 row sets,這裏的rowset實現其實就是將sql語句上的一些操做轉爲方法的操做,封裝了一些功能。
3.3. JDBC-ODBC驅動會在jdk8中刪除
4. 併發工具加強
4.1.fork-join
最大的加強,充分利用多核特性,將大問題分解成各個子問題,由多個cpu能夠同時解決多個子問題,最後合併結果,繼承RecursiveTask,實現compute方法,而後調用fork計算,最後用join合併結果。
4.2.ThreadLocalRandon 併發下隨機數生成類,保證併發下的隨機數生成的線程安全,實際上就是使用threadlocal
4.3. phaser 相似cyclebarrier和countdownlatch,不過能夠動態添加資源減小資源
5. Networking加強
新增URLClassLoader close方法,能夠及時關閉資源,後續從新加載class文件時不會致使資源被佔用或者沒法釋放問題
URLClassLoader.newInstance(new URL[]{}).close();
新增Sockets Direct Protocol
繞過操做系統的數據拷貝,將數據從一臺機器的內存數據經過網絡直接傳輸到另一臺機器的內存中
6. Multithreaded Custom Class Loaders
解決併發下加載class可能致使的死鎖問題,這個是jdk1.6的一些新版本就解決了,jdk7也作了一些優化。有興趣能夠仔細從官方文檔詳細瞭解
JDK1.8的新特性
1、接口的默認方法
Java 8容許咱們給接口添加一個非抽象的方法實現,只須要使用 default關鍵字便可,這個特徵又叫作擴展方法。
2、Lambda 表達式
在Java 8 中你就不必使用這種傳統的匿名對象的方式了,Java 8提供了更簡潔的語法,lambda表達式:
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
3、函數式接口
Lambda表達式是如何在java的類型系統中表示的呢?每個lambda表達式都對應一個類型,一般是接口類型。而「函數式接口」是指僅僅只包含一個抽象方法的接口,每個該類型的lambda表達式都會被匹配到這個抽象方法。由於 默認方法 不算抽象方法,因此你也能夠給你的函數式接口添加默認方法。
4、方法與構造函數引用
Java 8 容許你使用 :: 關鍵字來傳遞方法或者構造函數引用,上面的代碼展現瞭如何引用一個靜態方法,咱們也能夠引用一個對象的方法:
converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted);
5、Lambda 做用域
在lambda表達式中訪問外層做用域和老版本的匿名對象中的方式很類似。你能夠直接訪問標記了final的外層局部變量,或者實例的字段以及靜態變量。
6、訪問局部變量
能夠直接在lambda表達式中訪問外層的局部變量:
7、訪問對象字段與靜態變量
和本地變量不一樣的是,lambda內部對於實例的字段以及靜態變量是便可讀又可寫。該行爲和匿名對象是一致的:
8、訪問接口的默認方法
JDK 1.8 API包含了不少內建的函數式接口,在老Java中經常使用到的好比Comparator或者Runnable接口,這些接口都增長了@FunctionalInterface註解以便能用在lambda上。
Java 8 API一樣還提供了不少全新的函數式接口來讓工做更加方便,有一些接口是來自Google Guava庫裏的,即使你對這些很熟悉了,仍是有必要看看這些是如何擴展到lambda上使用的。
1.jdk7語法上
1.1二進制變量的表示,支持將整數類型用二進制來表示,用0b開頭。
// 全部整數 int, short,long,byte均可以用二進制表示
// An 8-bit 'byte' value:
byte aByte = (byte) 0b00100001;
// A 16-bit 'short' value:
short aShort = (short) 0b1010000101000101;
// Some 32-bit 'int' values:
intanInt1 = 0b10100001010001011010000101000101;
intanInt2 = 0b101;
intanInt3 = 0B101; // The B can be upper or lower case.
// A 64-bit 'long' value. Note the "L" suffix:
long aLong = 0b1010000101000101101000010100010110100001010001011010000101000101L;
// 二進制在數組等的使用
final int[] phases = { 0b00110001, 0b01100010, 0b11000100, 0b10001001,
0b00010011, 0b00100110, 0b01001100, 0b10011000 };
1.2 Switch語句支持string類型
public static String getTypeOfDayWithSwitchStatement(String dayOfWeekArg) {
String typeOfDay;
switch (dayOfWeekArg) {
case "Monday":
typeOfDay = "Start of work week";
break;
case "Tuesday":
case "Wednesday":
case "Thursday":
typeOfDay = "Midweek";
break;
case "Friday":
typeOfDay = "End of work week";
break;
case "Saturday":
case "Sunday":
typeOfDay = "Weekend";
break;
default:
throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeekArg);
}
return typeOfDay;
}
1.3 Try-with-resource語句
注意:實現java.lang.AutoCloseable接口的資源均可以放到try中,跟final裏面的關閉資源相似; 按照聲明逆序關閉資源 ;Try塊拋出的異常經過Throwable.getSuppressed獲取
try (java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName);
java.io.BufferedWriter writer = java.nio.file.Files
.newBufferedWriter(outputFilePath, charset)) {
// Enumerate each entry
for (java.util.Enumeration entries = zf.entries(); entries
.hasMoreElements();) {
// Get the entry name and write it to the output file
String newLine = System.getProperty("line.separator");
String zipEntryName = ((java.util.zip.ZipEntry) entries
.nextElement()).getName() + newLine;
writer.write(zipEntryName, 0, zipEntryName.length());
}
}
1.4 Catch多個異常 說明:Catch異常類型爲final; 生成Bytecode 會比多個catch小; Rethrow時保持異常類型
public static void main(String[] args) throws Exception {
try {
testthrows();
} catch (IOException | SQLException ex) {
throw ex;
}
}
public static void testthrows() throws IOException, SQLException {
}
1.5 數字類型的下劃線表示 更友好的表示方式,不過要注意下劃線添加的一些標準,能夠參考下面的示例
long creditCardNumber = 1234_5678_9012_3456L;
long socialSecurityNumber = 999_99_9999L;
float pi = 3.14_15F;
long hexBytes = 0xFF_EC_DE_5E;
long hexWords = 0xCAFE_BABE;
long maxLong = 0x7fff_ffff_ffff_ffffL;
byte nybbles = 0b0010_0101;
long bytes = 0b11010010_01101001_10010100_10010010;
//float pi1 = 3_.1415F; // Invalid; cannot put underscores adjacent to a decimal point
//float pi2 = 3._1415F; // Invalid; cannot put underscores adjacent to a decimal point
//long socialSecurityNumber1= 999_99_9999_L; // Invalid; cannot put underscores prior to an L suffix
//int x1 = _52; // This is an identifier, not a numeric literal
int x2 = 5_2; // OK (decimal literal)
//int x3 = 52_; // Invalid; cannot put underscores at the end of a literal
int x4 = 5_______2; // OK (decimal literal)
//int x5 = 0_x52; // Invalid; cannot put underscores in the 0x radix prefix
//int x6 = 0x_52; // Invalid; cannot put underscores at the beginning of a number
int x7 = 0x5_2; // OK (hexadecimal literal)
//int x8 = 0x52_; // Invalid; cannot put underscores at the end of a number
int x9 = 0_52; // OK (octal literal)
int x10 = 05_2; // OK (octal literal)
//int x11 = 052_; // Invalid; cannot put underscores at the end of a number
1.6 泛型實例的建立能夠經過類型推斷來簡化 能夠去掉後面new部分的泛型類型,只用<>就能夠了。
//使用泛型前
List strList = new ArrayList();
List<String> strList4 = new ArrayList<String>();
List<Map<String, List<String>>> strList5 = new ArrayList<Map<String, List<String>>>();
//編譯器使用尖括號 (<>) 推斷類型
List<String> strList0 = new ArrayList<String>();
List<Map<String, List<String>>> strList1 = new ArrayList<Map<String, List<String>>>();
List<String> strList2 = new ArrayList<>();
List<Map<String, List<String>>> strList3 = new ArrayList<>();
List<String> list = new ArrayList<>();
list.add("A");
// The following statement should fail since addAll expects
// Collection<? extends String>
//list.addAll(new ArrayList<>());
1.7在可變參數方法中傳遞非具體化參數,改進編譯警告和錯誤
Heap pollution 指一個變量被指向另一個不是相同類型的變量。例如
List l = new ArrayList<Number>();
List<String> ls = l; // unchecked warning
l.add(0, new Integer(42)); // another unchecked warning
String s = ls.get(0); // ClassCastException is thrown
Jdk7:
public static <T> void addToList (List<T> listArg, T... elements) {
for (T x : elements) {
listArg.add(x);
}
}
你會獲得一個warning
warning: [varargs] Possible heap pollution from parameterized vararg type
要消除警告,能夠有三種方式
1.加 annotation @SafeVarargs
2.加 annotation @SuppressWarnings({"unchecked", "varargs"})
3.使用編譯器參數 –Xlint:varargs;
1.8 信息更豐富的回溯追蹤 就是上面try中try語句和裏面的語句同時拋出異常時,異常棧的信息
java.io.IOException
§? at Suppress.write(Suppress.java:19)
§? at Suppress.main(Suppress.java:8)
§? Suppressed: java.io.IOException
§? at Suppress.close(Suppress.java:24)
§? at Suppress.main(Suppress.java:9)
§? Suppressed: java.io.IOException
§? at Suppress.close(Suppress.java:24)
§? at Suppress.main(Suppress.java:9)
2. NIO2的一些新特性
1.java.nio.file 和java.nio.file.attribute包 支持更詳細屬性,好比權限,全部者
2. symbolic and hard links支持
3. Path訪問文件系統,Files支持各類文件操做
4.高效的訪問metadata信息
5.遞歸查找文件樹,文件擴展搜索
6.文件系統修改通知機制
7.File類操做API兼容
8.文件隨機訪問加強 mapping a region,locl a region,絕對位置讀取
9. AIO Reactor(基於事件)和Proactor
下面列一些示例:
2.1IO and New IO 監聽文件系統變化通知
經過FileSystems.getDefault().newWatchService()獲取watchService,而後將須要監聽的path目錄註冊到這個watchservice中,對於這個目錄的文件修改,新增,刪除等實踐能夠配置,而後就自動能監聽到響應的事件。
private WatchService watcher;
public TestWatcherService(Path path) throws IOException {
watcher = FileSystems.getDefault().newWatchService();
path.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
}
public void handleEvents() throws InterruptedException {
while (true) {
WatchKey key = watcher.take();
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind kind = event.kind();
if (kind == OVERFLOW) {// 事件可能lost or discarded
continue;
}
WatchEvent<Path> e = (WatchEvent<Path>) event;
Path fileName = e.context();
System.out.printf("Event %s has happened,which fileName is %s%n",kind.name(), fileName);
}
if (!key.reset()) {
break;
}
2.2 IO and New IO遍歷文件樹 ,經過繼承SimpleFileVisitor類,實現事件遍歷目錄樹的操做,而後經過Files.walkFileTree(listDir, opts, Integer.MAX_VALUE, walk);這個API來遍歷目錄樹
private void workFilePath() {
Path listDir = Paths.get("/tmp"); // define the starting file
ListTree walk = new ListTree();
…Files.walkFileTree(listDir, walk);…
// 遍歷的時候跟蹤連接
EnumSet opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
try {
Files.walkFileTree(listDir, opts, Integer.MAX_VALUE, walk);
} catch (IOException e) {
System.err.println(e);
}
class ListTree extends SimpleFileVisitor<Path> {// NIO2 遞歸遍歷文件目錄的接口
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
System.out.println("Visited directory: " + dir.toString());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
System.out.println(exc);
return FileVisitResult.CONTINUE;
}
}
2.3 AIO異步IO 文件和網絡 異步IO在java
NIO2實現了,都是用AsynchronousFileChannel,AsynchronousSocketChanne等實現,關於同步阻塞IO,同步非阻塞IO,異步阻塞IO和異步非阻塞IO在ppt的這頁上下面備註有說明,有興趣的能夠深刻了解下。Java NIO2中就實現了操做系統的異步非阻塞IO。
// 使用AsynchronousFileChannel.open(path, withOptions(),
// taskExecutor))這個API對異步文件IO的處理
public static void asyFileChannel2() {
final int THREADS = 5;
ExecutorService taskExecutor = Executors.newFixedThreadPool(THREADS);
String encoding = System.getProperty("file.encoding");
List<Future<ByteBuffer>> list = new ArrayList<>();
int sheeps = 0;
Path path = Paths.get("/tmp",
"store.txt");
try (AsynchronousFileChannel asynchronousFileChannel = AsynchronousFileChannel
.open(path, withOptions(), taskExecutor)) {
for (int i = 0; i < 50; i++) {
Callable<ByteBuffer> worker = new Callable<ByteBuffer>() {
@Override
public ByteBuffer call() throws Exception {
ByteBuffer buffer = ByteBuffer
.allocateDirect(ThreadLocalRandom.current()
.nextInt(100, 200));
asynchronousFileChannel.read(buffer, ThreadLocalRandom
……
3. JDBC 4.1
3.1.能夠使用try-with-resources自動關閉Connection, ResultSet, 和 Statement資源對象
3.2. RowSet 1.1:引入RowSetFactory接口和RowSetProvider類,能夠建立JDBC driver支持的各類 row sets,這裏的rowset實現其實就是將sql語句上的一些操做轉爲方法的操做,封裝了一些功能。
3.3. JDBC-ODBC驅動會在jdk8中刪除
try (Statement stmt = con.createStatement()) {
RowSetFactory aFactory = RowSetProvider.newFactory();
CachedRowSet crs = aFactory.createCachedRowSet();
RowSetFactory rsf = RowSetProvider.newFactory("com.sun.rowset.RowSetFactoryImpl", null);
WebRowSet wrs = rsf.createWebRowSet();
createCachedRowSet
createFilteredRowSet
createJdbcRowSet
createJoinRowSet
createWebRowSet
4. 併發工具加強
4.1.fork-join
最大的加強,充分利用多核特性,將大問題分解成各個子問題,由多個cpu能夠同時解決多個子問題,最後合併結果,繼承RecursiveTask,實現compute方法,而後調用fork計算,最後用join合併結果。
class Fibonacci extends RecursiveTask<Integer> {
final int n;
Fibonacci(int n) {
this.n = n;
}
private int compute(int small) {
final int[] results = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };
return results[small];
}
public Integer compute() {
if (n <= 10) {
return compute(n);
}
Fibonacci f1 = new Fibonacci(n - 1);
Fibonacci f2 = new Fibonacci(n - 2);
System.out.println("fork new thread for " + (n - 1));
f1.fork();
System.out.println("fork new thread for " + (n - 2));
f2.fork();
return f1.join() + f2.join();
}
}
4.2.ThreadLocalRandon 併發下隨機數生成類,保證併發下的隨機數生成的線程安全,實際上就是使用threadlocal
final int MAX = 100000;
ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
long start = System.nanoTime();
for (int i = 0; i < MAX; i++) {
threadLocalRandom.nextDouble();
}
long end = System.nanoTime() - start;
System.out.println("use time1 : " + end);
long start2 = System.nanoTime();
for (int i = 0; i < MAX; i++) {
Math.random();
}
long end2 = System.nanoTime() - start2;
System.out.println("use time2 : " + end2);
4.3. phaser 相似cyclebarrier和countdownlatch,不過能夠動態添加資源減小資源
void runTasks(List<Runnable> tasks) {
final Phaser phaser = new Phaser(1); // "1" to register self
// create and start threads
for (final Runnable task : tasks) {
phaser.register();
new Thread() {
public void run() {
phaser.arriveAndAwaitAdvance(); // await all creation
task.run();
}
}.start();
}
// allow threads to start and deregister self
phaser.arriveAndDeregister();
}
5. Networking加強
新增URLClassLoader close方法,能夠及時關閉資源,後續從新加載class文件時不會致使資源被佔用或者沒法釋放問題
URLClassLoader.newInstance(new URL[]{}).close();
新增Sockets Direct Protocol
繞過操做系統的數據拷貝,將數據從一臺機器的內存數據經過網絡直接傳輸到另一臺機器的內存中
6. Multithreaded Custom Class Loaders
解決併發下加載class可能致使的死鎖問題,這個是jdk1.6的一些新版本就解決了,jdk7也作了一些優化。有興趣能夠仔細從官方文檔詳細瞭解
jdk7前:
Class Hierarchy:
class A extends B
class C extends D
ClassLoader Delegation Hierarchy:
Custom Classloader CL1:
directly loads class A
delegates to custom ClassLoader CL2 for class B
Custom Classloader CL2:
directly loads class C
delegates to custom ClassLoader CL1 for class D
Thread 1:
Use CL1 to load class A (locks CL1)
defineClass A triggers
loadClass B (try to lock CL2)
Thread 2:
Use CL2 to load class C (locks CL2)
defineClass C triggers
loadClass D (try to lock CL1)
Synchronization in the ClassLoader class wa
jdk7
Thread 1:
Use CL1 to load class A (locks CL1+A)
defineClass A triggers
loadClass B (locks CL2+B)
Thread 2:
Use CL2 to load class C (locks CL2+C)
defineClass C triggers
loadClass D (locks CL1+D)
7. Security 加強
7.1.提供幾種 ECC-based algorithms (ECDSA/ECDH) Elliptic Curve Cryptography (ECC)
7.2.禁用CertPath Algorithm Disabling
7.3. JSSE (SSL/TLS)的一些加強
8. Internationalization 加強 增長了對一些編碼的支持和增長了一些顯示方面的編碼設置等
1. New Scripts and Characters from Unicode 6.0.0
2. Extensible Support for ISO 4217 Currency Codes
Currency類添加:
getAvailableCurrencies
getNumericCode
getDisplayName
getDisplayName(Locale)
3. Category Locale Support
getDefault(Locale.Category)FORMAT DISPLAY
4. Locale Class Supports BCP47 and UTR35
UNICODE_LOCALE_EXTENSION
PRIVATE_USE_EXTENSION
Locale.Builder