Java面試寶典2010(一.51—83)

uiop_7890_7890發表於2010-10-15

51、啟動一個執行緒是用run()還是start()? .
啟動一個執行緒是呼叫start()方法,使執行緒就緒狀態,以後可以被排程為執行狀態,一個執行緒必須關聯一些具體的執行程式碼,run()方法是該執行緒所關聯的執行程式碼。

 

52、當一個執行緒進入一個物件的一個synchronized方法後,其它執行緒是否可進入此物件的其它方法?
分幾種情況:

     1.其他方法前是否加了synchronized關鍵字,如果沒加,則能。

     2.如果這個方法內部呼叫了wait,則可以進入其他synchronized方法。

     3.如果其他個方法都加了synchronized關鍵字,並且內部沒有呼叫wait,則不能。

        4.如果其他方法是static,它用的同步鎖是當前類的位元組碼,與非靜態的方法不能同步,因為非靜態的方法用的是this。

 

53、執行緒的基本概念、執行緒的基本狀態以及狀態之間的關係
 

一個程式中可以有多條執行線索同時執行,一個執行緒就是程式中的一條執行線索,每個執行緒上都關聯有要執行的程式碼,即可以有多段程式程式碼同時執行,每個程式至少都有一個執行緒,即main方法執行的那個執行緒。如果只是一個cpu,它怎麼能夠同時執行多段程式呢?這是從巨集觀上來看的,cpu一會執行a線索,一會執行b線索,切換時間很快,給人的感覺是a,b在同時執行,好比大家在同一個辦公室上網,只有一條連結到外部網線,其實,這條網線一會為a傳資料,一會為b傳資料,由於切換時間很短暫,所以,大家感覺都在同時上網。

 

  狀態:就緒,執行,synchronize阻塞,wait和sleep掛起,結束。wait必須在synchronized內部呼叫。

  呼叫執行緒的start方法後執行緒進入就緒狀態,執行緒排程系統將就緒狀態的執行緒轉為執行狀態,遇到synchronized語句時,由執行狀態轉為阻塞,當synchronized獲得鎖後,由阻塞轉為執行,在這種情況可以呼叫wait方法轉為掛起狀態,當執行緒關聯的程式碼執行完後,執行緒變為結束狀態。

 

54、簡述synchronized和java.util.concurrent.locks.Lock的異同 ?
主要相同點:Lock能完成synchronized所實現的所有功能

主要不同點:Lock有比synchronized更精確的執行緒語義和更好的效能。synchronized會自動釋放鎖,而Lock一定要求程式設計師手工釋放,並且必須在finally從句中釋放。Lock還有更強大的功能,例如,它的tryLock方法可以非阻塞方式去拿鎖。

舉例說明(對下面的題用lock進行了改寫):

package com.huawei.interview;

 

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

 

public class ThreadTest {

 

    /**

     * @param args

     */

   

    private int j;

    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {

       // TODO Auto-generated method stub

       ThreadTest tt = new ThreadTest();

       for(int i=0;i<2;i++)

       {

           new Thread(tt.new Adder()).start();

           new Thread(tt.new Subtractor()).start();

       }

    }

 

    private class Subtractor implements Runnable

    {

 

       @Override

       public void run() {

           // TODO Auto-generated method stub

           while(true)

           {

              /*synchronized (ThreadTest.this) {       

                  System.out.println("j--=" + j--);

                  //這裡拋異常了,鎖能釋放嗎?

              }*/

              lock.lock();

              try

              {

                  System.out.println("j--=" + j--);

              }finally

              {

                  lock.unlock();

              }

           }

       }

      

    }

   

    private class Adder implements Runnable

    {

 

       @Override

       public void run() {

           // TODO Auto-generated method stub

           while(true)

           {

              /*synchronized (ThreadTest.this) {

              System.out.println("j++=" + j++); 

              }*/

              lock.lock();

               try

              {

                  System.out.println("j++=" + j++);

              }finally

              {

                  lock.unlock();

              }            

           }         

       }

      

    }

}

55、設計4個執行緒,其中兩個執行緒每次對j增加1,另外兩個執行緒對j每次減少1。寫出程式。
以下程式使用內部類實現執行緒,對j增減的時候沒有考慮順序問題。

public class ThreadTest1

{

private int j;

public static void main(String args[]){

   ThreadTest1 tt=new ThreadTest1();

   Inc inc=tt.new Inc();

   Dec dec=tt.new Dec();

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

       Thread t=new Thread(inc);

       t.start();

           t=new Thread(dec);

       t.start();

       }

   }

private synchronized void inc(){

   j++;

   System.out.println(Thread.currentThread().getName()+"-inc:"+j);

   }

private synchronized void dec(){

   j--;

   System.out.println(Thread.currentThread().getName()+"-dec:"+j);

   }

class Inc implements Runnable{

   public void run(){

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

       inc();

       }

   }

}

class Dec implements Runnable{

   public void run(){

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

       dec();

       }

   }

}

}

 

----------隨手再寫的一個-------------

class A

{

JManger j =new JManager();

main()

{

   new A().call();

}

 

void call

{

   for(int i=0;i<2;i++)

   {

        new Thread(

               new Runnable(){ public void run(){while(true){j.accumulate()}}}

        ).start();

        new Thread(new Runnable(){ public void run(){while(true){j.sub()}}}).start();

   }

}

}

 

class JManager

{

   private j = 0;

  

   public synchronized void subtract()

   {

        j--

   }

  

   public synchronized void accumulate()

   {

        j++;

   }

  

}

 

56、子執行緒迴圈10次,接著主執行緒迴圈100,接著又回到子執行緒迴圈10次,接著再回到主執行緒又迴圈100,如此迴圈50次,請寫出程式。
 

最終的程式程式碼如下:

public class ThreadTest {

 

    /**

     * @param args

     */

    public static void main(String[] args) {

       // TODO Auto-generated method stub

       new ThreadTest().init();

 

    }

 

    public void init()

    {

       final Business business = new Business();

       new Thread(

              new Runnable()

              {

 

                  public void run() {

                     for(int i=0;i<50;i++)

                     {

                         business.SubThread(i);

                     }                   

                  }

                 

              }

      

       ).start();

      

       for(int i=0;i<50;i++)

       {

           business.MainThread(i);

       }     

    }

   

    private class Business

    {

       boolean bShouldSub = true;//這裡相當於定義了控制該誰執行的一個訊號燈

       public synchronized void MainThread(int i)

       {

           if(bShouldSub)

              try {

                  this.wait();

              } catch (InterruptedException e) {

                  // TODO Auto-generated catch block

                  e.printStackTrace();

              }     

             

           for(int j=0;j<5;j++)

           {

              System.out.println(Thread.currentThread().getName() + ":i=" + i +",j=" + j);

           }

           bShouldSub = true;

           this.notify();

      

       }

      

      

       public synchronized void SubThread(int i)

       {

           if(!bShouldSub)

              try {

                  this.wait();

              } catch (InterruptedException e) {

                  // TODO Auto-generated catch block

                  e.printStackTrace();

              }  

             

           for(int j=0;j<10;j++)

           {

              System.out.println(Thread.currentThread().getName() + ":i=" + i +",j=" + j);

           }

           bShouldSub = false;            

           this.notify();          

       }

    }

}

 

備註:不可能一上來就寫出上面的完整程式碼,最初寫出來的程式碼如下,問題在於兩個執行緒的程式碼要參照同一個變數,即這兩個執行緒的程式碼要共享資料,所以,把這兩個執行緒的執行程式碼搬到同一個類中去:

 

package com.huawei.interview.lym;

 

public class ThreadTest {

   

    private static boolean bShouldMain = false;

   

    public static void main(String[] args) {

       // TODO Auto-generated method stub

       /*new Thread(){

       public void run()

       {

           for(int i=0;i<50;i++)

           {

              for(int j=0;j<10;j++)

              {

                  System.out.println("i=" + i + ",j=" + j);

              }

           }            

       }

      

    }.start();*/     

      

      

       //final String str = new String("");

 

       new Thread(

              new Runnable()

              {

                  public void run()

                  {

                     for(int i=0;i<50;i++)

                     {

                         synchronized (ThreadTest.class) {

                            if(bShouldMain)

                            {

                                try {

                                   ThreadTest.class.wait();}

                                catch (InterruptedException e) {

                                   e.printStackTrace();

                                }

                            }

                            for(int j=0;j<10;j++)

                            {

                                System.out.println(

                                       Thread.currentThread().getName() +

                                       "i=" + i + ",j=" + j);

                            }

                             bShouldMain = true;

                            ThreadTest.class.notify();

                         }                       

                     }                   

                  }

              }

       ).start();

      

       for(int i=0;i<50;i++)

       {

           synchronized (ThreadTest.class) {

              if(!bShouldMain)

              {

                  try {

                     ThreadTest.class.wait();}

                  catch (InterruptedException e) {

                     e.printStackTrace();

                  }

              }            

              for(int j=0;j<5;j++)

              {

                  System.out.println(

                         Thread.currentThread().getName() +                  

                         "i=" + i + ",j=" + j);

              }

              bShouldMain = false;

              ThreadTest.class.notify();            

           }         

       }

    }

 

}

下面使用jdk5中的併發庫來實現的:

import java.util.concurrent.Executors;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

import java.util.concurrent.locks.Condition;

 

public class ThreadTest

{

       private static Lock lock = new ReentrantLock();

       private static Condition subThreadCondition = lock.newCondition();

       private static boolean bBhouldSubThread = false;

       public static void main(String [] args)

       {

              ExecutorService threadPool = Executors.newFixedThreadPool(3);

              threadPool.execute(new Runnable(){

                     public void run()

                     {

                            for(int i=0;i<50;i++)

                            {

                                   lock.lock();                                

                                   try

                                   {                                

                                          if(!bBhouldSubThread)

                                                 subThreadCondition.await();

                                          for(int j=0;j<10;j++)

                                          {

                                                 System.out.println(Thread.currentThread().getName() + ",j=" + j);

                                          }

                                          bBhouldSubThread = false;

                                          subThreadCondition.signal();

                                   }catch(Exception e)

                                   {                                       

                                   }

                                   finally

                                   {

                                          lock.unlock();

                                   }

                            }                  

                     }

                    

              });

              threadPool.shutdown();

              for(int i=0;i<50;i++)

              {

                            lock.lock();                                

                            try

                            {    

                                   if(bBhouldSubThread)

                                                 subThreadCondition.await();                                                  

                                   for(int j=0;j<10;j++)

                                   {

                                          System.out.println(Thread.currentThread().getName() + ",j=" + j);

                                   }

                                   bBhouldSubThread = true;

                                   subThreadCondition.signal();                            

                            }catch(Exception e)

                            {                                       

                            }

                            finally

                            {

                                   lock.unlock();

                            }                                

              }

       }

}

57、介紹Collection框架的結構
答:隨意發揮題,天南海北誰便談,只要讓別覺得你知識淵博,理解透徹即可。

 

58、Collection框架中實現比較要實現什麼介面
comparable/comparator

59、ArrayList和Vector的區別
答:

這兩個類都實現了List介面(List介面繼承了Collection介面),他們都是有序集合,即儲存在這兩個集合中的元素的位置都是有順序的,相當於一種動態的陣列,我們以後可以按位置索引號取出某個元素,,並且其中的資料是允許重複的,這是HashSet之類的集合的最大不同處,HashSet之類的集合不可以按索引號去檢索其中的元素,也不允許有重複的元素(本來題目問的與hashset沒有任何關係,但為了說清楚ArrayList與Vector的功能,我們使用對比方式,更有利於說明問題)。

 

接著才說ArrayList與Vector的區別,這主要包括兩個方面:.
(1)同步性:

       Vector是執行緒安全的,也就是說是它的方法之間是執行緒同步的,而ArrayList是執行緒序不安全的,它的方法之間是執行緒不同步的。如果只有一個執行緒會訪問到集合,那最好是使用ArrayList,因為它不考慮執行緒安全,效率會高些;如果有多個執行緒會訪問到集合,那最好是使用Vector,因為不需要我們自己再去考慮和編寫執行緒安全的程式碼。

 

備註:對於Vector&ArrayList、Hashtable&HashMap,要記住執行緒安全的問題,記住Vector與Hashtable是舊的,是java一誕生就提供了的,它們是執行緒安全的,ArrayList與HashMap是java2時才提供的,它們是執行緒不安全的。所以,我們講課時先講老的。
(2)資料增長:

       ArrayList與Vector都有一個初始的容量大小,當儲存進它們裡面的元素的個數超過了容量時,就需要增加ArrayList與Vector的儲存空間,每次要增加儲存空間時,不是隻增加一個儲存單元,而是增加多個儲存單元,每次增加的儲存單元的個數在記憶體空間利用與程式效率之間要取得一定的平衡。Vector預設增長為原來兩倍,而ArrayList的增長策略在文件中沒有明確規定(從原始碼看到的是增長為原來的1.5倍)。ArrayList與Vector都可以設定初始的空間大小,Vector還可以設定增長的空間大小,而ArrayList沒有提供設定增長空間的方法。

    總結:即Vector增長原來的一倍,ArrayList增加原來的0.5倍。

60、HashMap和Hashtable的區別
(條理上還需要整理,也是先說相同點,再說不同點)

HashMap是Hashtable的輕量級實現(非執行緒安全的實現),他們都完成了Map介面,主要區別在於HashMap允許空(null)鍵值(key),由於非執行緒安全,在只有一個執行緒訪問的情況下,效率要高於Hashtable。

HashMap允許將null作為一個entry的key或者value,而Hashtable不允許。

HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因為contains方法容易讓人引起誤解。

Hashtable繼承自Dictionary類,而HashMap是Java1.2引進的Map interface的一個實現。

最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多個執行緒訪問Hashtable時,不需要自己為它的方法實現同步,而HashMap 就必須為之提供外同步。

Hashtable和HashMap採用的hash/rehash演算法都大概一樣,所以效能不會有很大的差異。

 

就HashMap與HashTable主要從三方面來說。
一.歷史原因:Hashtable是基於陳舊的Dictionary類的,HashMap是Java 1.2引進的Map介面的一個實現
二.同步性:Hashtable是執行緒安全的,也就是說是同步的,而HashMap是執行緒序不安全的,不是同步的
三.值:只有HashMap可以讓你將空值作為一個表的條目的key或value

61、List 和 Map 區別?
一個是儲存單列資料的集合,另一個是儲存鍵和值這樣的雙列資料的集合,List中儲存的資料是有順序,並且允許重複;Map中儲存的資料是沒有順序的,其鍵是不能重複的,它的值是可以有重複的。

62、List, Set, Map是否繼承自Collection介面?
   List,Set是,Map不是

 

63、List、Map、Set三個介面,存取元素時,各有什麼特點?
這樣的題屬於隨意發揮題:這樣的題比較考水平,兩個方面的水平:一是要真正明白這些內容,二是要有較強的總結和表述能力。如果你明白,但表述不清楚,在別人那裡則等同於不明白。

 

首先,List與Set具有相似性,它們都是單列元素的集合,所以,它們有一個功共同的父介面,叫Collection。Set裡面不允許有重複的元素,所謂重複,即不能有兩個相等(注意,不是僅僅是相同)的物件 ,即假設Set集合中有了一個A物件,現在我要向Set集合再存入一個B物件,但B物件與A物件equals相等,則B物件儲存不進去,所以,Set集合的add方法有一個boolean的返回值,當集合中沒有某個元素,此時add方法可成功加入該元素時,則返回true,當集合含有與某個元素equals相等的元素時,此時add方法無法加入該元素,返回結果為false。Set取元素時,沒法說取第幾個,只能以Iterator介面取得所有的元素,再逐一遍歷各個元素。

       List表示有先後順序的集合, 注意,不是那種按年齡、按大小、按價格之類的排序。當我們多次呼叫add(Obj e)方法時,每次加入的物件就像火車站買票有排隊順序一樣,按先來後到的順序排序。有時候,也可以插隊,即呼叫add(int index,Obj e)方法,就可以指定當前物件在集合中的存放位置。一個物件可以被反覆儲存進List中,每呼叫一次add方法,這個物件就被插入進集合中一次,其實,並不是把這個物件本身儲存進了集合中,而是在集合中用一個索引變數指向這個物件,當這個物件被add多次時,即相當於集合中有多個索引指向了這個物件,如圖x所示。List除了可以以Iterator介面取得所有的元素,再逐一遍歷各個元素之外,還可以呼叫get(index i)來明確說明取第幾個。

       Map與List和Set不同,它是雙列的集合,其中有put方法,定義如下:put(obj key,obj value),每次儲存時,要儲存一對key/value,不能儲存重複的key,這個重複的規則也是按equals比較相等。取則可以根據key獲得相應的value,即get(Object key)返回值為key 所對應的value。另外,也可以獲得所有的key的結合,還可以獲得所有的value的結合,還可以獲得key和value組合成的Map.Entry物件的集合。

 

List 以特定次序來持有元素,可有重複元素。Set 無法擁有重複元素,內部排序。Map 儲存key-value值,value可多值。

 

 

HashSet按照hashcode值的某種運算方式進行儲存,而不是直接按hashCode值的大小進行儲存。例如,"abc" ---> 78,"def" ---> 62,"xyz" ---> 65在hashSet中的儲存順序不是62,65,78,這些問題感謝以前一個叫崔健的學員提出,最後通過檢視原始碼給他解釋清楚,看本次培訓學員當中有多少能看懂原始碼。LinkedHashSet按插入的順序儲存,那被儲存物件的hashcode方法還有什麼作用呢?學員想想!hashset集合比較兩個物件是否相等,首先看hashcode方法是否相等,然後看equals方法是否相等。new 兩個Student插入到HashSet中,看HashSet的size,實現hashcode和equals方法後再看size。

 

同一個物件可以在Vector中加入多次。往集合裡面加元素,相當於集合裡用一根繩子連線到了目標物件。往HashSet中卻加不了多次的。

 

64、說出ArrayList,Vector, LinkedList的儲存效能和特性
ArrayList和Vector都是使用陣列方式儲存資料,此陣列元素數大於實際儲存的資料以便增加和插入元素,它們都允許直接按序號索引元素,但是插入元素要涉及陣列元素移動等記憶體操作,所以索引資料快而插入資料慢,Vector由於使用了synchronized方法(執行緒安全),通常效能上較ArrayList差,而LinkedList使用雙向連結串列實現儲存,按序號索引資料需要進行前向或後向遍歷,但是插入資料時只需要記錄本項的前後項即可,所以插入速度較快。

 

LinkedList也是執行緒不安全的,LinkedList提供了一些方法,使得LinkedList可以被當作堆疊和佇列來使用。

65、去掉一個Vector集合中重複的元素
Vector newVector = new Vector();

For (int i=0;i<vector.size();i++)

{

Object obj = vector.get(i);

       if(!newVector.contains(obj);

              newVector.add(obj);

}

還有一種簡單的方式,HashSet set = new HashSet(vector);

66、Collection 和 Collections的區別。
  Collection是集合類的上級介面,繼承與他的介面主要有Set 和List.

Collections是針對集合類的一個幫助類,他提供一系列靜態方法實現對各種集合的搜尋、排序、執行緒安全化等操作。

67、Set裡的元素是不能重複的,那麼用什麼方法來區分重複與否呢? 是用==還是equals()? 它們有何區別?
Set裡的元素是不能重複的,元素重複與否是使用equals()方法進行判斷的。

   equals()和==方法決定引用值是否指向同一物件equals()在類中被覆蓋,為的是當兩個分離的物件的內容和型別相配的話,返回真值。

 

68、你所知道的集合類都有哪些?主要方法?
最常用的集合類是 List 和 Map。 List 的具體實現包括 ArrayList 和 Vector,它們是可變大小的列表,比較適合構建、儲存和操作任何型別物件的元素列表。 List 適用於按數值索引訪問元素的情形。

Map 提供了一個更通用的元素儲存方法。 Map 集合類用於儲存元素對(稱作"鍵"和"值"),其中每個鍵對映到一個值。

 

ArrayList/VectoràList

                    àCollection

HashSet/TreeSetàSet

 

PropetiesàHashTable

                                   àMap

       Treemap/HashMap

 

我記的不是方法名,而是思想,我知道它們都有增刪改查的方法,但這些方法的具體名稱,我記得不是很清楚,對於set,大概的方法是add,remove, contains;對於map,大概的方法就是put,remove,contains等,因為,我只要在eclispe下按點操作符,很自然的這些方法就出來了。我記住的一些思想就是List類會有get(int index)這樣的方法,因為它可以按順序取元素,而set類中沒有get(int index)這樣的方法。List和set都可以迭代出所有元素,迭代時先要得到一個iterator物件,所以,set和list類都有一個iterator方法,用於返回那個iterator物件。map可以返回三個集合,一個是返回所有的key的集合,另外一個返回的是所有value的集合,再一個返回的key和value組合成的EntrySet物件的集合,map也有get方法,引數是key,返回值是key對應的value。

 

69、兩個物件值相同(x.equals(y) == true),但卻可有不同的hash code,這句話對不對?
對。

如果物件要儲存在HashSet或HashMap中,它們的equals相等,那麼,它們的hashcode值就必須相等。

如果不是要儲存在HashSet或HashMap,則與hashcode沒有什麼關係了,這時候hashcode不等是可以的,例如arrayList儲存的物件就不用實現hashcode,當然,我們沒有理由不實現,通常都會去實現的。

70、TreeSet裡面放物件,如果同時放入了父類和子類的例項物件,那比較時使用的是父類的compareTo方法,還是使用的子類的compareTo方法,還是拋異常!
(應該是沒有針對問題的確切的答案,當前的add方法放入的是哪個物件,就呼叫哪個物件的compareTo方法,至於這個compareTo方法怎麼做,就看當前這個物件的類中是如何編寫這個方法的)

實驗程式碼:

public class Parent implements Comparable {

    private int age = 0;

    public Parent(int age){

       this.age = age;

    }

    public int compareTo(Object o) {

       // TODO Auto-generated method stub

       System.out.println("method of parent");

       Parent o1 = (Parent)o;

       return age>o1.age?1:age<o1.age?-1:0;

    }

 

}

 

public class Child extends Parent {

 

    public Child(){

       super(3);

    }

    public int compareTo(Object o) {

 

           // TODO Auto-generated method stub

           System.out.println("method of child");

//         Child o1 = (Child)o;

           return 1;

 

    }

}

 

public class TreeSetTest {

 

    /**

     * @param args

     */

    public static void main(String[] args) {

       // TODO Auto-generated method stub

       TreeSet set = new TreeSet();

       set.add(new Parent(3));

       set.add(new Child());

       set.add(new Parent(4));

       System.out.println(set.size());

    }

 

}

 

71、說出一些常用的類,包,介面,請各舉5個
要讓人家感覺你對java ee開發很熟,所以,不能僅僅只列core java中的那些東西,要多列你在做ssh專案中涉及的那些東西。就寫你最近寫的那些程式中涉及的那些類。

 

常用的類:BufferedReader  BufferedWriter  FileReader  FileWirter  String  Integer

java.util.Date,System,Class,List,HashMap

 

常用的包:java.lang   java.io  java.util  java.sql ,javax.servlet,org.apache.strtuts.action,org.hibernate

常用的介面:Remote  List  Map  Document  NodeList ,Servlet,HttpServletRequest,HttpServletResponse,Transaction(Hibernate)、Session(Hibernate),HttpSession

72、java中有幾種型別的流?JDK為每種型別的流提供了一些抽象類以供繼承,請說出他們分別是哪些類?
位元組流,字元流。位元組流繼承於InputStream OutputStream,字元流繼承於InputStreamReader OutputStreamWriter。在java.io包中還有許多其他的流,主要是為了提高效能和使用方便。

73、位元組流與字元流的區別
       要把一片二進位制資料資料逐一輸出到某個裝置中,或者從某個裝置中逐一讀取一片二進位制資料,不管輸入輸出裝置是什麼,我們要用統一的方式來完成這些操作,用一種抽象的方式進行描述,這個抽象描述方式起名為IO流,對應的抽象類為OutputStream和InputStream ,不同的實現類就代表不同的輸入和輸出裝置,它們都是針對位元組進行操作的。

       在應用中,經常要完全是字元的一段文字輸出去或讀進來,用位元組流可以嗎?計算機中的一切最終都是二進位制的位元組形式存在。對於“中國”這些字元,首先要得到其對應的位元組,然後將位元組寫入到輸出流。讀取時,首先讀到的是位元組,可是我們要把它顯示為字元,我們需要將位元組轉換成字元。由於這樣的需求很廣泛,人家專門提供了字元流的包裝類。

  底層裝置永遠只接受位元組資料,有時候要寫字串到底層裝置,需要將字串轉成位元組再進行寫入。字元流是位元組流的包裝,字元流則是直接接受字串,它內部將串轉成位元組,再寫入底層裝置,這為我們向IO設別寫入或讀取字串提供了一點點方便。

  字元向位元組轉換時,要注意編碼的問題,因為字串轉成位元組陣列,

  其實是轉成該字元的某種編碼的位元組形式,讀取也是反之的道理。

 

講解位元組流與字元流關係的程式碼案例:

import java.io.BufferedReader;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.FileReader;

import java.io.FileWriter;

import java.io.InputStreamReader;

import java.io.PrintWriter;

 

public class IOTest {

   public static void main(String[] args) throws Exception {

        String str = "中國人";

        /*FileOutputStream fos  = new FileOutputStream("1.txt");

       

        fos.write(str.getBytes("UTF-8"));

        fos.close();*/

       

        /*FileWriter fw = new FileWriter("1.txt");

        fw.write(str);

        fw.close();*/

        PrintWriter pw = new PrintWriter("1.txt","utf-8");

        pw.write(str);

        pw.close();

       

        /*FileReader fr = new FileReader("1.txt");

        char[] buf = new char[1024];

        int len = fr.read(buf);

        String myStr = new String(buf,0,len);

        System.out.println(myStr);*/

        /*FileInputStream fr = new FileInputStream("1.txt");

        byte[] buf = new byte[1024];

        int len = fr.read(buf);

        String myStr = new String(buf,0,len,"UTF-8");

        System.out.println(myStr);*/

        BufferedReader br = new BufferedReader(

                      new InputStreamReader(

                             new FileInputStream("1.txt"),"UTF-8"

                             )

                      );

        String myStr = br.readLine();

        br.close();

        System.out.println(myStr);

   }

 

}

74、什麼是java序列化,如何實現java序列化?或者請解釋Serializable介面的作用。
 

我們有時候將一個java物件變成位元組流的形式傳出去或者從一個位元組流中恢復成一個java物件,例如,要將java物件儲存到硬碟或者傳送給網路上的其他計算機,這個過程我們可以自己寫程式碼去把一個java物件變成某個格式的位元組流再傳輸,但是,jre本身就提供了這種支援,我們可以呼叫OutputStream的writeObject方法來做,如果要讓java 幫我們做,要被傳輸的物件必須實現serializable介面,這樣,javac編譯時就會進行特殊處理,編譯的類才可以被writeObject方法操作,這就是所謂的序列化。需要被序列化的類必須實現Serializable介面,該介面是一個mini介面,其中沒有需要實現的方法,implements Serializable只是為了標註該物件是可被序列化的。

 

 

例如,在web開發中,如果物件被儲存在了Session中,tomcat在重啟時要把Session物件序列化到硬碟,這個物件就必須實現Serializable介面。如果物件要經過分散式系統進行網路傳輸或通過rmi等遠端呼叫,這就需要在網路上傳輸物件,被傳輸的物件就必須實現Serializable介面。

 

75、描述一下JVM載入class檔案的原理機制?
JVM中類的裝載是由ClassLoader和它的子類來實現的,Java ClassLoader 是一個重要的Java執行時系統元件。它負責在執行時查詢和裝入類檔案的類。

 

76、heap和stack有什麼區別。
java的記憶體分為兩類,一類是棧記憶體,一類是堆記憶體。棧記憶體是指程式進入一個方法時,會為這個方法單獨分配一塊私屬儲存空間,用於儲存這個方法內部的區域性變數,當這個方法結束時,分配給這個方法的棧會釋放,這個棧中的變數也將隨之釋放。

堆是與棧作用不同的記憶體,一般用於存放不放在當前方法棧中的那些資料,例如,使用new建立的物件都放在堆裡,所以,它不會隨方法的結束而消失。方法中的區域性變數使用final修飾後,放在堆中,而不是棧中。

 

77、GC是什麼? 為什麼要有GC?   
GC是垃圾收集的意思(Gabage Collection),記憶體處理是程式設計人員容易出現問題的地方,忘記或者錯誤的記憶體回收會導致程式或系統的不穩定甚至崩潰,Java提供的GC功能可以自動監測物件是否超過作用域從而達到自動回收記憶體的目的,Java語言沒有提供釋放已分配記憶體的顯示操作方法。

 

78、垃圾回收的優點和原理。並考慮2種回收機制。
Java語言中一個顯著的特點就是引入了垃圾回收機制,使c++程式設計師最頭疼的記憶體管理的問題迎刃而解,它使得Java程式設計師在編寫程式的時候不再需要考慮記憶體管理。由於有個垃圾回收機制,Java中的物件不再有"作用域"的概念,只有物件的引用才有"作用域"。垃圾回收可以有效的防止記憶體洩露,有效的使用可以使用的記憶體。垃圾回收器通常是作為一個單獨的低階別的執行緒執行,不可預知的情況下對記憶體堆中已經死亡的或者長時間沒有使用的物件進行清楚和回收,程式設計師不能實時的呼叫垃圾回收器對某個物件或所有物件進行垃圾回收。回收機制有分代複製垃圾回收和標記垃圾回收,增量垃圾回收。

 

79、垃圾回收器的基本原理是什麼?垃圾回收器可以馬上回收記憶體嗎?有什麼辦法主動通知虛擬機器進行垃圾回收?
對於GC來說,當程式設計師建立物件時,GC就開始監控這個物件的地址、大小以及使用情況。通常,GC採用有向圖的方式記錄和管理堆(heap)中的所有物件。通過這種方式確定哪些物件是"可達的",哪些物件是"不可達的"。當GC確定一些物件為"不可達"時,GC就有責任回收這些記憶體空間。可以。程式設計師可以手動執行System.gc(),通知GC執行,但是Java語言規範並不保證GC一定會執行。

 

 

80、什麼時候用assert。
assertion(斷言)在軟體開發中是一種常用的除錯方式,很多開發語言中都支援這種機制。在實現中,assertion就是在程式中的一條語句,它對一個boolean表示式進行檢查,一個正確程式必須保證這個boolean表示式的值為true;如果該值為false,說明程式已經處於不正確的狀態下,assert將給出警告或退出。一般來說,assertion用於保證程式最基本、關鍵的正確性。assertion檢查通常在開發和測試時開啟。為了提高效能,在軟體釋出後,assertion檢查通常是關閉的。

package com.huawei.interview;

 

public class AssertTest {

 

    /**

     * @param args

     */

    public static void main(String[] args) {

       // TODO Auto-generated method stub

       int i = 0;

       for(i=0;i<5;i++)

       {

           System.out.println(i);

       }

       //假設程式不小心多了一句--i;

       --i;

       assert i==5;     

    }

 

}

 

81、java中會存在記憶體洩漏嗎,請簡單描述。
所謂記憶體洩露就是指一個不再被程式使用的物件或變數一直被佔據在記憶體中。java中有垃圾回收機制,它可以保證一物件不再被引用的時候,即物件程式設計了孤兒的時候,物件將自動被垃圾回收器從記憶體中清除掉。由於Java 使用有向圖的方式進行垃圾回收管理,可以消除引用迴圈的問題,例如有兩個物件,相互引用,只要它們和根程式不可達的,那麼GC也是可以回收它們的,例如下面的程式碼可以看到這種情況的記憶體回收:

package com.huawei.interview;

 

import java.io.IOException;

 

public class GarbageTest {

 

    /**

     * @param args

     * @throws IOException

     */

    public static void main(String[] args) throws IOException {

       // TODO Auto-generated method stub

       try {

           gcTest();

       } catch (IOException e) {

           // TODO Auto-generated catch block

           e.printStackTrace();

       }

       System.out.println("has exited gcTest!");

       System.in.read();

       System.in.read();   

       System.out.println("out begin gc!");     

       for(int i=0;i<100;i++)

       {

           System.gc();

           System.in.read();

           System.in.read();

       }

    }

 

    private static void gcTest() throws IOException {

       System.in.read();

       System.in.read();   

       Person p1 = new Person();

       System.in.read();

       System.in.read();   

       Person p2 = new Person();

       p1.setMate(p2);

       p2.setMate(p1);

       System.out.println("before exit gctest!");

       System.in.read();

       System.in.read();   

       System.gc();

       System.out.println("exit gctest!");

    }

 

    private static class Person

    {

       byte[] data = new byte[20000000];

       Person mate = null;

       public void setMate(Person other)

       {

           mate = other;

       }

    }

}

 

java中的記憶體洩露的情況:長生命週期的物件持有短生命週期物件的引用就很可能發生記憶體洩露,儘管短生命週期物件已經不再需要,但是因為長生命週期物件持有它的引用而導致不能被回收,這就是java中記憶體洩露的發生場景,通俗地說,就是程式設計師可能建立了一個物件,以後一直不再使用這個物件,這個物件卻一直被引用,即這個物件無用但是卻無法被垃圾回收器回收的,這就是java中可能出現記憶體洩露的情況,例如,快取系統,我們載入了一個物件放在快取中(例如放在一個全域性map物件中),然後一直不再使用它,這個物件一直被快取引用,但卻不再被使用。

檢查java中的記憶體洩露,一定要讓程式將各種分支情況都完整執行到程式結束,然後看某個物件是否被使用過,如果沒有,則才能判定這個物件屬於記憶體洩露。

 

如果一個外部類的例項物件的方法返回了一個內部類的例項物件,這個內部類物件被長期引用了,即使那個外部類例項物件不再被使用,但由於內部類持久外部類的例項物件,這個外部類物件將不會被垃圾回收,這也會造成記憶體洩露。

 

下面內容來自於網上(主要特點就是清空堆疊中的某個元素,並不是徹底把它從陣列中拿掉,而是把儲存的總數減少,本人寫得可以比這個好,在拿掉某個元素時,順便也讓它從陣列中消失,將那個元素所在的位置的值設定為null即可):

我實在想不到比那個堆疊更經典的例子了,以致於我還要引用別人的例子,下面的例子不是我想到的,是書上看到的,當然如果沒有在書上看到,可能過一段時間我自己也想的到,可是那時我說是我自己想到的也沒有人相信的。

    public class Stack {
    private Object[] elements=new Object[10];
    private int size = 0;
    public void push(Object e){
    ensureCapacity();
    elements[size++] = e;
    }
    public Object pop(){
    if( size == 0)

    throw new EmptyStackException();
    return elements[--size];
    }
    private void ensureCapacity(){
    if(elements.length == size){
    Object[] oldElements = elements;
    elements = new Object[2 * elements.length+1];
    System.arraycopy(oldElements,0, elements, 0, size);
    }
    }
    }
    上面的原理應該很簡單,假如堆疊加了10個元素,然後全部彈出來,雖然堆疊是空的,沒有我們要的東西,但是這是個物件是無法回收的,這個才符合了記憶體洩露的兩個條件:無用,無法回收。

    但是就是存在這樣的東西也不一定會導致什麼樣的後果,如果這個堆疊用的比較少,也就浪費了幾個K記憶體而已,反正我們的記憶體都上G了,哪裡會有什麼影響,再說這個東西很快就會被回收的,有什麼關係。下面看兩個例子。

    例子1
    public class Bad{
    public static Stack s=Stack();
    static{
    s.push(new Object());
    s.pop(); //這裡有一個物件發生記憶體洩露
    s.push(new Object()); //上面的物件可以被回收了,等於是自愈了
    }
    }
    因為是static,就一直存在到程式退出,但是我們也可以看到它有自愈功能,就是說如果你的Stack最多有100個物件,那麼最多也就只有100個物件無法被回收其實這個應該很容易理解,Stack內部持有100個引用,最壞的情況就是他們都是無用的,因為我們一旦放新的進取,以前的引用自然消失!

 

記憶體洩露的另外一種情況:當一個物件被儲存進HashSet集合中以後,就不能修改這個物件中的那些參與計算雜湊值的欄位了,否則,物件修改後的雜湊值與最初儲存進HashSet集合中時的雜湊值就不同了,在這種情況下,即使在contains方法使用該物件的當前引用作為的引數去HashSet集合中檢索物件,也將返回找不到物件的結果,這也會導致無法從HashSet集合中單獨刪除當前物件,造成記憶體洩露。

 

 

 

82、能不能自己寫個類,也叫java.lang.String?
 

可以,但在應用的時候,需要用自己的類載入器去載入,否則,系統的類載入器永遠只是去載入jre.jar包中的那個java.lang.String。由於在tomcat的web應用程式中,都是由webapp自己的類載入器先自己載入WEB-INF/classess目錄中的類,然後才委託上級的類載入器載入,如果我們在tomcat的web應用程式中寫一個java.lang.String,這時候Servlet程式載入的就是我們自己寫的java.lang.String,但是這麼幹就會出很多潛在的問題,原來所有用了java.lang.String類的都將出現問題。

 

雖然java提供了endorsed技術,可以覆蓋jdk中的某些類,具體做法是….。但是,能夠被覆蓋的類是有限制範圍,反正不包括java.lang這樣的包中的類。

 

(下面的例如主要是便於大家學習理解只用,不要作為答案的一部分,否則,人家懷疑是題目洩露了)例如,執行下面的程式:

package java.lang;

 

public class String {

 

    /**

     * @param args

     */

    public static void main(String[] args) {

       // TODO Auto-generated method stub

       System.out.println("string");

    }

 

}

報告的錯誤如下:

java.lang.NoSuchMethodError: main

Exception in thread "main"

這是因為載入了jre自帶的java.lang.String,而該類中沒有main方法。

 

83. Java程式碼查錯
1.
abstract class Name {
   private String name;
   public abstract boolean isStupidName(String name) {}
}
大俠們,這有何錯誤?
答案: 錯。abstract method必須以分號結尾,且不帶花括號。
2.
public class Something {
   void doSomething () {
       private String s = "";
       int l = s.length();
   }
}
有錯嗎?
答案: 錯。區域性變數前不能放置任何訪問修飾符 (private,public,和protected)。final可以用來修飾區域性變數
(final如同abstract和strictfp,都是非訪問修飾符,strictfp只能修飾class和method而非variable)。
3.
abstract class Something {
   private abstract String doSomething ();
}
這好像沒什麼錯吧?
答案: 錯。abstract的methods不能以private修飾。abstract的methods就是讓子類implement(實現)具體細節的,怎麼可以用private把abstract
method封鎖起來呢? (同理,abstract method前不能加final)。
4.
public class Something {
   public int addOne(final int x) {
       return ++x;
   }
}
這個比較明顯。
答案: 錯。int x被修飾成final,意味著x不能在addOne method中被修改。
5.
public class Something {
   public static void main(String[] args) {
       Other o = new Other();
       new Something().addOne(o);
   }
   public void addOne(final Other o) {
       o.i++;
   }
}
class Other {
   public int i;
}
和上面的很相似,都是關於final的問題,這有錯嗎?
答案: 正確。在addOne method中,引數o被修飾成final。如果在addOne method裡我們修改了o的reference
(比如: o = new Other();),那麼如同上例這題也是錯的。但這裡修改的是o的member vairable
(成員變數),而o的reference並沒有改變。
6.
class Something {
    int i;
    public void doSomething() {
        System.out.println("i = " + i);
    }
}
有什麼錯呢? 看不出來啊。
答案: 正確。輸出的是"i = 0"。int i屬於instant variable (例項變數,或叫成員變數)。instant variable有default value。int的default value是0。
7.
class Something {
    final int i;
    public void doSomething() {
        System.out.println("i = " + i);
    }
}
和上面一題只有一個地方不同,就是多了一個final。這難道就錯了嗎?
答案: 錯。final int i是個final的instant variable (例項變數,或叫成員變數)。final的instant variable沒有default value,必須在constructor (構造器)結束之前被賦予一個明確的值。可以修改為"final int i = 0;"。
8.
public class Something {
     public static void main(String[] args) {
        Something s = new Something();
        System.out.println("s.doSomething() returns " + doSomething());
    }
    public String doSomething() {
        return "Do something ...";
    }
}
 看上去很完美。
答案: 錯。看上去在main裡call doSomething沒有什麼問題,畢竟兩個methods都在同一個class裡。但仔細看,main是static的。static method不能直接call non-static methods。可改成"System.out.println("s.doSomething() returns " + s.doSomething());"。同理,static method不能訪問non-static instant variable。
9.
此處,Something類的檔名叫OtherThing.java
class Something {
    private static void main(String[] something_to_do) {       
        System.out.println("Do something ...");
    }
}
 這個好像很明顯。
答案: 正確。從來沒有人說過Java的Class名字必須和其檔名相同。但public class的名字必須和檔名相同。
10.
interface  A{
   int x = 0;
}
class B{
   int x =1;
}
class C extends B implements A {
   public void pX(){
      System.out.println(x);
   }
   public static void main(String[] args) {
      new C().pX();
   }
}
答案:錯誤。在編譯時會發生錯誤(錯誤描述不同的JVM有不同的資訊,意思就是未明確的x呼叫,兩個x都匹配(就象在同時import java.util和java.sql兩個包時直接宣告Date一樣)。對於父類的變數,可以用super.x來明確,而介面的屬性預設隱含為 public static final.所以可以通過A.x來明確。
11.
interface Playable {
    void play();
}
interface Bounceable {
    void play();
}
interface Rollable extends Playable, Bounceable {
    Ball ball = new Ball("PingPang");
}
class Ball implements Rollable {
    private String name;
    public String getName() {
        return name;
    }
    public Ball(String name) {
        this.name = name;       
    }
   public void play() {
        ball = new Ball("Football");
        System.out.println(ball.getName());
    }
}
這個錯誤不容易發現。
答案: 錯。"interface Rollable extends Playable, Bounceable"沒有問題。interface可繼承多個interfaces,所以這裡沒錯。問題出在interface Rollable裡的"Ball ball = new Ball("PingPang");"。任何在interface裡宣告的interface variable (介面變數,也可稱成員變數),預設為public static final。也就是說"Ball ball = new Ball("PingPang");"實際上是"public static final Ball ball = new Ball("PingPang");"。在Ball類的Play()方法中,"ball = new Ball("Football");"改變了ball的reference,而這裡的ball來自Rollable interface,Rollable interface裡的ball是public static final的,final的object是不能被改變reference的。因此編譯器將在"ball = new Ball("Football");"這裡顯示有錯。

 

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/saviola1999/archive/2010/07/28/5771737.aspx

相關文章