一、集合和數組概念
在Java中,數組是存儲某一類型的數據的容器,但是這個容器有侷限性,因爲定義後的數組長度不可變,超出長度後,再存放數據就會報錯(例如:數組下標越界)。而且數組只能存放基本數據類型和對象
在開發過程中,大多時候數據長度是不確定的,這就需要有不定長的容器來存放數據,這就涉及到集合Collection,Java 中的集合Collection都採用了泛型實現,可以存入任何類型的對象數據,集合彌補了數組的缺點,比數組更靈活更實用,而且不同的集合框架類可適用不同場合。它以類的形式存在,具有封裝、繼承、多態等類的特性,通過簡單的方法和屬性即可實現各種複雜操作,從而大大提高了軟件的開發效率
二、集合分類
Java的集合類主要由兩個接口派生而出: Collection和Map, Collection和Map是Java 集合框架的根接口,這兩個接口又包含了一些子接口或實現類。
Java集合框架是一個用來代表和操縱集合的統一架構,包含如下內容:
接口:代表集合的抽象數據類型。例如: Collection、List、Set、Map 等。定義多個接口,便於以不同的方式操作集合對象
實現(類):接口的具體實現。例如:ArrayList、LinkedList、HashSet、HashMap。
算法:實現接口的對象裏的方法中執行的計算,例如:Collections 工具類提供了對集合進行排序,遍歷等多種算法實現。
Collection接口下有三個子接口,List,Queue,Set及最常用的三個類,ArrayList,LinkedList,HashSet。
Map接口下最常用的是HashMap。
1、Collection接口
Collection 是最基本的集合接口,是單列集合的頂層父類,一個獨立元素的序列。一個 Collection 代表一組 Object,即 Collection 的元素, Java不提供直接繼承自Collection的類,只提供繼承於的子接口(如List和set)。Collection 接口存儲一組不唯一,無序的對象。
1)List集合是一個有序的隊列,使用此接口能夠精確的控制每個元素插入的位置,能夠通過索引(元素在List中位置,類似於數組的下標)來訪問List中的元素,第一個元素的索引爲 0,允許有相同的元素。List 接口存儲一組不唯一,有序(插入順序)的對象。
List的實現類有LinkedList、ArrayList、Vector、Stack。
2)Set集合是一個不允許有重複元素的集合。存儲一組唯一,無序的對象。
Set的實現類有HashSet、TreeSet。
HashSet依賴於HashMap,他實際上是通過HashMap實現的;
TreeSet依賴 於TreeMap,他實際是通過TreeMap實現的。
2、Map接口
是雙列集合的頂層父類接口,是一個映射接口,即key-Value鍵值對。允許使用鍵來查找對應的值,從某種意義上來說,他將數字和對象關聯在一起。Map中的每一個元素包含"key"和"key對應的value"。
1)Map.Entry:描述在一個Map中的一個元素(鍵/值對)。是一個Map的內部類。
Entry將鍵值對的對應關係封裝成了對象。
即鍵值對對象,在遍歷Map集合時,可以從每一個鍵值對(Entry)對象中獲取對應的鍵與對應的值。
相關方法
getKey()方法:獲取Entry對象中的鍵
getValue()方法:獲取Entry對象中的值
entrySet()方法:用於返回Map集合中所有的鍵值對(Entry)對象,以Set集合形式返回。
2)Map的實現類有:HashMap ,TreeMap。LinkedHashMap 繼承自 HashMap 。
3、迭代器Iterator
1)Iterator通常被稱爲輕量級對象,創建它的代價很小,它是遍歷集合的工具,通常通過Iterator迭代器來遍歷集合。Collection依賴於Iterator,Collection的實現類都要實現iterator()函數,返回一個Iterator對象。
三、List接口
- ArrayList介紹
ArrayList是一個數組隊列,也是一個動態數組,底層是Object數組,容量是自增長的,所以ArrayList具有數組的查詢速度快的優點以及增刪速度慢的缺點。它繼承與AbstractList。
ArrayList線程是非安全的,一般使用在單線程的情況下。要保證同步,可以使用:List list = Collections.synchronizedList(new ArrayList());
進行包裝,默認容量爲10.
ArrayList可以有null值
ArrayList集合特點:底層採用的是數組結構
ArrayList al=new ArrayList();//創建了一個長度爲0的Object類型數組
al.add("abc");//底層會創建一個長度爲10的Object數組 Object[] obj=new Object[10]
//obj[0]="abc"
//如果添加的元素的超過10個,底層會開闢一個1.5*10的長度的新數組
//把原數組中的元素拷貝到新數組,再把最後一個元素添加到新數組中
原數組:
a b c d e f g h k l
添加m:
a b c d e f g h k l m null null null null
常用方法:add
,addAll
,remove
,indexOf
,subList
,contains
,isEmpty
…
- LinkedList介紹
LinkedList 是一個繼承於AbstractSequentialList的雙向鏈表。實現了List、Deque、Cloneable、Java.io.serializable接口,它也可以被當作堆棧、隊列或雙端隊列進行操作,。
LinkedList 是非線程安全的,集合中的元素允許爲空,保存的元素爲有序的,實現了List接口,則允許集合中的元素是可以重複的。經常用在增刪操作較多而查詢操作很少的情況下。
1)LinkedList中定義了兩個屬性
size:LinkedList對象中存儲的元素個數。
header:LinkedList是基於鏈表實現的, header 就是鏈表的頭結點 ,Entry是節點對應的對象。
Entry類中只定義了存儲的元素、前一個元素、後一個元素,這就是雙向鏈表的節點的定義,每個節點只知道自己的前一個節點和後一個節點。
2)LinkedList元素存儲
LinkedList存儲元素的方法有:add(E e)、addBefore(E e,Entry entry)、 add(int index,E e)、addFirst(E e)、 addLast(E e)等。
addBefore(E e,Entry entry)是私有方法,無法在外部程序中調用(可以通過反射調用)。
addBefore(E e,Entry entry)先通過Entry的構造方法創建e的節點newEntry(包含了將其下一個節點設置爲entry,上一個節點設置爲entry.previous的操作,相當於修改newEntry的“指針”),之後修改插入位置後newEntry的前一節點的next引用和後一節點的previous引用,使鏈表節點間的引用關係保持正確。之後修改和size大小和記錄modCount,然後返回新插入的節點。
則有,addBefore(E e,Entry entry)實現在entry之前插入由e構造的新節點。
add(E element) 在header節點之前插入由element構造的新節點。
public void addFirst(E e) {
addBefore(e, header.next);
}
add(int index, E element) 調用了addBefore(E e,Entry entry)方法,只是entry節點由index的值決定
public void add(int index, E element) {
addBefore(element, (index==size ? header : entry(index)));
}
addFirst(E e) 實現在header元素的下一個元素之前插入。
addLast(E e) 實現在header節點前插入節點。
3)LinkedList元素讀取
LinkedList元素讀取的方法有:set(int index,E element) ,get(int Index) ,getFirst(),getLast()等。
set(int index,E element) 先獲取指定索引的節點,之後保留原來的元素,然後用element進行替換,之後返回原來的元素
get(int Index) 用於獲得指定索引位置的節點的元素。通過entry(int index)方法獲取節點。遍歷鏈表並獲取節點,entry的方法是根據索引值,進行遍歷。
getFirst() 返回鏈表的第一個節點的元素
getLast() 獲取header節點的前一個節點的元素
三、Set接口
- Set接口的特點
Set集合不包含重複元素,無索引
Set集合取出元素的方式可以採用:迭代器、增強for。
Set集合有多個子類,常用的有HashSet、LinkedHashSet。
Set接口的實現類,HashSet (哈希表):無序集合,存儲和取出的順序不同,沒有索引,不存儲重複元素
- HashSet介紹
底層數據結構是哈希表(實際上是一個 HashMap 實例),允許使用 null 元素,唯一且無序,非線程安全.通過元素的hashcode和equals來保證元素的唯一性。如果元素的hashcode值相同,纔會判斷equals是否爲true; 如果元素的hashcode的值不同,不會調用equals。
HashSet常用方法
public boolean contains(Object o) :如果set包含指定元素,返回true
public Iterator iterator()返回set中元素的迭代器
public Object[] toArray() :返回包含set中所有元素的數組public Object[] toArray(Object[] a) :返回包含set中所有元素的數組,返回數組的運行時類型是指定數組的運行時類型
public boolean add(Object o) :如果set中不存在指定元素,則向set加入
public boolean remove(Object o) :如果set中存在指定元素,則從set中刪除
public boolean removeAll(Collection c) :如果set包含指定集合,則從set中刪除指定集合的所有元素
public boolean containsAll(Collection c) :如果set包含指定集合的所有元素,返回true。如果指定集合也是一個set,只有是當前set的子集時,方法返回true
- LinkedHashSet介紹
LinkedHashSet是HashSet的一個子類,LinkedHashSet也根據HashCode的值來決定元素的存儲位置,但同時它還用一個鏈表來維護元素的插入順序,插入的時候即要計算hashCode又要維護鏈表,而遍歷的時候只需要按鏈表來訪問元素。因此在插入時性能比HashSet低,但在迭代訪問(遍歷)時性能更高
- TreeSet介紹
TreeSet實現了SortedSet接口,顧名思義這是一種排序的Set集合,底層是用TreeMap實現的,本質上是一個紅黑樹原理。 TreeSet提供了一些額外的按排序位置訪問元素的方法,例如first(), last(), lower(), higher(), subSet(), headSet(), tailSet().
四、Map接口
- Map接口下的集合與Collection接口下的集合比較
Collection中的集合,元素是孤立存在的,向集合中存儲元素採用一個個元素的方式存儲。
Collection中的集合稱爲單列集合,Map中的集合稱爲雙列集合。
Map中的集合,元素是成對存在的。每個元素由鍵與值兩部分組成,通過鍵可以找對所對應的值。
- Map常用方法
Object put(Object key,Object value):用來存放一個鍵-值對Map中
Object remove(Object key):根據key(鍵),移除鍵-值對,並將值返回
void putAll(Map mapping) :將另外一個Map中的元素存入當前的Map中
void clear() :清空當前Map中的元素
Object get(Object key) :根據key(鍵)取得對應的值
boolean containsKey(Object key) :判斷Map中是否存在某鍵(key)
boolean containsValue(Object value):判斷Map中是否存在某值(value)
public Set keySet() :返回所有的鍵(key),並使用Set容器存放
public Collection values() :返回所有的值(Value),並使用Collection存放
public Set entrySet() :返回一個實現 Map.Entry 接口的元素 Set
- HashMap和LinkedHashMap
HashMap 基於哈希表的 Map 接口的實現,存入順序和輸出順序無關。存儲鍵值對,鍵是唯一的,允許使用 null 值和 null 鍵,非同步(除了非同步和允許使用 null 之外,HashMap 類與 Hashtable 大致相同)。
interface Map{
interface Entry{//Entry是Map的一個內部接口
//由Map的子類的內部類實現
}
}
class HashMap{
static class Entry<K,V> implements Map.Entry<K,V> {//Entry對象指的就是該類的對象
final K key;
V value;
}
}
使用HashMap集合,存儲自定義的對象
public class HashMapDemo {
public static void main(String[] args) {
function_1();
}
/*
* HashMap 存儲自定義對象Person,作爲鍵出現
* 鍵的對象,是Person類型,值是字符串
* 保證鍵的唯一性,存儲到鍵的對象,重寫hashCode equals
*/
public static void function_1(){
HashMap<Person, String> map = new HashMap<Person, String>();
map.put(new Person("a",20), "里約熱內盧");
map.put(new Person("b",18), "索馬里");
map.put(new Person("b",18), "索馬里");
map.put(new Person("c",19), "百慕大");
for(Person key : map.keySet()){
String value = map.get(key);
System.out.println(key+"..."+value);
}
System.out.println("===================");
for(Map.Entry<Person, String> entry : map.entrySet()){
System.out.println(entry.getKey()+"..."+entry.getValue());
}
}
/*
* HashMap 存儲自定義的對象Person,作爲值出現
* 鍵的對象,是字符串,可以保證唯一性
*/
public static void function(){
HashMap<String, Person> map = new HashMap<String, Person>();
map.put("beijing", new Person("a",20));
map.put("tianjin", new Person("b",18));
map.put("shanghai", new Person("c",19));
for(String key : map.keySet()){
Person value = map.get(key);
System.out.println(key+"..."+value);
}
System.out.println("=================");
for(Map.Entry<String, Person> entry : map.entrySet()){
String key = entry.getKey();
Person value = entry.getValue();
System.out.println(key+"..."+value);
}
}
}
LinkedHashMap 繼承自 HashMap 所以具有 HashMap 的所有特性。同時又實現了雙向鏈表的特性,保留了元素插入順序。
public class LinkedHashMapDemo {
public static void main(String[] args) {
LinkedHashMap<String, String> link = new LinkedHashMap<String, String>();
link.put("1", "a");
link.put("13", "a");
link.put("15", "a");
link.put("17", "a");
System.out.println(link);
}
}
TreeMap則是對Map中的元素進行排序(根據鍵(Key)進行排序)。HashMap和LinkedHashMap 存儲數據的速度比直接使用TreeMap 要快,存取效率要高。所以在完成了所有的元素的存放後,再對整個的Map中的元素進行排序。這樣可以提高整個程序的運行的效率,縮短執行時間。
- Map集合遍歷方式
1、Map集合遍歷方式keySet方法
1).獲取Map集合中所有的鍵,由於鍵是唯一的,所以返回一個Set集合存儲所有的鍵
2).遍歷鍵的Set集合,得到每一個鍵
3).根據鍵利用get(key)去Map找所對應的值
public class MapDemo1 {
public static void main(String[] args) {
/*
* 1. 調用map集合的方法keySet,所有的鍵存儲到Set集合中
* 2. 遍歷Set集合,獲取出Set集合中的所有元素 (Map中的鍵)
* 3. 調用map集合方法get,通過鍵獲取到值
*/
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("a", 11);
map.put("b", 12);
map.put("c", 13);
map.put("d", 14);
//1. 調用map集合的方法keySet,所有的鍵存儲到Set集合中
Set<String> set = map.keySet();
//2. 遍歷Set集合,獲取出Set集合中的所有元素 (Map中的鍵)
Iterator<String> it = set.iterator();
while(it.hasNext()){
//it.next返回是Set集合元素,也就是Map中的鍵
//3. 調用map集合方法get,通過鍵獲取到值
String key = it.next();
Integer value = map.get(key);
System.out.println(key+"...."+value);
}
System.out.println("=======================");
for(String key : map.keySet()){
Integer value = map.get(key);
System.out.println(key+"...."+value);
}
}
}
2、Map集合遍歷方式entrySet方法
entrySet方法,鍵值對映射關係獲取
實現步驟:
1). 調用map集合方法entrySet()將集合中的映射關係對象,存儲到Set集合Set<Entry <K,V> >
2). 迭代Set集合
3). 獲取出的Set集合的元素,是映射關係對象
4). 通過映射關係對象方法 getKet, getValue獲取鍵值對
public class MapDemo2 {
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<Integer, String>();
map.put(1, "abc");
map.put(2, "bcd");
map.put(3, "cde");
//1. 調用map集合方法entrySet()將集合中的映射關係對象,存儲到Set集合
Set<Map.Entry <Integer,String> > set = map.entrySet();
//2. 迭代Set集合
Iterator<Map.Entry <Integer,String> > it = set.iterator();
while(it.hasNext()){
// 3. 獲取出的Set集合的元素,是映射關係對象
// it.next 獲取的是什麼對象,也是Map.Entry對象
Map.Entry<Integer, String> entry = it.next();
//4. 通過映射關係對象方法 getKet, getValue獲取鍵值對
Integer key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"...."+value);
}
}
}
3、Map集合遍歷方式增強for循環
實現步驟:
1. 調用map集合方法entrySet()將集合中的映射關係對象,存儲到Set集合Set<Entry <K,V> >
2. 迭代Set集合
3. 獲取出的Set集合的元素,是映射關係對象
4. 通過映射關係對象方法 getKet, getValue獲取鍵值對
Map集合不能直接使用迭代器或者foreach進行遍歷。但是轉成Set之後就可以了。
public class MapDemo2 {
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<Integer, String>();
map.put(1, "abc");
map.put(2, "bcd");
map.put(3, "cde");
//1. 調用map集合方法entrySet()將集合中的映射關係對象,存儲到Set集合
Set<Map.Entry <Integer,String> > set = map.entrySet();
//2. 迭代Set集合
Iterator<Map.Entry <Integer,String> > it = set.iterator();
while(it.hasNext()){
// 3. 獲取出的Set集合的元素,是映射關係對象
// it.next 獲取的是什麼對象,也是Map.Entry對象
Map.Entry<Integer, String> entry = it.next();
//4. 通過映射關係對象方法 getKet, getValue獲取鍵值對
Integer key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"...."+value);
}
System.out.println("=========================");
for(Map.Entry<Integer, String> entry : map.entrySet()){
System.out.println(entry.getKey()+"..."+entry.getValue());
}
}
}
五、Collections和Arrays工具類
- Collections
Collections提供對集合進行操作的一個包裝器,實現對集合的查找、排序、替換、 線程安全化(將非同步的集合轉換成同步的)等操作。
常用方法:
Collections.max(list);//返回list中字典順序最大的元素。
int index = Collections.binarySearch(list,”zz”);//二分查找,返回角標,必須是有序的。
Collections.fill();//可以將list集合中的所有元素替換成指定元素。
Collections.repalceAll(list,”要被替換的”,”替換的值”);//可以將list集合中的指定元素替換成指定元素。
Collections.reverse(); 反轉
Collections.reverseOrder(參數是比較器);//逆向反轉排序。倒序。
Collections.shuffle(list);//隨機對list中的元素進行位置的置換。
synchronizedXXX(XXX);非同步集合轉成同步集合的方法。
- Arrays
Arrays用於操作數組對象的工具類,裏面都是靜態方法。
Arrays.asList:可以從 Array 轉換成 List。可以作爲其他集合類型構造器的參數。
Arrays.binarySearch:在一個已排序的或者其中一段中快速查找。
Arrays.copyOf:如果你想擴大數組容量又不想改變它的內容的時候可以使用這個方法。
Arrays.copyOfRange:可以複製整個數組或其中的一部分。
Arrays.deepEquals、Arrays.deepHashCode:Arrays.equals/hashCode的高級版本,支持子數組的操作。
Arrays.equals:如果你想要比較兩個數組是否相等,應該調用這個方法而不是數組對象中的 equals方法(數組對象中沒有重寫equals()方法,所以這個方法之比較引用而不比較內容)。
Arrays.fill:用一個給定的值填充整個數組或其中的一部分。
Arrays.hashCode:用來根據數組的內容計算其哈希值(數組對象的hashCode()不可用)。
Arrays.sort:對整個數組或者數組的一部分進行排序。也可以使用此方法用給定的比較器對對象數組進行排序。
Arrays.toString:打印數組的內容。
六、面試常見問題總結
1、HashMap和Hashtable的區別?
相同點:
HashMap和Hashtable都是java的集合類,都可以用來存放java對象
不同點:
1)歷史原因:Hashtable是基於陳舊的Dictionary類的,HashMap是java 1.2引進的Map接口的一個現實。
2)同步性:Hashtable是同步的,這個類中的一些方法保證了Hashtable中的對象是線程安全的,而HashMap則是異步的,因此HashMap中的對象並不是線程安全的,因爲同步的要求會影響執行的效率,所以如果你不需要線程安全的結合那麼使用HashMap是一個很好的選擇,這樣可以避免由於同步帶來的不必要的性能開銷,從而提高效率,我們一般所編寫的程序都是異步的,但如果是服務器端的代碼除外。
3)值:HashMap可以讓你將空值作爲一個表的條目的key或value。Hashtable是不能放入空值(null)的。
2、ArrayList和Vector的區別?
相同點:ArrayList與Vector都是java的集合類,都是用來存放java對象 。
不同點:
1)同步性:Vector是同步的,這個類的一些方法保證了Vector中的對象的線程安全的,而ArrayList則是異步的,因此ArrayList中的對象並不 是線程安全的,因爲同步要求會影響執行的效率,所以你不需要線程安全的集合那麼使用ArrayList是一個很好的選擇,這樣可以避免由於同步帶來的不必 要的性能開銷。
2)數據增長:從內部實現的機制來講,ArrayList和Vector都是使用數組(Array)來控制集合中的對象,當你向兩種類型中增加元素的時候,如果元素的數目超過了內部數組目前的長度他們都需要擴展內部數組的長度,Vector缺省情況下自動增長原來一倍的數組長度,ArrayList是原來的50%,所以最後你獲得的這個集合所佔的空間總是比你實際需要的要大,所以如果你要在集合中保存大量的數據,那麼使用Vector有一些優勢,因爲你可以通過設置集合的初始大小來避免不必要的資源開銷。
3)總結:
1)如果要求線程安全,使用Vector,Hashtable
2)如果不要求線程安全,使用ArrayList,LinkedList,HashMap
3)如果要求鍵值對,則使用HashMap,Hashtable
4)如果數據量很大,又要求線程安全考慮Vector
3、Arraylist 與 Linkedlist聯繫與區別?
1)、ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。
2)、對於隨機訪問get和set,ArrayList覺得優於LinkedList,因爲LinkedList要移動指針。
3)、對於新增和刪除操作add和remove,LinedList比較佔優勢,因爲ArrayList要移動數據。 這一點要看實際情況的。若只對單條數據插入或刪除,ArrayList的速度反而優於LinkedList。但若是批量隨機的插入刪除數據,LinkedList的速度大大優於ArrayList. 因爲ArrayList每插入一條數據,要移動插入點及之後的所有數據。
4、HashMap與TreeMap聯繫與區別?
1)、 HashMap通過hashcode對其內容進行快速查找,而TreeMap中所有的元素都保持着某種固定的順序,如果你需要得到一個有序的結果你就應該使用TreeMap(HashMap中元素的排列順序是不固定的)。
2)、在Map 中插入、刪除和定位元素,HashMap是最好的選擇。但如果您要按自然順序或自定義順序遍歷鍵,那麼TreeMap會更好。使用HashMap要求添加的鍵類明確定義了hashCode()和 equals()的實現。
兩個map中的元素一樣,但順序不一樣,導致hashCode()不一樣。
在HashMap中,同樣的值的map,順序不同,equals時,false;
而在treeMap中,同樣的值的map,順序不同,equals時,true,說明,treeMap在equals()時是整理了順序了的。
七、小結
ArrayList: 元素單個,效率高,多用於查詢
Vector: 元素單個,線程安全,多用於查詢
LinkedList:元素單個,多用於插入和刪除
HashMap: 元素成對,元素可爲空
HashTable: 元素成對,線程安全,元素不可爲空
本文部分截取以下文章:
https://blog.csdn.net/qq_42565099/article/details/80889884
https://www.cnblogs.com/admol/p/5127823.html
https://www.cnblogs.com/chenglc/p/8073049.html
https://baijiahao.baidu.com/s?id=1635677223045847111&wfr=spider&for=pc