1. 集合
1.1 爲什麼使用集合
開發中會使用大量相同數據類型的情況。如果使用數組來解決問題
1. 數組能夠使用的方法非常少,功能方法需要程序員自己完成。
2. 數據類型單一化,不支持多種情況。
3. 數組容量不可以更改。
集合爲解決問題而生:
1. 方法多種多樣,基本功能完善
2. 數據類型支持多樣化,但是又不失數據類型一致要求
3. 容量可以變,並且不用開發者操心
1.2 集合架構
Java中集合的【總接口】Collection。Java中所有和集合有關的內容,都是Collection接口的子接口或者實現類
interface Collection
–| interface List List接口,有序可重複
----| class ArrayList
【重點】可變長數組結構
原碼實現,瞭解其中的特徵,性能…
----| class LinkedList
【重點】雙向鏈表結構
----| class Vector
【遠古時代】JDK1.0 線程安全的ArrayList,如果不考慮線程安全問
題,建議使用ArrayList
–| interface Set Set接口,無序不可重複
----| HashSet 底層存儲數據的方式是採用哈希表方式
----| TreeSet 底層存儲數據的方式一個平衡二叉樹方式
以上這些東西我們之後會一一講解,現在我們先來了解一下collection接口的常用方法
1.3 Collection<E>接口下的常用方法
增:
boolean add(E e);
存入元素到當前集合對象中,這裏要求的數據類型是E類型,也就是泛型對於
的具體數據類型
boolean addAll(Collection<? extends E> c);
class Dog extends Animal
class Cat extends Animal
class Tiger extends Animal
==> ? extends E 泛型的上限
要求存入的集合c中,存儲的元素要麼是E類型,要麼是E類的子類
刪:
void clear();
清空整個集合
boolean remove(Object obj);
刪除集合中的指定元素
boolean removeAll(Collection<?> c);
刪除兩個集合的交集
boolean retainAll(Collection<?> c);
保留兩個集合的交集
查:
int size();
返回集合中有效元素個數
boolean isEmpty();
判斷當前集合是否爲空
boolean contains(Object obj);
判斷指定元素在當前集合中是否存在
boolean containsAll(Collection<?> c);
判斷集合c是不是當前集合的子集合
以下是代碼的實現:
注意導包,之前的部分代碼也需要導包
就是這個:
import java.util.ArrayList;
import java.util.Collection;
public class Demo1 {
public static void main(String[] args) {
/*
* 因爲Collection<E>是一個接口,接口沒有自己的類對象
* 這裏使用Collection接口的實現類來完成演示過程 ArrayList<E>
*/
Collection<String> c = new ArrayList<String>();
c.add("82年的拉菲");
c.add("82年的雪碧");
c.add("82年的可樂");
c.add("82年的老雪");
System.out.println(c);
Collection<String> c1 = new ArrayList<String>();
c1.add("百威");
c1.add("福佳白");
c1.add("精釀啤酒");
c1.add("修道院啤酒");
c.addAll(c1);
System.out.println(c);
c.remove("82年的雪碧");
System.out.println(c);
//c.removeAll(c1);
//System.out.println(c);
//c.retainAll(c1);
//System.out.println(c);
System.out.println("size:" + c.size());
System.out.println(c.isEmpty());
System.out.println(c.contains("百威"));
System.out.println(c.contains("哈爾濱"));
System.out.println(c.containsAll(c1));
c1.add("野格");
System.out.println(c.containsAll(c1));
}
}
1.4 迭代器
通過集合對象獲取對應的Iterator迭代器
Iterator iterator();
常用方法:
boolean hasNext();
判斷當前Iterator是否可以繼續運行。
E next();
獲取Iterator當前指向元素,並且指向下一個元素。
void remove();
刪除
【注意】
1. remove方法有且只能刪除通過next方法獲取的元素
2. remove方法如果想要使用,必須緊挨着next方法
代碼實現:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Demo1 {
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("星期一");
c.add("星期二");
c.add("星期三");
c.add("星期四");
c.add("星期五");
c.add("星期六");
c.add("星期日");
System.out.println(c);
/*
* 獲取當前集合對應的Iterator迭代器對象
*/
Iterator<String> iterator = c.iterator();
/*
System.out.println("當前Iterator是否可以繼續運行:" +iterator.hasNext());
System.out.println("獲取當Iterator指向元:" + iterator.next());
System.out.println("獲取當Iterator指向元:" + iterator.next());
iterator.remove();
System.out.println(c);
iterator.remove();
System.out.println(c);
*/
while (iterator.hasNext()) {
iterator.next();
iterator.remove();
}
System.out.println(c.isEmpty());
}
}
1.5 Iterator使用注意問題
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Demo2 {
public static void main(String[] args) {
ArrayList<String> c = new ArrayList<String>();
c.add("烤羊排");
c.add("油燜大蝦");
c.add("土豆牛肉");
c.add("黃燜雞米飯");
c.add("麻辣香鍋");
c.add("孜然肉片");
c.add("酸湯肥牛");
Iterator<String> iterator = c.iterator();
/*
* ConcurrentModificationException
* Iterator在創建的過程中,會對整個集合所有元素打招呼,記錄每一個元素位置。
* Iterator在執行next方法過程中,會按照初始條件一個一個遍歷
* 當前集合通過remove方法,刪除已經被Iterator記錄的元素時,是有可能導致
* Iterator一臉懵逼!!!元素不見了!!!
*
* 這裏就會發生衝突!
*
* 這裏因爲集合中元素,對於集合本身和當前Iterator而言是一個共享資源
* 不管是哪一方操作元素,都存在影響對方操作的情況。【共享資源衝突問題】
*
* ArrayList存儲元素不是連續的嗎,土豆牛肉刪除了,他的位置不是會被後面的元素頂上來嗎
*/
while (iterator.hasNext()) {
System.out.println(iterator.next());
// 這裏通過集合刪除土豆牛肉元素
// 後期代碼中會出現很多相同名字方法,這裏一定要注意!!!
// 調用當前方法的是哪一個
c.remove("酸湯肥牛");
}
}
}
Iterator出現併發錯誤原因:
1.6 List<E>
1.6.1 List集合接口特徵和方法
特徵:
有序,可重複
有序: 添加順序和存儲順序一致
可重複:相同元素可以同時添加
List接口下的實現類,存在一定的下標操作機制
ArrayList 底層數組形式操作,可以通過下標直接訪問
LinkedList 底層是一個雙向鏈表結構,下標 ==> 計數器
特定的方法:
增:
add(E e);
List接口下,當前方法是添加元素到集合的末尾,尾插法
addAll(Collection<? extends E> c);
List接口下,當前方法是添加另一個集合到當前集合末尾,要求添加的
集合中保存的元素和當前集合保存元素一致,或者說是當前集合保存元
素的子類
add(int index, E e);
在指定的下標位置,添加指定元素
addAll(int index, Collection<? extends E> c);
在指定的下標位置,添加指定的集合,集合要求同上一個addAll方法
刪:
void clear();
清空整個集合
remove(Object obj);
刪除集合中的指定元素
removeAll(Colletion<?> c);
刪除兩個集合的交集
retainAll(Colletion<?> c);
保留兩個集合的交集
E remove(int index);
刪除集合中指定下標的元素。返回值是被刪除的元素
改:
E set(int index, E e);
使用指定元素替換指定下標index的元素,返回值是被替換掉的元素。
查:
int size();
有效元素個數
boolean isEmpty();
判斷當前集合是否爲空
boolean contains(Object obj);
boolean containsAll(Collection<?> c);
int indexOf(Object obj);
找出指定元素在集合中的第一次出現位置
int lastIndexOf(Object obj);
找出指定元素在集合中最後一次出現位置
E get(int index);
獲取指定下標的元素
List subList(int fromIndex, int endIndex);
獲取當前集合的子集合
【特徵】
獲取數據的範圍是 fromIndex <= n < endIndex
要頭不要尾
1.7 ArrayList 可變長數組
特徵:
數組形式的操作方式,查詢效率高,但是刪除,增加效率低。
數組:
Object類型數組
方法:
ArrayList使用的方法基本上都是從List接口中遵從實現的方法。
特徵:
ensureCapacity(int minCapacity);
判斷當前容量是否足夠
trimToSize();
截斷整個數組容量 ==> size有效元素個數
時間換空間,空間換時間
自定義實現的ArrayList
Constructor構造方法
add(E e);
add(int index, E e);
addAll(自定義ArrayList e)
addAll(int index,自定義ArrayList e)
remove(Object obj);
remove(int index);
set(int index, E);
E get(int index);
int indexOf();
int lastIndexOf();
boolean contains(Object obj);
boolean containsAll(自定義ArrayList類型 list)
boolean isEmpty();
int size();
自定義ArrayList subList(int fromIndex, int endIndex);
Object[] toArray();
方法代碼實現:
import java.util.Arrays;
/**
* 自定義實現MyArraylist
* @author Anonymous
*
* @param <E> 自定義泛型
*/
public class MyArrayList<E> {
/**
* 準備一個底層數組,用於存儲數據內容
*/
private Object[] elements;
/**
* 初始化默認容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 最大數組容量, -8是爲了騰出一定的空間,保存數組的必要內容
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 當前底層數組中保存的有效元素個數
*/
private int size = 0;
/**
* 無參數構造方法,但是需要提供給用戶一個初始化容量來保存必要的數據
*/
public MyArrayList() {
elements = new Object[DEFAULT_CAPACITY];
}
/**
* 用戶指定保存元素容量的初始化過程,要求用戶指定的容量範圍是有效的
*
* @param initCapacity 用戶指定的初始化容量,但是不能小於等於0 ,不能大於
* MAX_ARRAY_SIZE
*/
public MyArrayList(int initCapacity) {
// 用戶傳入參數的合法性判斷過程
if (initCapacity < 0 || initCapacity > MAX_ARRAY_SIZE) {
// 拋出異常
// IllegalArgumentException 是一個RuntimeException運行時異常的子類
// 不需要強制聲明拋出異常
throw new IllegalArgumentException("IllegalArgumentException : " + initCapacity);
}
elements = new Object[initCapacity];
}
/*
* 增加方法
*/
/**
* 添加元素到當前集合的末尾
*
* @param e 要求是符合泛型約束的指定數據類型
* @return 添加成功返回true, 否則返回false
*/
public boolean add(E e) {
// 直接調用在指定下標位置添加元素的方法,只不過這裏指定下標位置就是
// 尾插法下標位置
return add(size, e);
}
/**
* 在底層數組的指定下標位置保存對應的元素
*
* @param index 指定下標位置,不能超出有效範圍,0<= index <= size
* @param e 符合泛型約束的數據類型
* @return 添加成功返回true, 否則返回false
*/
public boolean add(int index, E e) {
if (index < 0 || index > size) {
throw new ArrayIndexOutOfBoundsException(index);
}
ensureCapacity(size + 1);
for (int i = size; i > index; i--) {
elements[i] = elements[i - 1];
}
elements[index] = e;
size += 1;
return true;
}
/*
* addAll方法
* 1. 需要得到添加集合中元素內容,有效元素個數
* 2. 確認容量問題
* 3. size = srcSize + newSize
*/
/**
* 添加另一個集合到當前集合的末尾
*
* @param list MyArrayList類型,自定義ArrayList,要求存儲元素和當前集合一致,或者
* 是其子類
* @return 添加成功返回true,添加失敗返回false
*/
public boolean addAll(MyArrayList<? extends E> list) {
Object[] array = list.toArray();
int newSize = array.length;
ensureCapacity(size + newSize);
for (int i = 0; i < newSize; i++) {
elements[i + size] = array[i];
}
size += newSize;
return true;
}
/**
* Do yourself 作業
* @param index
* @param list
* @return
*/
public boolean addAll(int index, MyArrayList<? extends E> list) {
return true;
}
/**
* 刪除指定元素
*
* @param obj 指定刪除的元素
* @return 刪除成功返回true
*/
public boolean remove(Object obj) {
int index = indexOf(obj);
return null != remove(index);
}
/**
* 刪除下標元素
*
* @param index 指定的下標範圍
* @return 刪除成功返回對應元素,失敗返回null
*/
public E remove(int index) {
if (-1 == index) {
return null;
}
E e = get(index);
for (int i = index; i < size - 1; i++) {
elements[i] = elements[i + 1];
}
// 原本最後一個有效元素位置上的內容賦值爲null
elements[size - 1] = null;
size -= 1;
return e;
}
/**
* 獲取集合中指定下標的元素
*
* @param index 指定下標的範圍,但是不能超出有效下標範圍
* @return 返回對應的元素
*/
@SuppressWarnings("unchecked")
public E get(int index) {
if (index < 0 || index > size) {
throw new ArrayIndexOutOfBoundsException(index);
}
return (E) elements[index];
}
/**
* 查詢指定元素在集合中的第一次出現下標位置
* @param obj 指定的元素
* @return 返回值大於等於0表示找到元素,否則返回-1
*/
public int indexOf(Object obj) {
int index = -1;
for (int i = 0; i < size; i++) {
// equals 判斷對象是否一致地方的方法
if (obj.equals(elements[i])) {
index = i;
break;
}
}
return index;
}
/**
* 查詢指定元素在集合中的最後一次出現下標位置
*
* @param obj 指定的元素
* @return 返回值大於等於0表示找到元素,否則返回-1
*/
public int lastIndexOf(Object obj) {
int index = -1;
for (int i = size - 1; i >= 0; i--) {
// equals 判斷對象是否一致地方的方法
if (obj.equals(elements[i])) {
index = i;
break;
}
}
return index;
}
/**
* 返回MyArrayList集合中所有有效元素的Object數組
*
* @return 包含所有集合元素的Object類型數組
*/
public Object[] toArray() {
// size是有效元素個數,通過該方法可以獲取到一個只有當前數組中有效元素的數組
return Arrays.copyOf(elements, size);
}
/**
* 替換指定下標的元素
*
* @param index 指定下標元素,但是必須在有效範圍以內
* @param e 符合泛型約束的對應數據類型
* @return 被替換的元素
*/
public E set(int index ,E e) {
if (index < 0 || index >= size) {
throw new ArrayIndexOutOfBoundsException(index);
}
E temp = get(index);
elements[index] = e;
return temp;
}
/**
* 判斷指定元素是否存在
*
* @param obj 指定元素
* @return 存在返回true,不存在返回false
*/
public boolean contains(Object obj) {
return indexOf(obj) > -1;
}
/**
*
* @param list
* @return
*/
public boolean containsAll(MyArrayList<?> list) {
return false;
}
/**
* 判斷集合是否是空的
*
* @return 如果爲空,返回true, 否則返回false
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 獲取當前集合中有效元素個數
*
* @return 有效元素個數
*/
public int size() {
return size;
}
/**
* 獲取當前集合的子集合,截取範圍是fromIndex <= n < endIndex
*
* @param fromIndex fromIndex <= endIndex 不得小於0
* @param endIndex endIndex >= fromIndex 小於等於size
* @return 截取得到的一個MyArrayList子集合對象
*/
public MyArrayList<E> subList(int fromIndex, int endIndex) {
if (fromIndex > endIndex || fromIndex < 0 || endIndex > size) {
throw new ArrayIndexOutOfBoundsException();
}
MyArrayList<E> listTemp = new MyArrayList<E>(endIndex - fromIndex);
for (int i = fromIndex; i < endIndex; i++) {
listTemp.add(this.get(i));
}
return listTemp;
}
@Override
public String toString() {
String str = "[";
for (int i = 0; i < size - 1; i++) {
str += elements[i] + ", ";
}
return str + elements[size - 1] + "]";
}
/*
* 這裏需要類內使用的可以用於判定當前容量是否滿足添加要求的方法
* 如果滿足直接進入添加模式,如果不滿足,需要執行grow方法,完成
* 底層數組的擴容問題。
*/
/**
* 每一次添加元素,都需要進行容量判斷,如果滿足可以進行添加操作
* 不滿足需要制定grow方法
*
* @param minCapacity 要求的最小容量
*/
private void ensureCapacity(int minCapacity) {
if (minCapacity > elements.length) {
// 完成一個底層數組的擴容方法
grow(minCapacity);
}
}
/**
* 底層數組的擴容方法,原理是創建新數組,移植數據,保存新數組地址
*
* @param minCapacity 要求的最小容量
*/
private void grow(int minCapacity) {
// 1. 獲取原數組容量
int oldCapacity = elements.length;
// 2. 計算得到新數組容量
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 3. 判斷新數組容量是否滿足要求
if (newCapacity < minCapacity) {
newCapacity = minCapacity;
}
// 新數組容量是大於允許的最大數組容量
if (newCapacity > MAX_ARRAY_SIZE) {
// 二次判斷minCapacity是否小於MAX_ARRAY_SIZE
if (minCapacity < MAX_ARRAY_SIZE) {
// 最小要求是不大於MAX_ARRAY_SIZE,代碼可以運行
newCapacity = minCapacity;
} else {
throw new OutOfMemoryError("Overflow MAX_ARRAY_SIZE");
}
}
/*
* 4. 使用數組工具類方法完成操作
* Arrays.copyOf(源數據數組,可以是任意類型,採用泛型約束, 指定的新數組容量);
* a. 根據指定的新數組容量創建對應泛型數據類型的新數組
* b. 從源數據數組中拷貝內容到新數組中
* c. 返回新數組首地址
*/
elements = Arrays.copyOf(elements, newCapacity);
}
}
1.8 ArrayList性能問題
增刪慢
增加慢
1. 增加元素有可能出現調用grow方法,grow需要進行數組的擴容操作,操作過程中需要大
量的移動和拷貝過程,浪費時間
2. 在某一個指定位置添加元素,會導致從指定位置開始,之後的元素整體向後移動,涉及
移動複製操作,浪費時間。
刪除慢:
1. 按照ArrayList可變長數組要求,刪除元素之後,之後的內容都需要整體向前移動。
查詢快
1.9 補充知識點【內存】
內存中的地址
1. 內存中的最小單元 字節
2. 計算機爲了記錄標記每一個字節的內存,給內存進行了編號
3. 航海中路60號,精確查詢,唯一性
4. 計算機內存按照字節,一個字節對應一個地址,從0開始編號 到 34359738367 (32G
內存)
5. 計算機通過內存編號訪問對應的內存,效率是非常高的!!!
6. 按照十進制表示地址編號,效果很差。 這裏引入16進制 0x0 ~ 0x7 FFFF FFFF
null到底是誰?
null ==> 0x0 內存中編號爲0的地址
該地址受到系統保護,任何程序讀取,寫入0x0地址,系統直接殺死程序
一般用於在開發中初始化引用數據類型的變量,利用null報錯。NullPointerException
1.10 LinkedList
1.10.1 1 LinkedList特徵
底層是一個雙向鏈表
鏈子 自行車鏈子 船錨 手鍊
自行車鏈子
維修很方便,前斷,後斷,鏈接搞定!!! 找到損壞的位置,需要一個一個來
鏈表結構
1. 增刪快
2. 查詢很慢很慢很慢
- 存儲數據,非連續空間。
- 數據之間通過引用連接,方便遍歷和使用
- 遍歷效率較低,數據非連續空間存儲,需要通過引用跳轉過程來完成
- 刪除插入操作效率高,但是注意地址的轉移和保存問題。
- LinkedList鏈表當中的操作其實大部分都是和C語言指針一個概念
1.10.2 LinkedList需要了解的方法
LinkedList使用的方法,大部分都是從List接口中遵從實現的方法
但是有一些特徵方法
addFirst(E e);
addLast(E e); ==> add(E e);
E getFirst();
E getLast();
removeFirst();
removeLast();
以上方法組合可以完堆棧隊列操作
堆
先進後出
彈夾
addLast(E e); E getLast(); removeLast();
隊列
先進先出
addLast(E e); E getFrist(); removeFirst();
1.11 自定義完成MyLinkedList
1.11.1 節點
a. 向前的引用
b. 存儲元素的引用
c. 向後的引用
class Node {
private Node prev;
private Node next;
private Object value;
// 構造方法存儲元素,進入鏈接
}
1.11.2 管理節點的MyLinkedList
a. 鏈表頭!!!
b. 當前鏈表中有多少個元素。
class MyLinkedList {
private Node first;
private int size;
}
要求實現的方法:
add(E e);
addFirst(E e);
addLast(E e);
E getFirst();
E getLast():
E get(int index);
removeFirst();
removeLast();
remove(int index);
remove(Object obj);
E set(int index, E);
size();
1.11.3 LinkedList實現
還需要補充的方法:
add(int index,E e);
addAll(MyLinkedList list);
addAll(int index, MyLinkedList list);
boolean isEmpty();
int indexOf(Object obj);
int lastIndexOf(Object obj);
boolean contains(Object obj);
boolean containsAll(MyLinkedList<?> list);
Object[] toArray();
1.12 Set集合
1.12.1 Set集合概述
特徵:
無序,不可重複
無序:添加順序和存儲順序不一致,【不代表有排序效果】
不可重複: 在一個Set集合中不能出現相同元素
interface Set
–| class HashSet 底層是哈希表存儲數據
–| class TreeSet 底層存儲數據是一個二叉樹
1.12.2 HashSet
底層結構
import java.util.HashSet;
public class Demo2 {
public static void main(String[] args) {
HashSet<Person> hashSet = new HashSet<Person>();
Person p1 = new Person(1, "寢室長", 10);
Person p2 = new Person(2, "隊長", 15);
Person p3 = new Person(3, "宇哥", 2);
Person p4 = new Person(4, "老康", -5);
Person p5 = new Person(5, "港兒子", 11);
/*
* 當前這裏兩個元素,ID一樣 ==> hashCode值是一致,會通過底層哈希表運算
* 保存到同一個單元格位置。
* 這裏會通過equals方法,比較兩個對象是否一致,來決定是否能夠保存。
* 如果兩個對象一致,無法保存。
*
* 期望每一個哈希表單元格內保存的數據是唯一
*/
Person p6 = new Person(6, "康爺", 8);
Person p7 = new Person(6, "康牙", 10);
hashSet.add(p4);
hashSet.add(p6);
hashSet.add(p3);
hashSet.add(p5);
hashSet.add(p2);
hashSet.add(p1);
hashSet.add(p7);
System.out.println(hashSet);
}
}
1.13.2 TreeSet
Tree樹形結構
TreeSet存儲方式
沒有比較方式無法存儲
Comparable接口使用
interface Comparable {
int compareTo(T t);
}
方法參數爲T類型,由實現類遵從接口時約束,
compareTo方法,返回值類型int類型,0, 負數,正數
0 表示兩個元素一致,如果在TreeSet中比較結果爲0,表示同一個元素,無法存儲第二個。
Comparable接口由存儲元素對應的類遵從,完成該方法
Comparator接口使用
interface Comparator {
int compare(T o1, T o2);
}
需要完成一個自定義比較器類對象,
int 返回值 0,負數,正數
0 表示兩個元素一致,如果在TreeSet中比較結果爲0,表示同一個元素,無法存儲第二個。
Comparator使用要高於Comparable使用