數據結構
?????? Java中的數組有差不多一樣的語法。只是java中處理8種基本類型,數組也是作為對象處理的,所以創建對象時也需要使用new關鍵字。和大多數編程語言一樣,數組一旦創建,大小便不可變。
Java中有一個Arrays類,專門用來操作array。
Arrays中擁有一組static函數,
-equals():比較兩個array是否相等。array擁有相同元素個數,且所有對應元素兩兩相等。
-fill():將值填入array中。
-sort():用來對array中尋找元素。
-System.arraycopy():array的復制。
?int[] intArr = new int[10];
Array時Java中隨機訪問一連串對象最有效率的數據結構,但很不靈活,大小固定,且不知道里面有多少元素。為此JDK已經為我們提供了一系列相應的類來實現功能強大且更靈活的基本數據結構。這些類均在java.util包中。其繼承結構如下:
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
│? ? └SortedSet
└Queue
-Map
├HashTable
├HashMap
└WeakHashMap
List
List是一個接口,不能實例化,需要實例化一個ArrayList或者LinkedList。
ArrayList里面的內部實現,是通過一定的增長規則動態復制增加數組長度來實現動態增加元素的。如果在大數據量的情況下,在某一個位置隨機插入或者刪除元素,就會產生性能問題。LinkedList可以解決這類問題,但LinkedList在通過下標取元素的時候,需要遍歷整個鏈表節點匹配,數據量大的情況下,效率不高。Vector是一種老的動態數組,是線程同步的,效率很低,一般不贊成使用。Stack是Java實現了一個堆棧,先進后出結構。
遍歷時刪除問題
使用增強for循環遍歷List(所有實現子類ArrayList,Stack等)元素對其中的元素進行刪除,會拋出java.util.ConcurrentModificationException的異常。若使用下面這種方式:
for(int i = 0; i < list.size(); i++) {????
???? list.remove(i);
}
則會刪除下標為偶數的元素,因為每次刪除后,后面的元素的下標全部減1,相當于元素位置全部左移一位,再次刪除時,會跳過一個元素進行刪除。這是非常不好的。如果非要這樣刪除,可以倒著來:
for(int i = list.size() - 1; i >= 0; i--){
???? list.remove(i);
}
或者新建一個要刪除的List,最后一起刪除。list.removeAll(deleteList);
Set
Set接口繼承Collection接口,最大的特點是集合中的元素都是唯一的,沒有重復,它有兩個子類,HashSet和TreeSet。
HashSet
?? 1.不允許重復元素;
?? 2.不保證集合中元素的順序,哈希算法來的;
?? 3.允許包含值為null的元素,但最多只能有一個null元素;
TreeSet
?? 1.不允許出現重復元素;
?? 2.集合中元素的順序按照某種規則進行排序;
?? 3.不允許包含值為null的元素;
Map
Map接口,沒有繼承Collection接口,它是獨立的一個接口。它使用key-value的鍵值對存儲數據。常用兩個子類是HashMap和TreeMap。
-HashMap:Map基于散列表的實現。插入和查詢“鍵值對”的開銷是固定的。可以通過構造器設置容量capacity和負載因子load facor,以調整容器的性能。
-LinkedHashMap:類似于HashMap,但是迭代遍歷它時,取得“鍵值對”的順序是其插入的順序,或者是最近最少使用(LRU)的次序。只比HashMap慢一點。它使用鏈表維護內部次序。
-TreeMap:基于紅黑樹數據結構的實現。查看“鍵”或“鍵值對”時,它們會被排序(次序由Comparabel或Comparator決定)。TreeMap的特點在 于,你得到的結果是經過排序的。TreeMap是唯一的帶有subMap()方法的Map,它可以返回一個子樹。
-WeakHashMap:弱鍵(weak key)Map,Map中使用的對象也被允許釋放: 這是為解決特殊問題設計的。如果沒有map之外的引用指向某個“鍵”,則此“鍵”可以被垃圾收集器回收。
-IdentifyHashMap:使用==代替equals()對“鍵”作比較的hash map。專為解決特殊問題而設計。
線程安全
Vector是線程同步的,也就是線程安全的,對多線程的操作采用了synchronized處理,但因為效率低,已不建議使用。ArrayList和LinkedList都是線程不安全的,在多線程環境中,對數據的修改造成錯誤的結果。有兩種解決方案:
使用同步包裝器
List safedList = Collections.synchronizedList(new ArrayList());
Set safedSet = Collections.synchronizedSet(new HashSet());
Map safedMap = Collections.synchronizedMap(new HashMap());
查看其源碼,發現是Collections類給不安全的集合類包裝了一層,然后生成一個新的類,新類里面采用了synchronized對集合的操作進行了同步處理。
使用安全的集合類
Java5.0新加入的ConcurrentLinkedQueue、ConcurrentHashMap、CopyOnWriteArrayList和CopyOnWriteArraySet,這些集合類都是線程安全的。這些類在java.util.concurrent包下。
Android中的List、Map替代方案
SparseArray與SparseArrayCompat和LongSparseArray
這3個類中,前2個基本上是同一類,只不過第二個類有removeAt方法,第三個是Long類型的。
這3個類也是用來代替HashMap,只不過他們的鍵(key)的類型是整型Integer或者Long類型,在實際開發中,如月份縮寫的映射,或者進行文件緩存映射,ViewHolder都特別適用
AtomicFile首先不是用來代替File的,而是作為File的輔助類存在, AtomicFile的作用是實現事務性原子操作,即文件讀寫必須完整,適合多線程中的文件讀寫操作。
用來實現多線程中的文件讀寫的安全操作
算法
二分查找
對于有序數組,二分查找的效率在大數據量的情況下,效率明顯:
private static int find(int [] arr,int searchKey) {
??????????? int lowerBound = 0;
??????????? int upperBound = arr.length -1;
??????????? int curIn;
??????????? while(lowerBound <= upperBound) {
???????????????????? curIn = (lowerBound + upperBound) / 2;
???????????????????? if(arr[curIn] == searchKey) {
??????????????????????????? return curIn;
???????????????????? }else{
??????????????????????????? if(arr[curIn] < searchKey) {
???????????????????????????????????? lowerBound = curIn + 1;
??????????????????????????? }else {
???????????????????????????????????? upperBound = curIn - 1;
??????????????????????????? }
??????????????????? }
????????? }
????????????????? return -1;
}
使用遞歸的方式編寫,貌似看起來好理解點:
private static int recursiveFind(int[] arr,int start,int end,int searchKey) {
??????????? if (start <= end) {
??????????????? // 中間位置
??????????????? int middle = (start + end) / 2;
??????????????? if (searchKey == arr[middle]) {
??????????????????? // 等于中值直接返回
??????????????????? return middle;
???????????????? } else if (searchKey < arr[middle]) {
???????????????????? // 小于中值時在中值前面找
??????????????????? return recursiveFind(arr, start, middle - 1, searchKey);
??????????????? } else {
???????????????????? // 大于中值在中值后面找
???????????????????? return recursiveFind(arr, middle + 1, end, searchKey);
?????????????? }
??????? } else {
???????????? // 找不到
???????????? return -1;
?? }
}
排序
簡單排序
冒泡排序
對亂序的數組,很常見的排序方法是冒泡排序:
private static void bubbleSrot(int[] arr) {
??????????? for (int i = 0; i < arr.length - 1; i++) {
?????????????????? for (int j = i + 1; j < arr.length; j++) {
???????????????????????? if(arr[i] > arr[j]) {
????????????????????????????? int temp = arr[i];
????????????????????????????? arr[i] = arr[j];
????????????????????????????? arr[j] = temp;
????????????????????????? }
???????????????????? }
???????????? }
}
這種排序方法速度是很慢的,運行時間為O(N2)級。
選擇排序改進了冒泡排序,將必要的交換次數從O(N2)減少到O(N),不幸的是比較次數依然是O(N2)級。
然而,選擇排序依然為大記錄量的排序提出了一個非常重要的改進,因為這些大量的記錄需要在內存中移動,這就使交換的時間和比較的時間相比起來,交換的時間更為重要。(一般來說,Java語言中不是這種情況,Java中只是改變了引用位置,而實際對象的位置并沒有發生改變)
選擇排序
private static void chooseSort(int[] arr) {
??????????? for (int i = 0; i < arr.length; i++) {
????????????????? int least = i;
????????????????? for (int j = i + 1; j < arr.length; j++) {
??????????????????????? if (arr[j] < arr[least]) {
??????????????????????????? least = j;
???????????????????????? }
?????????????????? }
????????????? // 將當前第一個元素與它后面序列中的最小的一個 元素交換,也就是將最小的元素放在最前端
?????????????? int temp = arr[i];
?????????????? arr[i] = arr[least];
?????????????? arr[least] = temp;
????????? }
}
選擇排序的效率:選擇排序和冒泡排序執行了相同次數的比較:N*(N-1)/2。對于10個數據項,需要45次比較,然而,10個數據項只需要少于10次的交換。對于100個數據項,需要4950次比較,但只進行不到100次交換。N值很大時,比較的次數是主要的,所以結論是選擇排序和冒泡排序一樣運行了O(N2)時間。但是,選擇排序無疑更快,因為它進行的交換少得多。
插入排序
插入排序,在一般情況下,比冒泡排序快一倍,比選擇排序快一點。
private static void insertionSort(int[] arr) {
??????????? int in = 0;
??????????? int out = 0;
??????????? for(out = 1 ; out < arr.length ; out ++) {
???????????????? int temp = arr[out];
???????????????? in = out;
???????????????? while(in > 0 && arr[in-1] >= temp) {
????????????????????????? arr[in] = arr[in - 1];
????????????????????????? --in;
???????????????? }
???????????????? arr[in] = temp;
???????????? }
}
在外層的for循環中,out變量從1開始,向右移動。它標記了未排序部分的最左端數據。而在內層的while循環中,in變量從out變量開始,向左移動,直到temp變量小于in所指的數組數據項,或者它已經不能再向左移動為止。while循環的每一趟都向左移動了一個已排序的數據項。
插入排序的效率:這個算法中,第一趟排序,最多比較一次,第二趟排序,最多比較兩次,以此類推,最后一趟最多比較N-1次,因此有1+2+3+…+N-1 = N*(N-1)/2。然而,因為在每一趟排序發現插入點之前,平均只有全體數據項的一半真的進行了比較,所以除以2最后是N*(N-1)/4。
對于隨機順序的數據,插入排序也需要O(N2)的時間級。當數據基本有序,插入排序幾乎只需要O(N)的時間,這對把一個基本有序的文件進行排序是一個簡單而有效的方法。
對于逆序排列的數據,每次比較和移動都會執行,所以插入排序不比冒泡排序快。
歸并排序
歸并排序比簡單排序要有效的多,至少在速度上是這樣的。冒泡排序、選擇排序、插入排序要用O(N2)的時間,而歸并排序只需要O(N*logN)的時間。
歸并排序的一個缺點是它需要在存儲器中有另一個大小等于被排序的數據項數目的數組。如果初始數組幾乎占滿整個存儲器,那么歸并排序將不能工作。但是,如果有足夠的空間,歸并排序會是一個很好的選擇。
原理是合并兩個已排序的數組到一個數組:
//將兩個已排序的數組合并到第三個數組上。
private static void merge(int[] arrA, int[] arrB, int[] arrC) {
??????????? int aDex = 0;
??????????? int bDex = 0;
??????????? int cDex = 0;
??????????? int sizeA = arrA.length;
??????????? int sizeB = arrB.length;
??????????? // A數組和B數組都不為空
??????????? while (aDex < sizeA && bDex < sizeB) {
????????????????????? if (arrA[aDex] < arrB[bDex]) {
????????????????????????? arrC[cDex++] = arrA[aDex++];
?????????????????????? } else {
????????????????????????? arrC[cDex++] = arrB[bDex++];
?????????????????????? }
????????????? }
????????????? //A數組不為空,B數組為空
????????????? while (aDex < sizeA) {
??????????????????????? arrC[cDex++] = arrA[aDex++];
????????????? }
???????????? //A數組為空,B數組不為空
???????????? while (bDex < sizeB) {
?????????????????????? arrC[cDex++] = arrB[bDex++];
???????????? }
}
于是,詳細的完整實現如下:
static class DArray{
???????? private int [] theArray;
???????? public DArray(int[] theArray) {
??????????????????? this.theArray = theArray;
???????? }
???????? //執行歸并排序
???????? public void mergeSort() {
??????? ? ? ? ? ?? //復制一份出來
?????????????????? int [] workSpace = new int [theArray.length];
?????????????????? reMergeSort(workSpace, 0, theArray.length-1);
??????? }
??????? private void reMergeSort(int [] workSpace,int lowerBound,int upperBound) {
??????????????????? if(lowerBound == upperBound){
?????????????????????? return;
??????????????????? }else{
?????????????????????? int mid = (lowerBound + upperBound) / 2;
?????????????????????? reMergeSort(workSpace, lowerBound, mid);
?????????????????????? reMergeSort(workSpace, mid + 1, upperBound);
?????????????????????? merge(workSpace, lowerBound, mid + 1,upperBound);
??????????????????? }
????????? }
????????? private void merge(int [] workSpace,int lowPtr,int highPtr,int upperBound){
????????????????????? int j= 0; //workSpace's index
????????????????????? int lowerBound = lowPtr;
????????????????????? int mid = highPtr -1;
????????????????????? int n = upperBound - lowerBound + 1;
????????????????????? while(lowPtr <= mid && highPtr <= upperBound) {
??????????????????????????????? if(theArray[lowPtr] < theArray[highPtr]) {
?????????????????????????????????? workSpace[j++] = theArray[lowPtr++];
??????????????????????????????? }else{
?????????????????????????????????? workSpace[j++] = theArray[highPtr++];
??????????????????????????????? }
?????????????????????? } while(lowPtr <= mid) {
????????????????????????????????? workSpace[j++] = theArray[lowPtr++];
?????????????????????? } while(highPtr <= upperBound) {
?????????????????????????????????? workSpace[j++] = theArray[highPtr++];
??????????????????????? }
?????????????????????? for(j = 0;j < n ;j++) {
??????????????????????????? theArray[lowerBound+j] = workSpace[j];
?????????????????????? }
????????? }
}
高級排序
有2個高級的排序算法,希爾排序和快速排序。這兩種排序算法都比簡單排序算法快得多:希爾排序大約需要O(N*(logN)2)時間,快速排序需要O(N*logN)時間。這兩種排序算法都和歸并排序不同,不需要大量的輔助存儲空間。希爾排序幾乎和歸并排序一樣容易實現,而快速排序是所有通用排序算法中最快的一種排序算法。
還有一種基數排序,是一種不常用但很有趣的排序算法。
希爾排序
希爾排序是基于插入排序的。
private static void shellSort(int[] arr) {
??????????? int inner, outer;
??????????? int temp;
??????????? int h = 1;
??????????? int nElem = arr.length;
??????????? while (h <= nElem / 3) {
????????????????????? h = h * 3 + 1;
??????????? }
??????????? while (h > 0) {
????????????????????? for (outer = h; outer < nElem; outer++) {
??????????????????????????? temp = arr[outer];
??????????????????????????? inner = outer;
??????????????????????????? while (inner > h - 1 && arr[inner - h] >= temp) {
????????????????????????????????????? arr[inner] = arr[inner - h];
????????????????????????????????????? inner -= h;
???????????????????????????? }
???????????????????????????? arr[inner] = temp;
?????????????????????? }
?????????????????????? h = (h - 1) / 3;
??????????? }
}
快速排序
快速排序是最流行的排序算法,在大多數情況下,快速排序都是最快的,執行時間是O(N*logN)級。
劃分
劃分是快速排序的根本機制。劃分本身也是一個有用的操作。
劃分數據就是把數據分為兩組,使所有關鍵字大于特定值的數據項在一組,所有關鍵字小于特定值的數據項在另一組。
private static int partitionIt(int[] arr ,int left,int right,int pivot){
??????????? int leftPtr = left - 1;
??????????? int rightPtr = right + 1;
??????????? while(true) {
??????????????????? while(leftPtr < right && arr[++leftPtr] < pivot);
??????????????????? while(rightPtr > 0 && arr[--rightPtr] > pivot);
??????????????????? if(leftPtr >= rightPtr) {
?????????????????????? break;
????????????????? ? }else{
?????????????????????? //交換leftPtr和rightPtr位置的元素
????????????????????? int temp = arr[leftPtr];
????????????????????? arr[leftPtr] = arr[rightPtr];
????????????????????? arr[rightPtr] = temp;
?????????????? ? ? }
???????? }
??????? return leftPtr;//返回樞紐位置
}
快速排序(應用)
private static void recQuickSort(int arr [] ,int left,int right) {
??????????? if(right - left <= 0){
??????????????????? return;
??????????? }else{
??????????????????? int pivot = arr[right];//一般使用數組最右邊的元素作為樞紐
??????????????????? int partition = partitionIt(arr, left, right, pivot);
??????????????????? recQuickSort(arr, left, partition-1);
??????????????????? recQuickSort(arr, partition+1, right);
??????????? }
}
//劃分
private static int partitionIt(int[] arr ,int left,int right,int pivot) {
??????????? int leftPtr = left - 1;
?????????? //int rightPtr = right + 1;
??????????? int rightPtr = right ; //使用最右邊的元素作為樞紐,劃分時就要將最右端的數據項排除在外
??????????? while(true) {
??????????????????? while(arr[++leftPtr] < pivot);
??????????????????? while(rightPtr > 0 && arr[--rightPtr] > pivot);
??????????????????? if(leftPtr >= rightPtr) {
??????????????????????????? break;
??????????????????? }else {
??????????????????????????? //交換leftPtr和rightPtr位置的元素
??????????????????????????? int temp = arr[leftPtr];
??????????????????????????? arr[leftPtr] = arr[rightPtr];
??????????????????????????? arr[rightPtr] = temp;
??????????????????? }
?????????? }
?????????? //交換leftPtr和right位置的元素
?????????? int temp = arr[leftPtr];
?????????? arr[leftPtr] = arr[right];
?????????? arr[right] = temp;
?????????? return leftPtr;//返回樞紐位置
}
最后測試,10萬條隨機數據,排序完成耗時18~25ms。希爾排序耗時差不多,而簡單排序中的插入排序和選擇排序耗時3500ms以上,冒泡排序最慢,超過17000ms以上才完成;歸并排序比希爾排序和快速排序稍微慢點,在30ms左右。