知識總結(二)

  • 動態規劃算法
    一、基本概念
    動態規劃過程是:每次決策依賴于當前狀態,又隨即引起狀態的轉移。一個決策序列就是在變化的狀態中產生出來的,所以,這種多階段最優化決策解決問題的過程就稱為動態規劃。
    二、基本思想與策略
    基本思想與分治法類似,也是將待求解的問題分解為若干個子問題(階段),按順序求解子階段,前一子問題的解,為后一子問題的求解提供了有用的信息。在求解任一子問題時,列出各種可能的局部解,通過決策保留那些有可能達到最優的局部解,丟棄其他局部解。依次解決各子問題,最后一個子問題就是初始問題的解。
    由于動態規劃解決的問題多數有重疊子問題這個特點,為減少重復計算,對每一個子問題只解一次,將其不同階段的不同狀態保存在一個二維數組中。
    與分治法最大的差別是:適合于用動態規劃法求解的問題,經分解后得到的子問題往往不是互相獨立的(即下一個子階段的求解是建立在上一個子階段的解的基礎上,進行進一步的求解)。
    以上都過于理論,還是看看常見的動態規劃問題吧!!!
    三、常見動態規劃問題
    1、找零錢問題
    有數組penny,penny中所有的值都為正數且不重復。每個值代表一種面值的貨幣,每種面值的貨幣可以使用任意張,再給定一個整數aim(小于等于1000)代表要找的錢數,求換錢有多少種方法。給定數組penny及它的大小(小于等于50),同時給定一個整數aim,請返回有多少種方法可以湊成aim。測試樣例:[1,2,4],3,3返回:2
    解析:設dp[n][m]為使用前n中貨幣湊成的m的種數,那么就會有兩種情況:
    使用第n種貨幣:dp[n-1][m]+dp[n-1][m-peney[n]]
    不用第n種貨幣:dp[n-1][m],為什么不使用第n種貨幣呢,因為penney[n]>m。
    這樣就可以求出當m>=penney[n]時 dp[n][m] = dp[n-1][m]+dp[n-1][m-peney[n]],否則,dp[n][m] = dp[n-1][m]
    代碼如下:
import java.util.*;  
  
public class Exchange {  
    public int countWays(int[] penny, int n, int aim) {  
        // write code here  
        if(n==0||penny==null||aim<0){  
         return 0;     
        }  
        int[][] pd = new int[n][aim+1];  
        for(int i=0;i<n;i++){  
         pd[i][0] = 1;     
        }  
        for(int i=1;penny[0]*i<=aim;i++){  
         pd[0][penny[0]*i] = 1;     
        }  
        for(int i=1;i<n;i++){  
            for(int j=0;j<=aim;j++){  
                if(j>=penny[i]){  
                    pd[i][j] = pd[i-1][j]+pd[i][j-penny[i]];  
                }else{  
                    pd[i][j] = pd[i-1][j];  
                }  
            }  
        }  
        return pd[n-1][aim];  
    }  
}

2、走方格問題
有一個矩陣map,它每個格子有一個權值。從左上角的格子開始每次只能向右或者向下走,最后到達右下角的位置,路徑上所有的數字累加起來就是路徑和,返回所有的路徑中最小的路徑和。給定一個矩陣map及它的行數n和列數m,請返回最小路徑和。保證行列數均小于等于100.測試樣例:[[1,2,3],[1,1,1]],2,3返回:4
解析:設dp[n][m]為走到n*m位置的路徑長度,那么顯而易見dp[n][m] = min(dp[n-1][m],dp[n][m-1]);

代碼如下:

import java.util.*;  
  
public class MinimumPath {  
    public int getMin(int[][] map, int n, int m) {  
        // write code here  
       int[][] dp = new int[n][m];  
        for(int i=0;i<n;i++){  
            for(int j=0;j<=i;j++){  
             dp[i][0]+=map[j][0];      
            }  
        }  
        for(int i=0;i<m;i++){  
            for(int j=0;j<=i;j++){  
             dp[0][i]+=map[0][j];      
            }  
        }  
        for(int i=1;i<n;i++){  
            for(int j=1;j<m;j++){  
             dp[i][j] = min(dp[i][j-1]+map[i][j],dp[i-1][j]+map[i][j]);     
            }  
        }  
        return dp[n-1][m-1];  
    }  
    public int min(int a,int b){  
        if(a>b){  
         return b;     
        }else{  
         return a;     
        }  
    }  
}

3、走臺階問題
有n級臺階,一個人每次上一級或者兩級,問有多少種走完n級臺階的方法。為了防止溢出,請將結果Mod 1000000007給定一個正整數int n,請返回一個數,代表上樓的方式數。保證n小于等于100000。測試樣例:1返回:1
解析:這是一個非常經典的為題,設f(n)為上n級臺階的方法,要上到n級臺階的最后一步有兩種方式:從n-1級臺階走一步;從n-1級臺階走兩步,于是就有了這個公式f(n) = f(n-1)+f(n-2);
代碼如下:

import java.util.*;  
  
public class GoUpstairs {  
    public int countWays(int n) {  
        // write code here  
        if(n<=2)  
            return n;  
        int f = 1%1000000007;  
        int s = 2%1000000007;  
        int t = 0;  
        for(int i=3;i<=n;i++){  
         t = (f+s)%1000000007;  
         f = s;  
         s = t;  
        }  
       return t;   
    }  
}

4、最長公共序列數
給定兩個字符串A和B,返回兩個字符串的最長公共子序列的長度。例如,A="1A2C3D4B56”,B="B1D23CA45B6A”,”123456"或者"12C4B6"都是最長公共子序列。給定兩個字符串A和B,同時給定兩個串的長度n和m,請返回最長公共子序列的長度。保證兩串長度均小于等于300。測試樣例:"1A2C3D4B56",10,"B1D23CA45B6A",12返回:6
解析:設dp[n][m] ,為A的前n個字符與B的前m個字符的公共序列長度,則當A[n]==B[m]的時候,dp[i][j] = max(dp[i-1][j-1]+1,dp[i-1][j],dp[i][j-1]),否則,dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
代碼如下:

import java.util.*;  
  
public class LCS {  
    public int findLCS(String A, int n, String B, int m) {  
        // write code here  
        int[][] dp = new int[n][m];  
        char[] a = A.toCharArray();  
        char[] b = B.toCharArray();  
       for(int i=0;i<n;i++){  
           if(a[i]==b[0]){  
               dp[i][0] = 1;  
               for(int j=i+1;j<n;j++){  
                   dp[j][0] = 1;  
               }  
               break;  
           }  
             
       }  
         for(int i=0;i<m;i++){  
           if(a[0]==b[i]){  
               dp[0][i] = 1;  
               for(int j=i+1;j<m;j++){  
                   dp[0][j] = 1;  
               }  
               break;  
           }  
             
       }  
       for(int i=1;i<n;i++){  
           for(int j=1;j<m;j++){  
               if(a[i]==b[j]){  
                  dp[i][j] = max(dp[i-1][j-1]+1,dp[i-1][j],dp[i][j-1]);  
               }else{  
                   dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);  
               }  
                     
           }  
       }   
          
        return dp[n-1][m-1];  
    }  
    public int max(int a,int b,int c){  
        int max = a;  
        if(b>max)  
            max=b;  
        if(c>max)  
            max = c;  
        return max;  
    }  
}

Java篇

  • volatile關鍵字的作用
    volatile這個關鍵字可能很多朋友都聽說過,或許也都用過。在Java 5之前,它是一個備受爭議的關鍵字,因為在程序中使用它往往會導致出人意料的結果。在Java 5之后,volatile關鍵字才得以重獲生機。
    volatile 關鍵字作用是,使系統中所有線程對該關鍵字修飾的變量共享可見,可以禁止線程的工作內存對volatile修飾的變量進行緩存。
    volatile 2個使用場景:
    1.可見性:Java提供了volatile關鍵字來保證可見性。
      當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,它會去內存中讀取新值。
      而普通的共享變量不能保證可見性,因為普通共享變量被修改之后,什么時候被寫入主存是不確定的,當其他線程去讀取時,此時內存中可能還是原來的舊值,因此無法保證可見性。
      另外,通過synchronized和Lock也能夠保證可見性,synchronized和Lock能保證同一時刻只有一個線程獲取鎖然后執行同步代碼,并且在釋放鎖之前會將對變量的修改刷新到主存當中。因此可以保證可見性。
    先看一段代碼,假如線程1先執行,線程2后執行:
//線程1
boolean stop = false;
while(!stop){
doSomething();
}
//線程2
stop = true;

這段代碼是很典型的一段代碼,很多人在中斷線程時可能都會采用這種標記辦法。但是事實上,這段代碼會完全運行正確么?即一定會將線程中斷么?不一定,也許在大多數時候,這個代碼能夠把線程中斷,但是也有可能會導致無法中斷線程(雖然這個可能性很小,但是只要一旦發生這種情況就會造成死循環了)。
  下面解釋一下這段代碼為何有可能導致無法中斷線程。在前面已經解釋過,每個線程在運行過程中都有自己的工作內存,那么線程1在運行的時候,會將stop變量的值拷貝一份放在自己的工作內存當中。
  那么當線程2更改了stop變量的值之后,但是還沒來得及寫入主存當中,線程2轉去做其他事情了,那么線程1由于不知道線程2對stop變量的更改,因此還會一直循環下去。
  但是用volatile修飾之后就變得不一樣了:
  第一:使用volatile關鍵字會強制將修改的值立即寫入主存;
  第二:使用volatile關鍵字的話,當線程2進行修改時,會導致線程1的工作內存中緩存變量stop的緩存行無效(反映到硬件層的話,就是CPU的L1或者L2緩存中對應的緩存行無效);
  第三:由于線程1的工作內存中緩存變量stop的緩存行無效,所以線程1再次讀取變量stop的值時會去主存讀取。
  那么在線程2修改stop值時(當然這里包括2個操作,修改線程2工作內存中的值,然后將修改后的值寫入內存),會使得線程1的工作內存中緩存變量stop的緩存行無效,然后線程1讀取時,發現自己的緩存行無效,它會等待緩存行對應的主存地址被更新之后,然后去對應的主存讀取最新的值。
  那么線程1讀取到的就是最新的正確的值。
2.保證有序性

volatile boolean inited = false;
//線程1:
context = loadContext(); 
inited = true; 
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

確保context已經初始化完成。
3.double check

class Singleton{
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}
  • synchronize關鍵字的作用
    Java 關鍵字volatile 與 synchronized 作用與區別
    1,volatile
    它所修飾的變量不保留拷貝,直接訪問主內存中的。
    在Java內存模型中,有main memory,每個線程也有自己的memory (例如寄存器)。為了性能,一個線程會在自己的memory中保持要訪問的變量的副本。這樣就會出現同一個變 量在某個瞬間,在一個線程的memory中的值可能與另外一個線程memory中的值,或者main memory中的值不一致的情況。 一個變量聲明為volatile,就意味著這個變量是隨時會被其他線程修改的,因此不能將它cache在線程memory中。
    2,synchronized
    當它用來修飾一個方法或者一個代碼塊的時候,能夠保證在同一時刻最多只有一個線程執行該段代碼。
    一、當兩個并發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以后才能執行該代碼塊。
    二、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
    三、尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
    四、當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。
    五、以上規則對其它對象鎖同樣適用.
    區別:
    一、volatile是變量修飾符,而synchronized則作用于一段代碼或方法。
    二、volatile只是在線程內存和“主”內存間同步某個變量的值;而synchronized通過鎖定和解鎖某個監視器同步所有變量的值。顯然synchronized要比volatile消耗更多資源。

  • HashMap、HashTable、ConcurrentHashMap的區別
    HashMap和HashTable的區別一種比較簡單的回答是:
    (1)HashMap是非線程安全的,HashTable是線程安全的。
    (2)HashMap的鍵和值都允許有null存在,而HashTable則都不行。
    (3)因為線程安全、哈希效率的問題,HashMap效率比HashTable的要高。
    但是如果繼續追問:Java中的另一個線程安全的與HashMap功能極其類似的類是什么?
    同樣是線程安全,它與HashTable在線程同步上有什么不同?帶著這些問題,開始今天的文章。
    本文為原創,相關內容會持續維護,轉載請標明出處:http://blog.csdn.net/seu_calvin/article/details/52653711


1. ****HashMap概述


Java中的數據存儲方式有兩種結構,一種是數組,另一種就是鏈表,前者的特點是連續空間,尋址迅速,但是在增刪元素的時候會有較大幅度的移動,所以數組的特點是查詢速度快,增刪較慢。
而鏈表由于空間不連續,尋址困難,增刪元素只需修改指針,所以鏈表的特點是查詢速度慢、增刪快。
那么有沒有一種數據結構來綜合一下數組和鏈表以便發揮他們各自的優勢?答案就是哈希表。哈希表的存儲結構如下圖所示:

image.png

從上圖中,我們可以發現哈希表是由數組+鏈表組成的,一個長度為16的數組中,每個元素存儲的是一個鏈表的頭結點,通過功能類似于hash(key.hashCode())%len的操作,獲得要添加的元素所要存放的的數組位置。
HashMap的哈希算法實際操作是通過位運算,比取模運算效率更高,同樣能達到使其分布均勻的目的,后面會介紹。
鍵值對所存放的數據結構其實是HashMap中定義的一個Entity內部類,數組來實現的,屬性有key、value和指向下一個Entity的next。


2. HashMap初始化


HashMap有兩種常用的構造方法:
第一種是不需要參數的構造方法:

static final int DEFAULT_INITIAL_CAPACITY = 16; //初始數組長度為16  
static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量為2的30次方  
//裝載因子用來衡量HashMap滿的程度  
//計算HashMap的實時裝載因子的方法為:size/capacity  
static final float DEFAULT_LOAD_FACTOR = 0.75f; //裝載因子  
  
public HashMap() {    
    this.loadFactor = DEFAULT_LOAD_FACTOR;    
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);  
//默認數組長度為16   
    table = new Entry[DEFAULT_INITIAL_CAPACITY];  
    init();    
}  

第二種是需要參數的構造方法:

public HashMap(int initialCapacity, float loadFactor) {    
        if (initialCapacity < 0)    
            throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);    
        if (initialCapacity > MAXIMUM_CAPACITY)    
            initialCapacity = MAXIMUM_CAPACITY;    
        if (loadFactor <= 0 || Float.isNaN(loadFactor))    
            throw new IllegalArgumentException("Illegal load factor: " + loadFactor);    
  
        // Find a power of 2 >= initialCapacity    
        int capacity = 1;    
        while (capacity < initialCapacity)    
            capacity <<= 1;    
    
        this.loadFactor = loadFactor;    
        threshold = (int)(capacity * loadFactor);    
        table = new Entry[capacity];    
        init();    
} 

從源碼可以看出,初始化的數組長度為capacity,capacity的值總是2的N次方,大小比第一個參數稍大或相等。

  1. HashMap的put操作
public V put(K key, V value) {    
        if (key == null)    
          return putForNullKey(value);    
        int hash = hash(key.hashCode());    
        int i = indexFor(hash, table.length);    
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {    
            Object k;    
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {    
                V oldValue = e.value;    
                e.value = value;    
                e.recordAccess(this);    
                return oldValue;    
            }    
        }          
modCount++;    
        addEntry(hash, key, value, i);    
        return null;    
}   

3.1 put進的key為null
從源碼中可以看出,HashMap是允許key為null的,會調用putForNullKey()方法:

private V putForNullKey(V value) {    
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {    
            if (e.key == null) {    
                V oldValue = e.value;    
                e.value = value;    
                e.recordAccess(this);    
                return oldValue;    
            }    
        }    
        modCount++;    
        addEntry(0, null, value, 0);    
        return null;    
}   
  
void addEntry(int hash, K key, V value, int bucketIndex) {    
    Entry<K,V> e = table[bucketIndex];    
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);    
        if (size++ >= threshold)    
            resize(2 * table.length);    
    }    

putForNullKey方法會遍歷以table[0]為鏈表頭的鏈表,如果存在key為null的KV,那么替換其value值并返回舊值。否則調用addEntry方法,這個方法也很簡單,將[null,value]放在table[0]的位置,并將新加入的鍵值對封裝成一個Entity對象,將其next指向原table[0]處的Entity實例。

size表示HashMap中存放的所有鍵值對的數量。
threshold = capacity*loadFactor,最后幾行代碼表示當HashMap的size大于threshold時會執行resize操作,將HashMap擴容為原來的2倍。擴容需要重新計算每個元素在數組中的位置,indexFor()方法中的table.length參數也證明了這一點。
但是擴容是一個非常消耗性能的操作,所以如果我們已經預知HashMap中元素的個數,那么預設元素的個數能夠有效的提高HashMap的性能。比如說我們有1000個元素,那么我們就該聲明new HashMap(2048),因為需要考慮默認的0.75的擴容因子和數組數必須是2的N次方。若使用聲明new HashMap(1024)那么put過程中會進行擴容。

3.2 put進的key不為null
將上述put方法中的相關代碼復制一下方便查看:

int hash = hash(key.hashCode());    
int i = indexFor(hash, table.length);    
for (Entry<K,V> e = table[i]; e != null; e = e.next) {    
    Object k;    
    if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {    
        V oldValue = e.value;    
        e.value = value;    
        e.recordAccess(this);    
        return oldValue;    
       }    
}          
modCount++;    
addEntry(hash, key, value, i);    
return null;    
}  

從源碼可以看出,第1、2行計算將要put進的鍵值對的數組的位置i。第4行判斷加入的key是否和以table[i]為鏈表頭的鏈表中所有的鍵值對有重復,若重復則替換value并返回舊值,若沒有重復則調用addEntry方法,上面對這個方法的邏輯已經介紹過了。
至此HashMap的put操作已經介紹完畢了。

  1. HashMap的get操作
public V get(Object key) {    
   if (key == null)    
       return getForNullKey();    
   int hash = hash(key.hashCode());    
   for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {    
            Object k;    
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
                return e.value;    
        }    
    return null;    
}    
  
private V getForNullKey() {    
   for (Entry<K,V> e = table[0]; e != null; e = e.next) {    
   if (e.key == null)    
     return e.value;    
    }    
    return null;    
}  

如果了解了前面的put操作,那么這里的get操作邏輯就很容易理解了,源碼中的邏輯已經非常非常清晰了。
需要注意的只有當找不到對應value時,返回的是null。或者value本身就是null。這是可以通過containsKey()來具體判斷。

了解了上面HashMap的put和get操作原理,可以通過下面這個小例題進行知識鞏固,題目是打印在數組中出現n/2以上的元素,我們便可以使用HashMap的特性來解決。

public class HashMapTest {    
    public static void main(String[] args) {    
        int [] a = {2,1,3,2,0,4,2,1,2,3,1,5,6,2,2,3};    
        Map<Integer, Integer> map = new HashMap<Integer,Integer>();    
        for(int i=0; i<a.length; i++){    
            if(map.containsKey(a[i])){    
                int tmp = map.get(a[i]);    
                tmp+=1;    
                map.put(a[i], tmp);    
            }else{    
                map.put(a[i], 1);    
            }    
        }    
        Set<Integer> set = map.keySet();          
for (Integer s : set) {    
            if(map.get(s)>=a.length/2){    
                System.out.println(s);    
            }    
        }  
    }    
}    

5. ****HashMap和HashTable的對比


HashTable和HashMap采用相同的存儲機制,二者的實現基本一致,不同的是:
(1)HashMap是非線程安全的,HashTable是線程安全的,內部的方法基本都經過synchronized修飾。
(2)因為同步、哈希性能等原因,性能肯定是HashMap更佳,因此HashTable已被淘汰。
(3) HashMap允許有null值的存在,而在HashTable中put進的鍵值只要有一個null,直接拋出NullPointerException。
(4)HashMap默認初始化數組的大小為16,HashTable為11。前者擴容時乘2,使用位運算取得哈希,效率高于取模。而后者為乘2加1,都是素數和奇數,這樣取模哈希結果更均勻。
這里本來我沒有仔細看兩者的具體哈希算法過程,打算粗略比較一下區別就過的,但是最近師姐面試美團移動開發時被問到了稍微具體一些的算法過程,我也是醉了…不過還是恭喜師姐面試成功,起薪20W,真是羨慕,希望自己一年后找工作也能順順利利的。
言歸正傳,看下兩種集合的hash算法。看源碼也不難理解。

//HashMap的散列函數,這里傳入參數為鍵值對的key  
static final int hash(Object key) {  
    int h;  
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);  
}   
//返回hash值的索引,h & (length-1)操作等價于 hash % length操作, 但&操作性能更優  
static int indexFor(int h, int length) {  
    // length must be a non-zero power of 2  
    return h & (length-1);  
}  
  
//HashTable的散列函數直接在put方法里實現了  
int hash = key.hashCode();  
int index = (hash & 0x7FFFFFFF) % tab.length;  
  1. HashTable和ConCurrentHashMap的對比
    先對ConcurrentHashMap進行一些介紹吧,它是線程安全的HashMap的實現。
    HashTable里使用的是synchronized關鍵字,這其實是對對象加鎖,鎖住的都是對象整體,當Hashtable的大小增加到一定的時候,性能會急劇下降,因為迭代時需要被鎖定很長的時間。
    ConcurrentHashMap算是對上述問題的優化,其構造函數如下,默認傳入的是16,0.75,16。
public ConcurrentHashMap(int paramInt1, float paramFloat, int paramInt2)  {    
    //…  
    int i = 0;    
    int j = 1;    
    while (j < paramInt2) {    
      ++i;    
      j <<= 1;    
    }    
    this.segmentShift = (32 - i);    
    this.segmentMask = (j - 1);    
    this.segments = Segment.newArray(j);    
    //…  
    int k = paramInt1 / j;    
    if (k * j < paramInt1)    
      ++k;    
    int l = 1;    
    while (l < k)    
      l <<= 1;    
    
    for (int i1 = 0; i1 < this.segments.length; ++i1)    
      this.segments[i1] = new Segment(l, paramFloat);    
  }    
  
public V put(K paramK, V paramV)  {    
    if (paramV == null)    
      throw new NullPointerException();    
    int i = hash(paramK.hashCode()); //這里的hash函數和HashMap中的不一樣  
    return this.segments[(i >>> this.segmentShift & this.segmentMask)].put(paramK, i, paramV, false);    
} 

ConcurrentHashMap引入了分割(Segment),上面代碼中的最后一行其實就可以理解為把一個大的Map拆分成N個小的HashTable,在put方法中,會根據hash(paramK.hashCode())來決定具體存放進哪個Segment,如果查看Segment的put操作,我們會發現內部使用的同步機制是基于lock操作的,這樣就可以對Map的一部分(Segment)進行上鎖,這樣影響的只是將要放入同一個Segment的元素的put操作,保證同步的時候,鎖住的不是整個Map(HashTable就是這么做的),相對于HashTable提高了多線程環境下的性能,因此HashTable已經被淘汰了。

  1. HashMap和ConCurrentHashMap的對比
    最后對這倆兄弟做個區別總結吧:
    (1)經過4.2的分析,我們知道ConcurrentHashMap對整個桶數組進行了分割分段(Segment),然后在每一個分段上都用lock鎖進行保護,相對于HashTable的syn關鍵字鎖的粒度更精細了一些,并發性能更好,而HashMap沒有鎖機制,不是線程安全的。
    (2)HashMap的鍵值對允許有null,但是ConCurrentHashMap都不允許。
  • 類加載機制
    類裝載器就是尋找類的字節碼文件,并構造出類在JVM內部表示的對象組件。在Java中,類裝載器把一個類裝入JVM中,要經過以下步驟:
    (1) 裝載:查找和導入Class文件;
    (2) 鏈接:把類的二進制數據合并到JRE中;
    (a)校驗:檢查載入Class文件數據的正確性;
    (b)準備:給類的靜態變量分配存儲空間;
    (c)解析:將符號引用轉成直接引用;
    (3) 初始化:對類的靜態變量,靜態代碼塊執行初始化操作

  • 面向對象編程的特點
    面向對象程序設計的基本特征有:
    1,封裝性:封裝性是指將對象相關的信息和行為狀態捆綁成一個單元,即將對象封裝為一個具體的類。封裝隱藏了對象的具體實現,當要操縱對象時,只需調用其中的方法,而不用管方法的具體實現。
    2,繼承性:一個類繼承另一個類,繼承者可以獲得被繼承類的所有方法和屬性,并且可以根據實際的需要添加新的方法或者對被繼承類中的方法進行覆寫,被繼承者稱為父類或者超類,繼承者稱為子類或導出類,繼承提高了程序代碼的可重用性,Java中一個子類只能繼承一個父類,Object類是所有類的最終父類。
    3,多態性:多態性是指不同的對象對同一事物而做出的相同行為,一個類A可以指向其自身類和其導出類,一個接口可以指向其接口實現類,在方法參數中,使用多態可以提高參數的靈活性。

  • Java虛擬機中的多態執行機制
    方法解析
    Class文件的編譯過程中不包含傳統編譯中的連接步驟,一切方法調用在Class文件里面存儲的都只是符號引用,而不是方法在實際運行時內存布局中的入口地址。這個特性給Java帶來了更強大的動態擴展能力,使得可以在類運行期間才能確定某些目標方法的直接引用,稱為動態連接,也有一部分方法的符號引用在類加載階段或第一次使用時轉化為直接引用,這種轉化稱為靜態解析。這在前面的“Java內存區域與內存溢出”一文中有提到。
    靜態解析成立的前提是:方法在程序真正執行前就有一個可確定的調用版本,并且這個方法的調用版本在運行期是不可改變的。換句話說,調用目標在編譯器進行編譯時就必須確定下來,這類方法的調用稱為解析。
    在Java語言中,符合“編譯器可知,運行期不可變”這個要求的方法主要有靜態方法和私有方法兩大類,前者與類型直接關聯,后者在外部不可被訪問,這兩種方法都不可能通過繼承或別的方式重寫出其他的版本,因此它們都適合在類加載階段進行解析。
    Java虛擬機里共提供了四條方法調用字節指令,分別是:

invokestatic:調用靜態方法。
invokespecial:調用實例構造器<init>方法、私有方法和父類方法。
invokevirtual:調用所有的虛方法。
invokeinterface:調用接口方法,會在運行時再確定一個實現此接口的對象。

只要能被invokestatic和invokespecial指令調用的方法,都可以在解析階段確定唯一的調用版本,符合這個條件的有靜態方法、私有方法、實例構造器和父類方法四類,它們在類加載時就會把符號引用解析為該方法的直接引用。這些方法可以稱為非虛方法(還包括final方法),與之相反,其他方法就稱為虛方法(final方法除外)。這里要特別說明下final方法,雖然調用final方法使用的是invokevirtual指令,但是由于它無法覆蓋,沒有其他版本,所以也無需對方發接收者進行多態選擇。Java語言規范中明確說明了final方法是一種非虛方法。
解析調用一定是個靜態過程,在編譯期間就完全確定,在類加載的解析階段就會把涉及的符號引用轉化為可確定的直接引用,不會延遲到運行期再去完成。而分派調用則可能是靜態的也可能是動態的,根據分派依據的宗量數(方法的調用者和方法的參數統稱為方法的宗量)又可分為單分派和多分派。兩類分派方式兩兩組合便構成了靜態單分派、靜態多分派、動態單分派、動態多分派四種分派情況。

靜態分派
所有依賴靜態類型來定位方法執行版本的分派動作,都稱為靜態分派,靜態分派的最典型應用就是多態性中的方法重載。靜態分派發生在編譯階段,因此確定靜態分配的動作實際上不是由虛擬機來執行的。下面通過一段方法重載的示例程序來更清晰地說明這種分派機制:

class Human{  
}    
class Man extends Human{  
}  
class Woman extends Human{  
}  
  
public class StaticPai{  
  
    public void say(Human hum){  
        System.out.println("I am human");  
    }  
    public void say(Man hum){  
        System.out.println("I am man");  
    }  
    public void say(Woman hum){  
        System.out.println("I am woman");  
    }  
  
    public static void main(String[] args){  
        Human man = new Man();  
        Human woman = new Woman();  
        StaticPai sp = new StaticPai();  
        sp.say(man);  
        sp.say(woman);  
    }  
}  

上面代碼的執行結果如下:
I am human I am human
以上結果的得出應該不難分析。在分析為什么會選擇參數類型為Human的重載方法去執行之前,先看如下代碼:

Human man = new Man();

我們把上面代碼中的“Human”稱為變量的靜態類型,后面的“Man”稱為變量的實際類型。靜態類型和實際類型在程序中都可以發生一些變化,區別是靜態類型的變化僅僅在使用時發生,變量本身的靜態類型不會被改變,并且最終的靜態類型是在編譯期可知的,而實際類型變化的結果在運行期才可確定。
回到上面的代碼分析中,在調用say()方法時,方法的調用者(回憶上面關于宗量的定義,方法的調用者屬于宗量)都為sp的前提下,使用哪個重載版本,完全取決于傳入參數的數量和數據類型(方法的參數也是數據宗量)。代碼中刻意定義了兩個靜態類型相同、實際類型不同的變量,可見編譯器(不是虛擬機,因為如果是根據靜態類型做出的判斷,那么在編譯期就確定了)在重載時是通過參數的靜態類型而不是實際類型作為判定依據的。并且靜態類型是編譯期可知的,所以在編譯階段,Javac編譯器就根據參數的靜態類型決定使用哪個重載版本。這就是靜態分派最典型的應用。

動態分派
動態分派與多態性的另一個重要體現——方法覆寫有著很緊密的關系。向上轉型后調用子類覆寫的方法便是一個很好地說明動態分派的例子。這種情況很常見,因此這里不再用示例程序進行分析。很顯然,在判斷執行父類中的方法還是子類中覆蓋的方法時,如果用靜態類型來判斷,那么無論怎么進行向上轉型,都只會調用父類中的方法,但實際情況是,根據對父類實例化的子類的不同,調用的是不同子類中覆寫的方法,很明顯,這里是要根據變量的實際類型來分派方法的執行版本的。而實際類型的確定需要在程序運行時才能確定下來,這種在運行期根據實際類型確定方法執行版本的分派過程稱為動態分派。

單分派和多分派
前面給出:方法的接受者(亦即方法的調用者)與方法的參數統稱為方法的宗量。但分派是根據一個宗量對目標方法進行選擇,多分派是根據多于一個宗量對目標方法進行選擇。
為了方便理解,下面給出一段示例代碼:

class Eat{  
}  
class Drink{  
}  
  
class Father{  
    public void doSomething(Eat arg){  
        System.out.println("爸爸在吃飯");  
    }  
    public void doSomething(Drink arg){  
        System.out.println("爸爸在喝水");  
    }  
}  
  
class Child extends Father{  
    public void doSomething(Eat arg){  
        System.out.println("兒子在吃飯");  
    }  
    public void doSomething(Drink arg){  
        System.out.println("兒子在喝水");  
    }  
}  
  
public class SingleDoublePai{  
    public static void main(String[] args){  
        Father father = new Father();  
        Father child = new Child();  
        father.doSomething(new Eat());  
        child.doSomething(new Drink());  
    }  
}  

運行結果應該很容易預測到,如下:
爸爸在吃飯
兒子在喝水
我們首先來看編譯階段編譯器的選擇過程,即靜態分派過程。這時候選擇目標方法的依據有兩點:一是方法的接受者(即調用者)的靜態類型是Father還是Child,二是方法參數類型是Eat還是Drink。因為是根據兩個宗量進行選擇,所以Java語言的靜態分派屬于多分派類型。
再來看運行階段虛擬機的選擇,即動態分派過程。由于編譯期已經了確定了目標方法的參數類型(編譯期根據參數的靜態類型進行靜態分派),因此唯一可以影響到虛擬機選擇的因素只有此方法的接受者的實際類型是Father還是Child。因為只有一個宗量作為選擇依據,所以Java語言的動態分派屬于單分派類型。

image.png

根據以上論證,我們可以總結如下:目前的Java語言(JDK1.6)是一門靜態多分派、動態單分派的語言。

  • 單例模式的優缺點
    1、優點
    提供了對唯一實例的受控訪問。因為單例類封裝了它的唯一實例,所以它可以嚴格控制客戶怎樣以及何時訪問它,并為設計及開發團隊提供了共享的概念。
    由于在系統內存中只存在一個對象,因此可以節約系統資源,對于一些需要頻繁創建和銷毀的對象,單例模式無疑可以提高系統的性能。
    允許可變數目的實例。我們可以基于單例模式進行擴展,使用與單例控制相似的方法來獲得指定個數的對象實例。

2、缺點
由于單例模式中沒有抽象層,因此單例類的擴展有很大的困難。
單例類的職責過重,在一定程度上違背了“單一職責原則”。因為單例類既充當了工廠角色,提供了工廠方法,同時又充當了產品角色,包含一些業務方法,將產品的創建和產品的本身的功能融合到一起。
濫用單例將帶來一些負面問題,如為了節省資源將數據庫連接池對象設計為單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;現在很多面向對象語言(如Java、C#)的運行環境都提供了自動垃圾回收的技術,因此,如果實例化的對象長時間不被利用,系統會認為它是垃圾,會自動銷毀并回收資源,下次利用時又將重新實例化,這將導致對象狀態的丟失。

Android篇

  • Service和IntentService的區別
    Android中的Service是用于后臺服務的,當應用程序被掛到后臺的時候,問了保證應用某些組件仍然可以工作而引入了Service這個概念,那么這里面要強調的是Service不是獨立的進程,也不是獨立的線程,它是依賴于應用程序的主線程的,也就是說,在更多時候不建議在Service中編寫耗時的邏輯和操作,否則會引起ANR。
    那么我們當我們編寫的耗時邏輯,不得不被service來管理的時候,就需要引入IntentService,IntentService是繼承Service的,那么它包含了Service的全部特性,當然也包含service的生命周期,那么與service不同的是,IntentService在執行onCreate操作的時候,內部開了一個線程,去你執行你的耗時操作。
    這里我 需要解釋以下幾個方法,也許大家都已經很清楚了,不過為了拋磚引玉,我還是要提一嘴。
    Service中提供了一個方法:
public int onStartCommand(Intent intent, int flags, int startId) {  
     onStart(intent, startId);  
     return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;  
 }  

這個方法的具體含義是,當你的需要這個service啟動的時候,或者調用這個servcie的時候,那么這個方法首先是要被回調的。
同時IntentService中提供了這么一個方法:

protected abstract void onHandleIntent(Intent intent);  

這是一個抽象方法,也就是說具體的實現需要被延伸到子類。
子類的聲明:

public class ChargeService extends IntentService   

上面提到過IntentService是繼承Service的,那么這個子類也肯定繼承service,那么onHandleIntent()方法是什么時候被調用的呢?讓我們具體看IntentService的內部實現:

private final class ServiceHandler extends Handler {  
    public ServiceHandler(Looper looper) {  
        super(looper);  
    }  
  
    @Override  
    public void handleMessage(Message msg) {  
        onHandleIntent((Intent)msg.obj);  
        stopSelf(msg.arg1);  
    }  
}  
  
/** 
 * Creates an IntentService.  Invoked by your subclass's constructor. 
 * 
 * @param name Used to name the worker thread, important only for debugging. 
 */  
public IntentService(String name) {  
    super();  
    mName = name;  
}  
  
/** 
 * Sets intent redelivery preferences.  Usually called from the constructor 
 * with your preferred semantics. 
 * 
 * <p>If enabled is true, 
 * {@link #onStartCommand(Intent, int, int)} will return 
 * {@link Service#START_REDELIVER_INTENT}, so if this process dies before 
 * {@link #onHandleIntent(Intent)} returns, the process will be restarted 
 * and the intent redelivered.  If multiple Intents have been sent, only 
 * the most recent one is guaranteed to be redelivered. 
 * 
 * <p>If enabled is false (the default), 
 * {@link #onStartCommand(Intent, int, int)} will return 
 * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent 
 * dies along with it. 
 */  
public void setIntentRedelivery(boolean enabled) {  
    mRedelivery = enabled;  
}  
  
@Override  
public void onCreate() {  
    // TODO: It would be nice to have an option to hold a partial wakelock  
    // during processing, and to have a static startService(Context, Intent)  
    // method that would launch the service & hand off a wakelock.  
  
    super.onCreate();  
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");  
    thread.start();  
  
    mServiceLooper = thread.getLooper();  
    mServiceHandler = new ServiceHandler(mServiceLooper);  
}  
  
@Override  
public void onStart(Intent intent, int startId) {  
    Message msg = mServiceHandler.obtainMessage();  
    msg.arg1 = startId;  
    msg.obj = intent;  
    mServiceHandler.sendMessage(msg);  
}  

在這里我們可以清楚的看到其實IntentService在執行onCreate的方法的時候,其實開了一個線程HandlerThread,并獲得了當前線程隊列管理的looper,并且在onStart的時候,把消息置入了消息隊列,

@Override  
       public void handleMessage(Message msg) {  
           onHandleIntent((Intent)msg.obj);  
           stopSelf(msg.arg1);  
       }  

在消息被handler接受并且回調的時候,執行了onHandlerIntent方法,該方法的實現是子類去做的。
結論:
IntentService是通過Handler looper message的方式實現了一個多線程的操作,同時耗時操作也可以被這個線程管理和執行,同時不會產生ANR的情況。

  • 自定義View的幾種方式
      總結來說,自定義控件的實現有三種方式,分別是:組合控件、自繪控件和繼承控件。下面將分別對這三種方式進行介紹。
    (一)組合控件
      組合控件,顧名思義就是將一些小的控件組合起來形成一個新的控件,這些小的控件多是系統自帶的控件。比如很多應用中普遍使用的標題欄控件,其實用的就是組合控件,那么下面將通過實現一個簡單的標題欄自定義控件來說說組合控件的用法。
    1、新建一個Android項目,創建自定義標題欄的布局文件title_bar.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#0000ff" >

    <Button
        android:id="@+id/left_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_margin="5dp"
        android:background="@drawable/back1_64" />

    <TextView
        android:id="@+id/title_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="這是標題"
        android:textColor="#ffffff"
        android:textSize="20sp" />

</RelativeLayout>

可見這個標題欄控件還是比較簡單的,其中在左邊有一個返回按鈕,背景是一張事先準備好的圖片back1_64.png,標題欄中間是標題文字。
2、創建一個類TitleView,繼承自RelativeLayout:

public class TitleView extends RelativeLayout {

    // 返回按鈕控件
    private Button mLeftBtn;
    // 標題Tv
    private TextView mTitleTv;

    public TitleView(Context context, AttributeSet attrs) {
        super(context, attrs);

        // 加載布局
        LayoutInflater.from(context).inflate(R.layout.title_bar, this);

        // 獲取控件
        mLeftBtn = (Button) findViewById(R.id.left_btn);
        mTitleTv = (TextView) findViewById(R.id.title_tv);

    }

    // 為左側返回按鈕添加自定義點擊事件
    public void setLeftButtonListener(OnClickListener listener) {
        mLeftBtn.setOnClickListener(listener);
    }

    // 設置標題的方法
    public void setTitleText(String title) {
        mTitleTv.setText(title);
    }
}

在TitleView中主要是為自定義的標題欄加載了布局,為返回按鈕添加事件監聽方法,并提供了設置標題文本的方法。
3、在activity_main.xml中引入自定義的標題欄:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <com.example.test.TitleView
        android:id="@+id/title_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
    </com.example.test.TitleView>

</LinearLayout>

4、在MainActivity中獲取自定義的標題欄,并且為返回按鈕添加自定義點擊事件:

private TitleView mTitleBar;
     mTitleBar = (TitleView) findViewById(R.id.title_bar);

        mTitleBar.setLeftButtonListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "點擊了返回按鈕", Toast.LENGTH_SHORT)
                        .show();
                finish();
            }
        });

5、運行效果如下:


  
  這樣就用組合的方式實現了自定義標題欄,其實經過更多的組合還可以創建出功能更為復雜的自定義控件,比如自定義搜索欄等。

(二)自繪控件
  自繪控件的內容都是自己繪制出來的,在View的onDraw方法中完成繪制。下面就實現一個簡單的計數器,每點擊它一次,計數值就加1并顯示出來。
1、創建CounterView類,繼承自View,實現OnClickListener接口:

public class CounterView extends View implements OnClickListener {

    // 定義畫筆
    private Paint mPaint;
    // 用于獲取文字的寬和高
    private Rect mBounds;
    // 計數值,每點擊一次本控件,其值增加1
    private int mCount;

    public CounterView(Context context, AttributeSet attrs) {
        super(context, attrs);

        // 初始化畫筆、Rect
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBounds = new Rect();
        // 本控件的點擊事件
        setOnClickListener(this);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mPaint.setColor(Color.BLUE);
        // 繪制一個填充色為藍色的矩形
        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);

        mPaint.setColor(Color.YELLOW);
        mPaint.setTextSize(50);
        String text = String.valueOf(mCount);
        // 獲取文字的寬和高
        mPaint.getTextBounds(text, 0, text.length(), mBounds);
        float textWidth = mBounds.width();
        float textHeight = mBounds.height();

        // 繪制字符串
        canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2
                + textHeight / 2, mPaint);
    }

    @Override
    public void onClick(View v) {
        mCount ++;
        
        // 重繪
        invalidate();
    }

}

2、在activity_main.xml中引入該自定義布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <com.example.test.CounterView
        android:id="@+id/counter_view"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="center_horizontal|top"
        android:layout_margin="20dp" />

</LinearLayout>

3、運行效果如下:


(三)繼承控件
  就是繼承已有的控件,創建新控件,保留繼承的父控件的特性,并且還可以引入新特性。下面就以支持橫向滑動刪除列表項的自定義ListView的實現來介紹。
1、創建刪除按鈕布局delete_btn.xml,這個布局是在橫向滑動列表項后顯示的:

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="#FF0000"
    android:padding="5dp"
    android:text="刪除"
    android:textColor="#FFFFFF"
    android:textSize="16sp" >

</Button>

2、創建CustomListView類,繼承自ListView,并實現了OnTouchListener和OnGestureListener接口:

public class CustomListView extends ListView implements OnTouchListener,
        OnGestureListener {

    // 手勢動作探測器
    private GestureDetector mGestureDetector;

    // 刪除事件監聽器
    public interface OnDeleteListener {
        void onDelete(int index);
    }

    private OnDeleteListener mOnDeleteListener;

    // 刪除按鈕
    private View mDeleteBtn;

    // 列表項布局
    private ViewGroup mItemLayout;

    // 選擇的列表項
    private int mSelectedItem;

    // 當前刪除按鈕是否顯示出來了
    private boolean isDeleteShown;

    public CustomListView(Context context, AttributeSet attrs) {
        super(context, attrs);

        // 創建手勢監聽器對象
        mGestureDetector = new GestureDetector(getContext(), this);

        // 監聽onTouch事件
        setOnTouchListener(this);
    }

    // 設置刪除監聽事件
    public void setOnDeleteListener(OnDeleteListener listener) {
        mOnDeleteListener = listener;
    }

    // 觸摸監聽事件
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (isDeleteShown) {
            hideDelete();
            return false;
        } else {
            return mGestureDetector.onTouchEvent(event);
        }
    }

    @Override
    public boolean onDown(MotionEvent e) {
        if (!isDeleteShown) {
            mSelectedItem = pointToPosition((int) e.getX(), (int) e.getY());
        }
        return false;
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
            float velocityY) {
        // 如果當前刪除按鈕沒有顯示出來,并且x方向滑動的速度大于y方向的滑動速度
        if (!isDeleteShown && Math.abs(velocityX) > Math.abs(velocityY)) {
            mDeleteBtn = LayoutInflater.from(getContext()).inflate(
                    R.layout.delete_btn, null);

            mDeleteBtn.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    mItemLayout.removeView(mDeleteBtn);
                    mDeleteBtn = null;
                    isDeleteShown = false;
                    mOnDeleteListener.onDelete(mSelectedItem);
                }
            });

            mItemLayout = (ViewGroup) getChildAt(mSelectedItem
                    - getFirstVisiblePosition());

            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
                    LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
            params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
            params.addRule(RelativeLayout.CENTER_VERTICAL);

            mItemLayout.addView(mDeleteBtn, params);
            isDeleteShown = true;
        }

        return false;
    }

    // 隱藏刪除按鈕
    public void hideDelete() {
        mItemLayout.removeView(mDeleteBtn);
        mDeleteBtn = null;
        isDeleteShown = false;
    }

    public boolean isDeleteShown() {
        return isDeleteShown;
    }
    
    /**
     * 后面幾個方法本例中沒有用到
     */
    @Override
    public void onShowPress(MotionEvent e) {

    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
            float distanceY) {
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {

    }

}

3、定義列表項布局custom_listview_item.xml,它的結構很簡單,只包含了一個TextView:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:descendantFocusability="blocksDescendants" >

    <TextView
        android:id="@+id/content_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_margin="30dp"
        android:gravity="center_vertical|left" />

</RelativeLayout>

4、定義適配器類CustomListViewAdapter,繼承自ArrayAdapter<String>:

public class CustomListViewAdapter extends ArrayAdapter<String> {

    public CustomListViewAdapter(Context context, int textViewResourceId,
            List<String> objects) {
        super(context, textViewResourceId, objects);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view;

        if (convertView == null) {
            view = LayoutInflater.from(getContext()).inflate(
                    R.layout.custom_listview_item, null);
        } else {
            view = convertView;
        }

        TextView contentTv = (TextView) view.findViewById(R.id.content_tv);
        contentTv.setText(getItem(position));

        return view;
    }

}

5、在activity_main.xml中引入自定義的ListView:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <com.example.test.CustomListView
        android:id="@+id/custom_lv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

6、在MainActivity中對列表做初始化、設置列表項刪除按鈕點擊事件等處理:

public class MainActivity extends Activity {

    // 自定義Lv
    private CustomListView mCustomLv;
    // 自定義適配器
    private CustomListViewAdapter mAdapter;
    // 內容列表
    private List<String> contentList = new ArrayList<String>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        initContentList();

        mCustomLv = (CustomListView) findViewById(R.id.custom_lv);
        mCustomLv.setOnDeleteListener(new OnDeleteListener() {

            @Override
            public void onDelete(int index) {
                contentList.remove(index);
                mAdapter.notifyDataSetChanged();
            }
        });

        mAdapter = new CustomListViewAdapter(this, 0, contentList);
        mCustomLv.setAdapter(mAdapter);
    }

    // 初始化內容列表
    private void initContentList() {
        for (int i = 0; i < 20; i++) {
            contentList.add("內容項" + i);
        }
    }

    @Override
    public void onBackPressed() {
        if (mCustomLv.isDeleteShown()) {
            mCustomLv.hideDelete();
            return;
        }
        super.onBackPressed();
    }

}

7、運行效果如下:


  • 為啥要序列化
    序列化的原因基本三種情況:
    1.永久性保存對象,保存對象的字節序列到本地文件中;
    2.對象在網絡中傳遞;
    3.對象在IPC間傳遞。

  • 觸摸事件傳遞流程
    Touch事件的一系列傳遞流程都是dispatchTouchEvent()來控制的,事件將由上而下依次傳遞。在向下傳遞的過程中onInterceptTouchEvent()
    返回true,即事件被攔截了,事件則停止向下傳遞,由當前View的onTouchEvent()處理。傳遞到最底層的View,就由他的onTouchEvent()處理,
    若處理成功則返回true,處理失敗則返回false,事件依次向上傳遞,每個View都調用自己的onTouchEvent()來處理。
    事件的傳向性具有當前系列事件的“記憶”功能,即當事件向下傳時其下級View處理不成功,當前系列內其將不再向下傳遞。

  • 如何設計緩存模塊
    內存層:(手機內存)
    內存緩存相對于磁盤緩存而言,速度要來的快很多,但缺點容量較小且會被系統回收,這里的實現用到了LruCache。
    LruCache這個類是Android3.1版本中提供的,如果你是在更早的Android版本中開發,則需要導入android-support-v4的jar包。
    磁盤層:(SD卡)
    相比內存緩存而言速度要來得慢很多,但容量很大,用到了DiskLruCache類。
    DiskLruCache是非Google官方編寫,但獲得官方認證的硬盤緩存類,該類沒有限定在Android內,所以理論上java應用也可以使用DiskLreCache來緩存。
    網絡層:(移動網絡,無線網絡)
    這里的網絡訪問實現用到了開源框架Volley。
    開源框架Volley是2013年Google大會發布的,Volley是Android平臺上的網絡通信庫,能使網絡通信更快,更簡單,更健壯。它的設計目標就是非常適合去進行數據量不大,但通信頻繁的網絡操作,而對于大數據量的網絡操作,比如說下載文件等,Volley的表現就會非常糟糕。

  • 如何做持久化
    一、SharedPreferences
    二、內部存儲(存儲到手機內部存儲空間)存儲目錄為data/data/package/files/
    三、sdcard存儲
    四、SQLite
    五、網絡存儲

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,283評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,947評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,094評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,485評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,268評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,817評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,906評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,039評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,551評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,502評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,662評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,188評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,907評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,304評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,563評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,255評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,637評論 2 370

推薦閱讀更多精彩內容

  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區別 13、...
    Miley_MOJIE閱讀 3,718評論 0 11
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,311評論 11 349
  • Java SE 基礎: 封裝、繼承、多態 封裝: 概念:就是把對象的屬性和操作(或服務)結合為一個獨立的整體,并盡...
    Jayden_Cao閱讀 2,121評論 0 8
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,704評論 18 399
  • 難道真是心有靈犀,不謀而合嗎? 昨晚輾轉難眠,因為自己的決斷失誤,造成的錯失良機,正悔恨交加,扼腕嘆息。 今天的晨...
    小小火紅閱讀 178評論 1 7