BAT、網易、蘑菇街面試題整理-3

25. ThreadLocal的設計理念與做用html

Why ThreadLocal?前端

不管如何,要編寫一個多線程安全(Thread-safe)的程序是困難的,爲了讓線程共享資源,必須當心地對共享資源進行同步,同步帶來必定的效能延遲,而另外一方面,在處理同步的時候,又要注意對象的鎖定與釋放,避免產生死結,種種因素都使得編寫多線程程序變得困難。java

嘗試從另外一個角度來思考多線程共享資源的問題,既然共享資源這麼困難,那麼就乾脆不要共享,何不爲每一個線程創造一個資源的複本。將每個線程存取數據的行爲加以隔離,實現的方法就是給予每一個線程一個特定空間來保管該線程所獨享的資源python

 


什麼是ThreadLocal? www.2cto.comlinux

顧名思義它是local variable(線程局部變量)。它的功用很是簡單,就是爲每個使用該變量的線程都提供一個變量值的副本,是每個線程均可以獨立地改變本身的副本,而不會和其它線程的副本衝突。從線程的角度看,就好像每個線程都徹底擁有該變量。android

使用場景ios

To keep state with a thread (user-id, transaction-id, logging-id)
To cache objects which you need frequently
ThreadLocal類程序員

它主要由四個方法組成initialValue(),get(),set(T),remove(),其中值得注意的是initialValue(),該方法是一個protected的方法,顯然是爲了子類重寫而特地實現的。該方法返回當前線程在該線程局部變量的初始值,這個方法是一個延遲調用方法,在一個線程第1次調用get()或者set(Object)時才執行,而且僅執行1次。ThreadLocal中的確實實現直接返回一個null:web

 

ThreadLocal的原理面試

ThreadLocal是如何作到爲每個線程維護變量的副本的呢?其實實現的思路很簡單,在ThreadLocal類中有一個Map,用於存儲每個線程的變量的副本。好比下面的示例實現:

public class ThreadLocal
{
  private Map values = Collections.synchronizedMap(new HashMap());
  public Object get()
  {
    Thread curThread = Thread.currentThread();
    Object o = values.get(curThread);
    if (o == null && !values.containsKey(curThread))
    {
       o = initialValue();
       values.put(curThread, o);
    }
    return o;
 }

 public void set(Object newValue)
 {
    values.put(Thread.currentThread(), newValue);
 }

 public Object initialValue()
 {
    return null;
 }
}

ThreadLocal 的使用

使用方法一:

Hibernate的文檔時看到了關於使ThreadLocal管理多線程訪問的部分。具體代碼以下

1.  public static final ThreadLocal session = new ThreadLocal();
2.  public static Session currentSession() {
3.      Session s = (Session)session.get();
4.      //open a new session,if this session has none
5.   if(s == null){
6.      s = sessionFactory.openSession();
7.      session.set(s);
8.   }
      return s;
9. }

咱們逐行分析
1。 初始化一個ThreadLocal對象,ThreadLocal有三個成員方法 get()、set()、initialvalue()。
    若是不初始化initialvalue,則initialvalue返回null。
3。 session的get根據當前線程返回其對應的線程內部變量,也就是咱們須要的net.sf.hibernate.Session(至關於對應每一個數據庫鏈接).多線程狀況下共享數據庫連接是不安全的。ThreadLocal保證了每一個線程都有本身的s(數據庫鏈接)。
5。若是是該線程初次訪問,天然,s(數據庫鏈接)會是null,接着建立一個Session,具體就是行6。
6。建立一個數據庫鏈接實例 s
7。保存該數據庫鏈接s到ThreadLocal中。
8。若是當前線程已經訪問過數據庫了,則從session中get()就能夠獲取該線程上次獲取過的鏈接實例。

使用方法二

當要給線程初始化一個特殊值時,須要本身實現ThreadLocal的子類並重寫該方法,一般使用一個內部匿名類對ThreadLocal進行子類化,EasyDBO中建立jdbc鏈接上下文就是這樣作的:

 public class JDBCContext{
 private static Logger logger = Logger.getLogger(JDBCContext.class);
 private DataSource ds;
 protected Connection connection;
 private boolean isValid = true;
 private static ThreadLocal jdbcContext;
 
 private JDBCContext(DataSource ds){
  this.ds = ds;
  createConnection(); 
 }
 public static JDBCContext getJdbcContext(javax.sql.DataSource ds)
 { 
  if(jdbcContext==null)jdbcContext=new JDBCContextThreadLocal(ds);
  JDBCContext context = (JDBCContext) jdbcContext.get();
  if (context == null) {
   context = new JDBCContext(ds);
  }
  return context;
 }

 private static class JDBCContextThreadLocal extends ThreadLocal {
  public javax.sql.DataSource ds;
  public JDBCContextThreadLocal(javax.sql.DataSource ds)
  {
   this.ds=ds;
  }
  protected synchronized Object initialValue() {
   return new JDBCContext(ds);
  }
 }
}

使用單例模式,不一樣的線程調用getJdbcContext()得到本身的jdbcContext,都是經過JDBCContextThreadLocal 內置子類來得到JDBCContext對象的線程局部變量,這個變量


26. ThreadPool用法與優點。

剛剛研究了一下線程池,若是有不足之處,請你們不吝賜教,你們共同窗習、共同交流。
在什麼狀況下使用線程池?
 

    1.
單個任務處理的時間比較短 
    2.
將需處理的任務的數量大 

    
使用線程池的好處: 

    1.
減小在建立和銷燬線程上所花的時間以及系統資源的開銷 
    2.
如不使用線程池,有可能形成系統建立大量線程而致使消耗完系統內存以及過分切換 

線程池工做原理:

爲何要用線程池?

諸如 Web 服務器、數據庫服務器、文件服務器或郵件服務器之類的許多服務器應用程序都面向處理來自某些遠程來源的大量短小的任務。請求以某種方式到達服務器,這種方式多是經過網絡協議(例如 HTTPFTP POP)、經過 JMS 隊列或者可能經過輪詢數據庫。無論請求如何到達,服務器應用程序中常常出現的狀況是:單個任務處理的時間很短而請求的數目倒是巨大的。

構建服務器應用程序的一個過於簡單的模型應該是:每當一個請求到達就建立一個新線程,而後在新線程中爲請求服務。實際上,對於原型開發這種方法工做得很好,但若是試圖部署以這種方式運行的服務器應用程序,那麼這種方法的嚴重不足就很明顯。每一個請求對應一個線程(thread-per-request)方法的不足之一是:爲每一個請求建立一個新線程的開銷很大;爲每一個請求建立新線程的服務器在建立和銷燬線程上花費的時間和消耗的系統資源要比花在處理實際的用戶請求的時間和資源更多。

除了建立和銷燬線程的開銷以外,活動的線程也消耗系統資源。在一個 JVM 裏建立太多的線程可能會致使系統因爲過分消耗內存而用完內存或切換過分。爲了防止資源不足,服務器應用程序須要一些辦法來限制任何給定時刻處理的請求數目。

線程池爲線程生命週期開銷問題和資源不足問題提供瞭解決方案。經過對多個任務重用線程,線程建立的開銷被分攤到了多個任務上。其好處是,由於在請求到達時線程已經存在,因此無心中也消除了線程建立所帶來的延遲。這樣,就能夠當即爲請求服務,使應用程序響應更快。並且,經過適當地調整線程池中的線程數目,也就是當請求的數目超過某個閾值時,就強制其它任何新到的請求一直等待,直到得到一個線程來處理爲止,從而能夠防止資源不足。

線程池的替代方案

線程池遠不是服務器應用程序內使用多線程的惟一方法。如同上面所提到的,有時,爲每一個新任務生成一個新線程是十分明智的。然而,若是任務建立過於頻繁而任務的平均處理時間太短,那麼爲每一個任務生成一個新線程將會致使性能問題。

另外一個常見的線程模型是爲某一類型的任務分配一個後臺線程與任務隊列。AWT Swing 就使用這個模型,在這個模型中有一個 GUI 事件線程,致使用戶界面發生變化的全部工做都必須在該線程中執行。然而,因爲只有一個 AWT 線程,所以要在 AWT 線程中執行任務可能要花費至關長時間才能完成,這是不可取的。所以,Swing 應用程序常常須要額外的工做線程,用於運行時間很長的、同 UI 有關的任務。

每一個任務對應一個線程方法和單個後臺線程(single-background-thread)方法在某些情形下都工做得很是理想。每一個任務一個線程方法在只有少許運行時間很長的任務時工做得十分好。而只要調度可預見性不是很重要,則單個後臺線程方法就工做得十分好,如低優先級後臺任務就是這種狀況。然而,大多數服務器應用程序都是面向處理大量的短時間任務或子任務,所以每每但願具備一種可以以低開銷有效地處理這些任務的機制以及一些資源管理和定時可預見性的措施。線程池提供了這些優勢。

工做隊列

就線程池的實際實現方式而言,術語線程池有些令人誤解,由於線程池明顯的實如今大多數情形下並不必定產生咱們但願的結果。術語線程池先於 Java 平臺出現,所以它多是較少面向對象方法的產物。然而,該術語仍繼續普遍應用着。

雖然咱們能夠輕易地實現一個線程池類,其中客戶機類等待一個可用線程、將任務傳遞給該線程以便執行、而後在任務完成時將線程歸還給池,但這種方法卻存在幾個潛在的負面影響。例如在池爲空時,會發生什麼呢?試圖向池線程傳遞任務的調用者都會發現池爲空,在調用者等待一個可用的池線程時,它的線程將阻塞。咱們之因此要使用後臺線程的緣由之一經常是爲了防止正在提交的線程被阻塞。徹底堵住調用者,如在線程池的明顯的實現的狀況,能夠杜絕咱們試圖解決的問題的發生。

咱們一般想要的是同一組固定的工做線程相結合的工做隊列,它使用 wait() notify() 來通知等待線程新的工做已經到達了。該工做隊列一般被實現成具備相關監視器對象的某種鏈表。清單 1 顯示了簡單的合用工做隊列的示例。儘管 Thread API 沒有對使用 Runnable 接口強加特殊要求,但使用 Runnable 對象隊列的這種模式是調度程序和工做隊列的公共約定。


清單 1. 具備線程池的工做隊列

public class WorkQueue

{

    private final int nThreads;

    private final PoolWorker[] threads;

    private final LinkedList queue;

    public WorkQueue(int nThreads)

    {

        this.nThreads = nThreads;

        queue = new LinkedList();

        threads = new PoolWorker[nThreads];

        for (int i=0; i<nThreads; i++) {

            threads[i] = new PoolWorker();

            threads[i].start();

        }

    }

    public void execute(Runnable r) {

        synchronized(queue) {

            queue.addLast(r);

            queue.notify();

        }

    }

    private class PoolWorker extends Thread {

        public void run() {

            Runnable r;

            while (true) {

                synchronized(queue) {

                    while (queue.isEmpty()) {

                        try

                        {

                            queue.wait();

                        }

                        catch (InterruptedException ignored)

                        {

                        }

                    }

                    r = (Runnable) queue.removeFirst();

                }

                // If we don't catch RuntimeException,

                // the pool could leak threads

                try {

                    r.run();

                }

                catch (RuntimeException e) {

                    // You might want to log something here

                }

            }

        }

    }

}

 

您可能已經注意到了清單 1 中的實現使用的是 notify() 而不是 notifyAll() 。大多數專家建議使用 notifyAll() 而不是 notify() ,並且理由很充分:使用 notify() 具備難以捉摸的風險,只有在某些特定條件下使用該方法纔是合適的。另外一方面,若是使用得當, notify() 具備比 notifyAll() 更可取的性能特徵;特別是, notify() 引發的環境切換要少得多,這一點在服務器應用程序中是很重要的。

清單 1 中的示例工做隊列知足了安全使用 notify() 的需求。所以,請繼續,在您的程序中使用它,但在其它情形下使用 notify() 時請格外當心。

使用線程池的風險

雖然線程池是構建多線程應用程序的強大機制,但使用它並非沒有風險的。用線程池構建的應用程序容易遭受任何其它多線程應用程序容易遭受的全部併發風險,諸如同步錯誤和死鎖,它還容易遭受特定於線程池的少數其它風險,諸如與池有關的死鎖、資源不足和線程泄漏。

死鎖

任何多線程應用程序都有死鎖風險。當一組進程或線程中的每個都在等待一個只有該組中另外一個進程才能引發的事件時,咱們就說這組進程或線程 死鎖了。死鎖的最簡單情形是:線程 A 持有對象 X 的獨佔鎖,而且在等待對象 Y 的鎖,而線程 B 持有對象 Y 的獨佔鎖,卻在等待對象 X 的鎖。除非有某種方法來打破對鎖的等待(Java 鎖定不支持這種方法),不然死鎖的線程將永遠等下去。

雖然任何多線程程序中都有死鎖的風險,但線程池卻引入了另外一種死鎖可能,在那種狀況下,全部池線程都在執行已阻塞的等待隊列中另外一任務的執行結果的任務,但這一任務卻由於沒有未被佔用的線程而不能運行。當線程池被用來實現涉及許多交互對象的模擬,被模擬的對象能夠相互發送查詢,這些查詢接下來做爲排隊的任務執行,查詢對象又同步等待着響應時,會發生這種狀況。

資源不足

線程池的一個優勢在於:相對於其它替代調度機制(有些咱們已經討論過)而言,它們一般執行得很好。但只有恰當地調整了線程池大小時纔是這樣的。線程消耗包括內存和其它系統資源在內的大量資源。除了 Thread 對象所需的內存以外,每一個線程都須要兩個可能很大的執行調用堆棧。除此之外,JVM 可能會爲每一個 Java 線程建立一個本機線程,這些本機線程將消耗額外的系統資源。最後,雖然線程之間切換的調度開銷很小,但若是有不少線程,環境切換也可能嚴重地影響程序的性能。

若是線程池太大,那麼被那些線程消耗的資源可能嚴重地影響系統性能。在線程之間進行切換將會浪費時間,並且使用超出比您實際須要的線程可能會引發資源匱乏問題,由於池線程正在消耗一些資源,而這些資源可能會被其它任務更有效地利用。除了線程自身所使用的資源之外,服務請求時所作的工做可能須要其它資源,例如 JDBC 鏈接、套接字或文件。這些也都是有限資源,有太多的併發請求也可能引發失效,例如不能分配 JDBC 鏈接。

併發錯誤

線程池和其它排隊機制依靠使用 wait() notify() 方法,這兩個方法都難於使用。若是編碼不正確,那麼可能丟失通知,致使線程保持空閒狀態,儘管隊列中有工做要處理。使用這些方法時,必須格外當心;即使是專家也可能在它們上面出錯。而最好使用現有的、已經知道能工做的實現,例如在下面的 無須編寫您本身的池中討論的 util.concurrent 包。

線程泄漏

各類類型的線程池中一個嚴重的風險是線程泄漏,當從池中除去一個線程以執行一項任務,而在任務完成後該線程卻沒有返回池時,會發生這種狀況。發生線程泄漏的一種情形出如今任務拋出一個 RuntimeException 或一個 Error 時。若是池類沒有捕捉到它們,那麼線程只會退出而線程池的大小將會永久減小一個。當這種狀況發生的次數足夠多時,線程池最終就爲空,並且系統將中止,由於沒有可用的線程來處理任務。

有些任務可能會永遠等待某些資源或來自用戶的輸入,而這些資源又不能保證變得可用,用戶可能也已經回家了,諸如此類的任務會永久中止,而這些中止的任務也會引發和線程泄漏一樣的問題。若是某個線程被這樣一個任務永久地消耗着,那麼它實際上就被從池除去了。對於這樣的任務,應該要麼只給予它們本身的線程,要麼只讓它們等待有限的時間。

請求過載

僅僅是請求就壓垮了服務器,這種狀況是可能的。在這種情形下,咱們可能不想將每一個到來的請求都排隊到咱們的工做隊列,由於排在隊列中等待執行的任務可能會消耗太多的系統資源並引發資源缺少。在這種情形下決定如何作取決於您本身;在某些狀況下,您能夠簡單地拋棄請求,依靠更高級別的協議稍後重試請求,您也能夠用一個指出服務器暫時很忙的響應來拒絕請求。

有效使用線程池的準則

只要您遵循幾條簡單的準則,線程池能夠成爲構建服務器應用程序的極其有效的方法:

·         不要對那些同步等待其它任務結果的任務排隊。這可能會致使上面所描述的那種形式的死鎖,在那種死鎖中,全部線程都被一些任務所佔用,這些任務依次等待排隊任務的結果,而這些任務又沒法執行,由於全部的線程都很忙。

·         在爲時間可能很長的操做使用合用的線程時要當心。若是程序必須等待諸如 I/O 完成這樣的某個資源,那麼請指定最長的等待時間,以及隨後是失效仍是將任務從新排隊以便稍後執行。這樣作保證了:經過將某個線程釋放給某個可能成功完成的任務,從而將最終取得 某些進展。

·         理解任務。要有效地調整線程池大小,您須要理解正在排隊的任務以及它們正在作什麼。它們是 CPU 限制的(CPU-bound)嗎?它們是 I/O 限制的(I/O-bound)嗎?您的答案將影響您如何調整應用程序。若是您有不一樣的任務類,這些類有着大相徑庭的特徵,那麼爲不一樣任務類設置多個工做隊列可能會有意義,這樣能夠相應地調整每一個池。

調整池的大小

調整線程池的大小基本上就是避免兩類錯誤:線程太少或線程太多。幸運的是,對於大多數應用程序來講,太多和太少之間的餘地至關寬。

請回憶:在應用程序中使用線程有兩個主要優勢,儘管在等待諸如 I/O 的慢操做,但容許繼續進行處理,而且能夠利用多處理器。在運行於具備 N 個處理器機器上的計算限制的應用程序中,在線程數目接近 N 時添加額外的線程可能會改善總處理能力,而在線程數目超過 N 時添加額外的線程將不起做用。事實上,太多的線程甚至會下降性能,由於它會致使額外的環境切換開銷。

線程池的最佳大小取決於可用處理器的數目以及工做隊列中的任務的性質。若在一個具備 N 個處理器的系統上只有一個工做隊列,其中所有是計算性質的任務,在線程池具備 N N+1 個線程時通常會得到最大的 CPU 利用率。

對於那些可能須要等待 I/O 完成的任務(例如,從套接字讀取 HTTP 請求的任務),須要讓池的大小超過可用處理器的數目,由於並非全部線程都一直在工做。經過使用概要分析,您能夠估計某個典型請求的等待時間(WT)與服務時間(ST)之間的比例。若是咱們將這一比例稱之爲 WT/ST,那麼對於一個具備 N 個處理器的系統,須要設置大約 N*(1+WT/ST) 個線程來保持處理器獲得充分利用。

處理器利用率不是調整線程池大小過程當中的惟一考慮事項。隨着線程池的增加,您可能會碰到調度程序、可用內存方面的限制,或者其它系統資源方面的限制,例如套接字、打開的文件句柄或數據庫鏈接等的數目。

無須編寫您本身的池

Doug Lea 編寫了一個優秀的併發實用程序開放源碼庫 util.concurrent ,它包括互斥、信號量、諸如在併發訪問下執行得很好的隊列和散列表之類集合類以及幾個工做隊列實現。該包中的 PooledExecutor 類是一種有效的、普遍使用的以工做隊列爲基礎的線程池的正確實現。您無須嘗試編寫您本身的線程池,這樣作容易出錯,相反您能夠考慮使用 util.concurrent 中的一些實用程序。參閱 參考資料以獲取連接和更多信息。

util.concurrent 庫也激發了 JSR 166JSR 166 是一個 Java 社區過程(Java Community Process (JCP))工做組,他們正在打算開發一組包含在 java.util.concurrent 包下的 Java 類庫中的併發實用程序,這個包應該用於 Java 開發工具箱 1.5 發行版。


線程池是組織服務器應用程序的有用工具。它在概念上十分簡單,但在實現和使用一個池時,卻須要注意幾個問題,例如死鎖、資源不足和 wait() notify() 的複雜性。若是您發現您的應用程序須要線程池,那麼請考慮使用 util.concurrent 中的某個 Executor 類,例如 PooledExecutor ,而不用從頭開始編寫。若是您要本身建立線程來處理生存期很短的任務,那麼您絕對應該考慮使用線程池來替代。

 

 
該文章裏有個例子,簡單的描述了線程池的內部實現,建議根據裏面的例子來了解JAVA 線程池的原理。同時,裏面還詳細描述了使用線程池存在的優勢和弊端,你們能夠研究下,我以爲是篇很是好的文章。 

    JDK
自帶線程池總類介紹介紹: 

    1
newFixedThreadPool建立一個指定工做線程數量的線程池。每當提交一個任務就建立一個工做線程,若是工做線程數量達到線程池初始的最大數,則將提交的任務存入到池隊列中。 

    2
newCachedThreadPool建立一個可緩存的線程池。這種類型的線程池特色是: 
    1).
工做線程的建立數量幾乎沒有限制(其實也有限制的,數目爲Interger. MAX_VALUE), 這樣可靈活的往線程池中添加線程。 
    2).
若是長時間沒有往線程池中提交任務,即若是工做線程空閒了指定的時間(默認爲1分鐘),則該工做線程將自動終止。終止後,若是你又提交了新的任務,則線程池從新建立一個工做線程。 

    3
newSingleThreadExecutor建立一個單線程化的Executor,即只建立惟一的工做者線程來執行任務,若是這個線程異常結束,會有另外一個取代它,保證順序執行(我以爲這點是它的特點)。單工做線程最大的特色是可保證順序地執行各個任務,而且在任意給定的時間不會有多個線程是活動的 

    4
newScheduleThreadPool建立一個定長的線程池,並且支持定時的以及週期性的任務執行,相似於Timer(這種線程池原理暫還沒徹底瞭解透徹

   
總結:.FixedThreadPool是一個典型且優秀的線程池,它具備線程池提升程序效率和節省建立線程時所耗的開銷的優勢。可是,在線程池空閒時,即線程池中沒有可運行任務時,它不會釋放工做線程,還會佔用必定的系統資源。 

       
二.CachedThreadPool的特色就是在線程池空閒時,即線程池中沒有可運行任務時,它會釋放工做線程,從而釋放工做線程所佔用的資源。可是,但當出現新任務時,又要建立一新的工做線程,又要必定的系統開銷。而且,在使用CachedThreadPool時,必定要注意控制任務的數量,不然,因爲大量線程同時運行,頗有會形成系統癱瘓。 


27. Concurrent包裏的其餘東西:ArrayBlockingQueueCountDownLatch等等。

CountDownLatch,一個同步輔助類,在完成一組正在其餘線程中執行的操做以前,它容許一個或多個線程一直等待。

主要方法

 public CountDownLatch(int count);

 public void countDown();

 public void await() throws InterruptedException
 

構造方法參數指定了計數的次數

countDown方法,當前線程調用此方法,則計數減一

awaint方法,調用此方法會一直阻塞當前線程,直到計時器的值爲0

 

例子

Java代碼   收藏代碼
  1. public class CountDownLatchDemo {  
  2.     final static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  3.     public static void main(String[] args) throws InterruptedException {  
  4.         CountDownLatch latch=new CountDownLatch(2);//兩個工人的協做  
  5.         Worker worker1=new Worker("zhang san"5000, latch);  
  6.         Worker worker2=new Worker("li si"8000, latch);  
  7.         worker1.start();//  
  8.         worker2.start();//  
  9.         latch.await();//等待全部工人完成工做  
  10.         System.out.println("all work done at "+sdf.format(new Date()));  
  11.     }  
  12.       
  13.       
  14.     static class Worker extends Thread{  
  15.         String workerName;   
  16.         int workTime;  
  17.         CountDownLatch latch;  
  18.         public Worker(String workerName ,int workTime ,CountDownLatch latch){  
  19.              this.workerName=workerName;  
  20.              this.workTime=workTime;  
  21.              this.latch=latch;  
  22.         }  
  23.         public void run(){  
  24.             System.out.println("Worker "+workerName+" do work begin at "+sdf.format(new Date()));  
  25.             doWork();//工做了  
  26.             System.out.println("Worker "+workerName+" do work complete at "+sdf.format(new Date()));  
  27.             latch.countDown();//工人完成工做,計數器減一  
  28.   
  29.         }  
  30.           
  31.         private void doWork(){  
  32.             try {  
  33.                 Thread.sleep(workTime);  
  34.             } catch (InterruptedException e) {  
  35.                 e.printStackTrace();  
  36.             }  
  37.         }  
  38.     }  
  39.       
  40.        
  41. }  

 

輸出:

Worker zhang san do work begin at 2011-04-14 11:05:11
Worker li si do work begin at 2011-04-14 11:05:11
Worker zhang san do work complete at 2011-04-14 11:05:16
Worker li si do work complete at 2011-04-14 11:05:19
all work done at 2011-04-14 11:05:19


何時使用CountDownLatch

正如每一個Java文檔所描述的那樣,CountDownLatch是一個同步工具類,它容許一個或多個線程一直等待,直到其餘線程的操做執行完後再執行。在Java併發中,countdownlatch的概念是一個常見的面試題,因此必定要確保你很好的理解了它。在這篇文章中,我將會涉及到在Java併發編 程中跟CountDownLatch相關的如下幾點:

目錄

  • CountDownLatch是什麼?
  • CountDownLatch如何工做?
  • 在實時系統中的應用場景
  • 應用範例
  • 常見的面試題

CountDownLatch是什麼

CountDownLatch是在java1.5被引入的,跟它一塊兒被引入的併發工具類還有CyclicBarrier、Semaphore、ConcurrentHashMapBlockingQueue,它們都存在於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. 實現最大的並行性:有時咱們想同時啓動多個線程,實現最大程度的並行性。例如,咱們想測試一個單例類。若是咱們建立一個初始計數爲1的CountDownLatch,並讓全部線程都在這個鎖上等待,那麼咱們能夠很輕鬆地完成測試。咱們只需調用 一次countDown()方法就可讓全部的等待線程同時恢復執行。
  2. 開始執行前等待n個線程完成各自任務:例如應用程序啓動類要確保在處理用戶請求前,全部N個外部系統已經啓動和運行了。
  3. 死鎖檢測:一個很是方便的使用場景是,你能夠使用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.javaCacheHealthChecker.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

 

Java代碼   收藏代碼
  1. public class BlockingQueueTest {  
  2.     private static ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(5true); //最大容量爲5的數組堵塞隊列  
  3.     //private static LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>(5);  
  4.       
  5.     private static CountDownLatch producerLatch; //倒計時計數器  
  6.     private static CountDownLatch consumerLatch;  
  7.       
  8.     public static void main(String[] args) {  
  9.         BlockingQueueTest queueTest = new BlockingQueueTest();  
  10.         queueTest.test();  
  11.     }  
  12.       
  13.     private void test(){  
  14.         producerLatch = new CountDownLatch(10); //state值爲10  
  15.         consumerLatch = new CountDownLatch(10); //state值爲10  
  16.           
  17.         Thread t1 = new Thread(new ProducerTask());  
  18.         Thread t2 = new Thread(new ConsumerTask());  
  19.   
  20.         //啓動線程  
  21.         t1.start();  
  22.         t2.start();  
  23.           
  24.         try {  
  25.             System.out.println("producer waiting...");  
  26.             producerLatch.await(); //進入等待狀態,直到state值爲0,再繼續往下執行  
  27.             System.out.println("producer end");  
  28.         } catch (InterruptedException e) {  
  29.             e.printStackTrace();  
  30.         }  
  31.           
  32.         try {  
  33.             System.out.println("consumer waiting...");  
  34.             consumerLatch.await(); //進入等待狀態,直到state值爲0,再繼續往下執行  
  35.             System.out.println("consumer end");  
  36.         } catch (InterruptedException e) {  
  37.             e.printStackTrace();  
  38.         }  
  39.   
  40.         //結束線程  
  41.         t1.interrupt();   
  42.         t2.interrupt();  
  43.           
  44.         System.out.println("end");  
  45.     }  
  46.       
  47.     //生產者  
  48.     class ProducerTask implements Runnable{  
  49.         private Random rnd = new Random();  
  50.           
  51.         @Override  
  52.         public void run() {  
  53.             try {  
  54.                 while(true){  
  55.                     queue.put(rnd.nextInt(100)); //若是queue容量已滿,則當前線程會堵塞,直到有空間再繼續  
  56.                       
  57.                     //offer方法爲非堵塞的  
  58.                     //queue.offer(rnd.nextInt(100), 1, TimeUnit.SECONDS); //等待1秒後還不能加入隊列則返回失敗,放棄加入  
  59.                     //queue.offer(rnd.nextInt(100));  
  60.                       
  61.                     producerLatch.countDown(); //state值減1  
  62.                     //TimeUnit.SECONDS.sleep(2); //線程休眠2秒  
  63.                 }  
  64.             } catch (InterruptedException e) {  
  65.                 //e.printStackTrace();  
  66.             }  catch (Exception ex){  
  67.                 ex.printStackTrace();  
  68.             }  
  69.         }  
  70.     }  
  71.       
  72.     //消費者  
  73.     class ConsumerTask implements Runnable{  
  74.         @Override  
  75.         public void run() {  
  76.             try {  
  77.                 while(true){  
  78.                     Integer value = queue.take(); //若是queue爲空,則當前線程會堵塞,直到有新數據加入  
  79.                       
  80.                     //poll方法爲非堵塞的  
  81.                     //Integer value = queue.poll(1, TimeUnit.SECONDS); //等待1秒後尚未數據可取則返回失敗,放棄獲取  
  82.                     //Integer value = queue.poll();  
  83.                       
  84.                     System.out.println("value = " + value);  
  85.                       
  86.                     consumerLatch.countDown(); //state值減1  
  87.                     TimeUnit.SECONDS.sleep(2); //線程休眠2秒  
  88.                 }  
  89.             } catch (InterruptedException e) {  
  90.                 //e.printStackTrace();  
  91.             } catch (Exception ex){  
  92.                 ex.printStackTrace();  
  93.             }  
  94.         }  
  95.     }  
  96.       
  97. }  

28. wait()sleep()的區別。

對於sleep()方法,咱們首先要知道該方法是屬於Thread類中的。而wait()方法,則是屬於Object類中的。

sleep()方法致使了程序暫停執行指定的時間,讓出cpu該其餘線程,可是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。

在調用sleep()方法的過程當中,線程不會釋放對象鎖。

而當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備

獲取對象鎖進入運行狀態。


29.foreach 與正常 for 循環效率對比

Java中常常會用到迭代列表數據的狀況,本文針對幾種經常使用的寫法進行效率比較。雖然網上已經有了相似的文章,可是對他們的結論並不認同。

常見的實現方法:

1.for循環:

[java]  view plain  copy
 print ?
  1. for(int i = 0; i < list.size(); i++)  
  2. for(int i = 0, size = list.size(); i < size; i++)  


通常人都會認爲第二種寫法效率高。

2.foreach:

[java]  view plain  copy
 print ?
  1. for(Object obj : list)  

這是一種簡潔的寫法,只能對列表進行讀取,沒法修改。


3.while:

[java]  view plain  copy
 print ?
  1. int size = list.size();  
  2. while(size-- > 0)  

4.迭代:

[java]  view plain  copy
 print ?
  1. Object iter = list.iterator();  
  2. while(iter.hasNext()) {  
  3.   iter.next();  
  4. }  


測試代碼:

 針對以上幾種方法編寫的測試代碼。
[java]  view plain  copy
 print ?
  1. public static void main(String[] args) {  
  2.       List<Integer> list = new ArrayList<Integer>();  
  3.   
  4.       int runTime = 1000;//執行次數  
  5.       for (int i = 0; i < 1000 * 1000; i++) {  
  6.           list.add(i);  
  7.       }  
  8.       int size = list.size();  
  9.       long currTime = System.currentTimeMillis();//開始分析前的系統時間  
  10.       //基本的for              
  11.       for(int j = 0; j < runTime; j++) {  
  12.           for (int i = 0; i < size; i++) {  
  13.               list.get(i);  
  14.           }  
  15.       }  
  16.       long time1 = System.currentTimeMillis();  
  17.   
  18.       //foreach  
  19.       for(int j = 0; j < runTime; j++) {  
  20.           for (Integer integer : list) {  
  21.           }  
  22.       }  
  23.       long time2 = System.currentTimeMillis();  
  24.   
  25.       for(int j = 0; j < runTime; j++) {  
  26.           //while  
  27.           int i = 0 ;  
  28.           while(i < size){  
  29.               list.get(i++);  
  30.           }  
  31.       }  
  32.       long time3 = System.currentTimeMillis();  
  33.   
  34.       for(int j = 0; j < runTime; j++) {//普通for循環  
  35.           for (int i = 0; i < list.size(); i++) {  
  36.               list.get(i);  
  37.           }  
  38.       }  
  39.       long time4 = System.currentTimeMillis();  
  40.   
  41.       for(int j = 0; j < runTime; j++) {//迭代  
  42.           Iterator<Integer> iter = list.iterator();  
  43.           while(iter.hasNext()) {  
  44.               iter.next();  
  45.           }  
  46.       }  
  47.       long time5 = System.currentTimeMillis();  
  48.   
  49.       long time = time1 - currTime ;  
  50.       System.out.print("use for:" + time);  
  51.       time = time2 - time1;  
  52.       System.out.print("\tuse foreach:" + time);  
  53.       time = time3 - time2;  
  54.       System.out.print("\tuse while:" + time);  
  55.       time = time4 - time3;  
  56.       System.out.print("\tuse for2:" + time);  
  57.       time = time5 - time4;  
  58.       System.out.print("\tuse iterator:" + time);  
  59.       System.out.println();  
  60.   }  

輸出結果(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()方法的只是返回了對象內的長度屬性,並無其它計算,因此只存在函數調用的開銷。

對數組的測試:

將代碼中的列表list換作數組再進行測試(iterator不適用),發現耗時基本爲0。說明:

3. 列表的get()方法開銷很多

應該主要是檢測數據合法性時產生的。

將執行次數增長100萬倍,這時能夠看出結果基本相等,並無明顯的差別。說明:

4. 數組length也沒有開銷

可見數組長度並非每次執行的時候都要計算的。聯想一下Java建立數組的時候要求必須指定數組的長度,編譯處理的時候顯然沒有把這個值拋棄掉。



30. Java IONIO

內核空間用戶空間計算機體系結構計算機組成原理、……確實有點兒深奧。

 

個人新書《代碼之謎》會有專門的章節講解相關知識,如今寫個簡短的科普文:

 

就速度來講 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();
}
}
}

31.  反射的做用於原理
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的幾種方式的原理與特色:DOMSAXPULL

 在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. JavaC++對比。

(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(一個關鍵字)
■句柄確定會獲得檢查,並在出錯時丟棄異常
■全部數組訪問都會獲得檢查,及時發現邊界違例狀況
■自動垃圾收集,防止出現內存漏洞
■明確、「傻瓜式」的異常控制機制
■爲多線程提供了簡單的語言支持
■對網絡程序片進行字節碼校驗


35. Java1.71.8新特性
在JDK1.7的新特性方面主要有下面幾方面的加強:
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上使用的。


在jdk7的新特性方面主要有下面幾方面的加強:
 
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 
相關文章
相關標籤/搜索