集合概念
在以往的編程過程中 , 使用數組來存儲一系列 相同類型的數據
數組的弊端
- 數組長度固定不變, 不能夠很好地適應元素數量動態變化 (新聞每天數量不等)
- 可以通過 .length 獲取數組長度, 不能獲取實際元素的個數
概念
是一種工具類,可以存儲任意數量、任意類型的對象
集合與數組的差別
1、數組長度固定,集合長度可變
2、數組只能通過下標訪問具體元素,集合則可通過任意類型查找所映射的具體對象
集合體繫結構
Collection 單列集合
list 存儲 可重複, 有序的一組數據
set 存儲 不重複(唯一) , 無序的一組數據
Map 雙列集合
存儲的數據, 是成對出現的
List
list 特點 可重複, 有序
ArrayList
方法名 | 說明 |
---|---|
boolean add(Object o) | |
void add(int index , Object o) | |
int size() | |
Object get(int index) | |
boolean contains(Object o) | |
boolean remove(Object o) | |
Object remove(int index) |
// 使用ArrayList 保存每天的新聞信息
public class NewsTitle {
private int id;
private String title;
private String author;
}
NewsTitle nt1 = new NewsTitle(1,"XX又出車禍了","某某強");
NewsTitle nt2 = new NewsTitle(2,"上海垃圾分類","某某強");
// 創建一個集合類
ArrayList list = new ArrayList();
// 添加元素
list.add(nt1);
list.add(nt2);
collection 接口中常用的方法
clear() | |
isEmpty() | size() == 0 |
toArray() | |
iterator(); | 迭代器 |
LinkedList 和 ArrayList 區別
數據結構
數組 ( 排隊 軍訓 報數 有下標 )
鏈表 ( 沒有下標 必須擺好一個積木之後才能開始擺下一個 )
數組
數組是數據結構中很基本的結構,很多編程語言都內置數組。
在java中當創建數組時會在內存中劃分出一塊連續的內存,然後當有數據進入的時候會將數據按順序的存儲在這塊連續的內存中。當需要讀取數組中的數據時,需要提供數組中的索引,然後數組根據索引將內存中的數據取出來,返回給讀取程序。在Java中並不是所有的數據都能存儲到數組中,只有相同類型的數據纔可以一起存儲到數組中。
所有的數據結構都支持幾個基本操作:讀取、插入、刪除。
因爲數組在存儲數據時是按順序存儲的,存儲數據的內存也是連續的,所以他的特點就是尋址讀取數據比較容易,插入和刪除比較困難。簡單解釋一下爲什麼,在讀取數據時,只需要告訴數組要從哪個位置(索引)取數據就可以了,數組會直接把你想要的位置的數據取出來給你。插入和刪除比較困難是因爲這些存儲數據的內存是連續的,要插入和刪除就需要變更整個數組中的數據的位置。舉個例子:一個數組中編號0->1->2->3->4這五個內存地址中都存了數組的數據,但現在你需要往4中插入一個數據,那就代表着從4開始,後面的所有內存中的數據都要往後移一個位置。這可是很耗時的。
鏈表
在java中創建鏈表的過程和創建數組的過程不同,不會先劃出一塊連續的內存。因爲鏈表中的數據並不是連續的,鏈表在存儲數據的內存中有兩塊區域,一塊區域用來存儲數據,一塊區域用來記錄下一個數據保存在哪裏(指向下一個數據的指針)。當有數據進入鏈表時候,會根據指針找到下一個存儲數據的位置,然後把數據保存起來,然後再指向下一個存儲數據的位置。這樣鏈表就把一些碎片空間利用起來了,雖然鏈表是線性表,但是並不會按線性的順序存儲數據。
由於鏈表是以這種方式保存數據,所以鏈表在插入和刪除時比較容易,讀取數據時比較麻煩。舉個例子:一個鏈表中0->1->2->3->4這五個內存地址中都存了數據,現在需要往2中插入一條數據,那麼只需要更改1號和2號中記錄下一個數據的位置就行了,對其他數據沒有影響。刪除一條數據與插入類似,很高效。但是如果是想要在鏈表其中取出一條數據,就需要從0號開始一個一個的找,直到找到想要的那條數據爲止。
鏈表中插入
鏈表中刪除
對比
ArrayList 底層數據結構爲數組, 查詢數據效率高, 插入數據, 刪除數據效率低
LinkedList 底層數據結構爲鏈表, 查詢數據效率低, 插入,刪除數據效率高
LinkedList 特有方法
特有方法 | |
---|---|
void addFirst(Object o) | |
void addLast(Object o) | |
Object getFirst() | |
Object getLast() | |
Object removeFirst() | |
Object removeLast() |
Set 接口
hashSet
無序 , 不重複
// 多態 父類引用指向子類對象
Set set = new HashSet();
set.add(3);
set.add(1);
set.add(2);
set.add(2);
// 無序, 是和程序存放的順序無關
System.out.println(set.toString());
// set 集合 沒有下標 所以 不能通過普通的for循環獲取數據
/*for(int i= 0 ; i< set.size(); i++){
System.out.println(set.get(i));
}*/
回顧Object類中的equals() 方法
// Object 源碼 , 一個類如果不重寫 , 就使用父類的, 還是比較地址
public boolean equals(Object obj) {
return (this == obj);
// s1 == s2
}
子類重寫之後
// 一般由子類重寫, 用於比較內容否一致
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return sId == student.sId &&
Objects.equals(name, student.name);
}
set 接口 通過equals() 方法判斷兩個類是否相同
TreeSet
具有排序功能的 類
二叉樹數據結構
樹結構
二叉樹結構
自平衡二叉樹
自動排序案例
TreeSet set = new TreeSet();
set.add(8);
set.add(6);
set.add(1);
set.add(9);
set.add(3);
set.add(7);
set.add(4);
for(Object obj : set){
System.out.println(obj);
}
學生按照成績排序
環境準備
public class Student {
private int sid; // 學號
private String name;// 姓名
private double score; // 得分
}
接口和抽象類的區別
抽象類 is a 子類是一個父類 貓是一個動物
接口 has a 子類有父接口的功能
排序 就是一個接口
使用TreeSet 添加自定義類型數據, 拋出異常分析
Student s1 = new Student(10086, "煎餅果子", 100);
Student s2 = new Student(10011, "雞蛋灌餅", 80);
Student s3 = new Student(12306, "手抓餅", 90);
Student s4 = new Student(12315, "武大郎燒餅", 100);
TreeSet<Student> set = new TreeSet<Student>();
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
// ClassCastException: date0719.Student cannot be cast to java.lang.Comparable
for(Student s : set){
System.out.println(s);
}
Comparable 就是一個排序接口, TreeSet 可以識別 Comparable接口的類型的排序功能
public class Student implements Comparable{
private int sid;
private String name;
private double score;
@Override
// 返回值 0 說明不添加
// 返回值爲 -1 說明添加到左側
// 返回值爲 +1 添加到右側
public int compareTo(Object o) { // 參數就是根節點
System.out.println("執行了排序算法!");
Student stu = (Student)o;
/* if(this.score < stu.score){
return -1;
}else{
return 1;
}*/
// 如果 我的成績比較小 我就向左放
// return (int)(this.score - stu.score);
// 指定 按照學號排序
return this.sid - stu.sid;
}
}
String Integer 都默認實現了 排序接口
public final class String
implements java.io.Serializable, Comparable<String>{}
Iterator
Collection 接口中 一個 Iterator 接口
Iterator 接口 專門用於 集合遍歷的接口
每一個 Collection 系列中的集合 都有一個方法 iterator() 用來獲取本身的迭代器對象
通過該對象 可以遍歷集合中的所有元素
iterator.hasNext()
iterator.next()
Iterator iterator = set.iterator();
// iterator.hasNext() 還有沒有元素
// iterator.next() 獲取下一個元素
while(iterator.hasNext()){
// 集合可以存放任意類型的元素
Object o = iterator.next();
System.out.println(o);
}
增強 for 循環
底層是 使用Iterator 實現的
// 獲取 set集合中的每一個 元素 , 使用Object 接收
for(Object o : set ){
System.out.println(o);
}
泛型
爲什麼要是用泛型
Student s1 = new Student(10086,"張三");
Student s2 = new Student(10010,"李四");
Student s3 = new Student(12306,"王二麻子");
set.add(s1);
set.add(s2);
set.add(s3);
// 可以存放任意類型
// set.add(123456);
// 要求打印每一個學生的名字
for(Object o : set ){
// 把Object 類 強轉爲 Student
// 123456 強轉失敗, 類型轉換異常
Student stu = (Student) o;
System.out.println(stu.name);
}
定義
泛型,即“參數化類型”。一提到參數,最熟悉的就是定義方法時的 形參,然後調用此方法時傳遞實參。那麼參數化類型怎麼理解呢?顧名思義,就是將類型由原來的具體的類型參數化,類似於方法中的變量參數,此時類型也定義成參數形式(可以稱之爲類型形參),然後在使用/調用時傳入具體的類型(類型實參)。
定義一個方法 傳入參數
public static void main(String[] args) {
m1(5); // 5 實際參數 實參
}
// 方法的參數列表 (形參)
public static void m1(int i){
System.out.println(i*i);
}
給集合添加參數化類型
List<Student> list = new ArrayList<Student>();
// 泛型集合 , 能夠約束 該集合中只能存放哪種類型的元素
List<Student> list = new ArrayList<Student>();
Student s1 = new Student(10086,"張三");
Student s2 = new Student(10010,"李四");
Student s3 = new Student(12306,"王二麻子");
list.add(s1);
list.add(s2);
list.add(s3);
// list.add(789); // 非 Student 類型 不能存儲到集合中
// 不需要 強制類型轉換
for(Student stu : list){
System.out.println(stu.name);
}
泛型的好處
- 可以把 運行時的異常 提升到編譯期 (徵兵 )
- 更加方便的使用增強型 for 循環
泛型方法
需求引入 // 輸出的值 就是輸出的值
public static void main(String[] args) {
int i = get(3);
String abc = get("abc");
Student stu = new Student(9527, "華安", 100);
Object o = get(stu);
}
public static Object get(Object obj){
return obj;
}
// 輸出的值 就是輸出的值
public static int get(int i){
return i;
}
// 輸出的值 就是輸出的值
public static String get(String s){
return s;
}
泛型方法
public static void main(String[] args) {
int i = get(3);
String abc = get("abc");
Student stu = new Student(9527, "華安", 100);
Student student = get(stu);
}
/* public static Object get(Object obj){
return obj;
}*/
// java 不允許出現 未定義的變量 E 需要定義<E>
// E 參數化類型 , 可以傳遞任意數據類型, 相當於Object
// 假設用戶傳遞的是一個 E , 就返回一個E類型
public static<E> E get(E e){
return e;
}
泛型類
/**
* 泛型類 , 把泛型定義到類上, 所有方法都可以使用該泛型
* @param <E>
*/
public class Demo<E>{
public E get(E e){
return e;
}
public E put(E e){
return e;
}
}
public class Demo<E>{
public E get(E e){
return e;
}
public E put(E e){
return e;
}
public static void main(String[] args) {
Demo<String> demo = new Demo<String>;
demo.get("abc");
Demo<Student> stuDemo = new Demo<Student>;
stuDemo.get(new Student());
// List 本質上就是一個泛型類
ArrayList<String> list = new ArrayList<String>();
list.add("abc");
}
}
泛型接口
public class ArrayList<E> extends AbstractList<E>
implements List<E>
Map
向集合中存儲一個一個的學生
需求 : 存儲一對一對的夫妻
List<String> list = new ArrayList<String>();
list.add("張三");
list.add("李四");
list.add("諸葛亮");
list.add("卓文君");
list.add("黃月英");
list.add("司馬相如");
// list.add("諸葛亮","黃月英"); // 永生不分離
語法
// 定義map 集合
Map<String,String> map = new HashMap<String,String>();
map.put("諸葛亮","黃月英");
map.put("司馬相如","卓文君");
map.put("薛平貴","王寶釧");
常用方法
方法名 | 解釋 |
---|---|
Object put(key 鍵, value 值) | 存儲鍵值對 |
Object get(key) | 根據鍵, 獲取相對應的值, 如果不存在,返回 null |
Object remove(key) | 根據鍵 刪除相應的鍵值對 |
int size() | 獲取 元素個數 |
Set keySet() | 獲取 map 中 所有的鍵 |
Collection values() | 獲取 map 中 所有的值 |
boolean containsKey(key) | 判斷 map 中 是否包含 指定的鍵 |
Set entrySet() | 獲取 所有鍵值對的集合 |
put()
map.put("王悉丞","高翠蓮");
map.put("王悉丞","嫦娥");
map.put("豬八戒","嫦娥");
System.out.println(map);
- 鍵是唯一的, 值是可以重複的
- 使用put 添加內容的時候, 如果鍵相同, 會覆蓋掉之前的鍵值對
遍歷 map 集合
廣場上 一些老大爺和老大娘 跳交際舞
召集一些成員組成一支隊伍 (map)
如何召集他們
第一種方式 keySet()
先找到 老大爺, 讓他們去自己的舞伴
// 獲取所有的鍵
Set<String> keySet = map.keySet();
// 根據鍵獲取值
for(String key : keySet){
System.out.print(key+"\t");
System.out.println(map.get(key));
}
第二種遍歷方式
Entry 類 (鍵值對類) entrySet
每一對舞伴, 都起一個組合的名稱 , 組合名稱報上來
// Entry<String, String>
Set<Map.Entry<String, String>> entrySet = map.entrySet();
for(Map.Entry<String, String> entry : entrySet ){
System.out.println(entry.getKey()+"\t"+entry.getValue());
}
Collections
Array 是數組 Arrays 是數組工具類
Colection 集合總接口 Collections 就是集合的工具類
常用方法
方法名 | |
---|---|
sort() | 排序 |
binarySearch() | 查找 |
max()/min() | 查找最大值/最小值 |
shuffle(List list) | 使用默認的隨機源隨機排列指定的列表。 |
鬥地主案例
需求分析
鬥地主 54張
四種花色 + 大小王
使用一個集合來製作一副撲克牌
初級版本的案例
List<String> huaSe = new ArrayList<String>();
huaSe.add("♥");
huaSe.add("♠");
huaSe.add("♣");
huaSe.add("♦");
List<String> shuZi = new ArrayList<String>();
shuZi.add("A");
shuZi.add("2");
shuZi.add("3");
shuZi.add("4");
shuZi.add("5");
shuZi.add("6");
shuZi.add("7");
shuZi.add("8");
shuZi.add("9");
shuZi.add("10");
shuZi.add("J");
shuZi.add("Q");
shuZi.add("K");
//把兩個集合的 內容拼裝組合成爲一副牌
List<String> PK = new ArrayList<String>();
// 花色
for(String hua : huaSe){
// 數字
for(String num : shuZi){
PK.add(hua+num);
}
}
PK.add("BJoker");
PK.add("SJoker");
// 洗牌
Collections.shuffle(PK);
// 定義三位遊戲玩家
List<String> wu = new ArrayList<String>();
List<String> yu = new ArrayList<String>();
List<String> li = new ArrayList<String>();
// 定義底牌
List<String> di = new ArrayList<String>();
// 開始發牌
for(int i = 0 ; i < PK.size() ; i++){
if(i > 50){
di.add(PK.get(i));
continue;
}
if(i % 3 == 0){
wu.add(PK.get(i));
}else if(i % 3 == 1){
yu.add(PK.get(i));
}else{
li.add(PK.get(i));
}
}
// 看牌
System.out.println(li);
有序版本的優化
添加排序功能
集合中 數據全部都是使用特殊符號組成的 字符串, 排序的結果無非是按照花色排序, 不是我們需要的
集合本身沒有排序功能
手動給所有的牌添加一個序號, 按照牌面大小分配編號
牌面 和 對應的編號 是成對出現的
考慮使用map 集合
// 序號 和 牌面是一個一一對應的關係
洗牌的時候 和 發給用戶的時候 , 都是編號
只有在最後看牌的時候, 纔是使用 map 獲取真正的牌面
public class DouPlus {
public static void main(String[] args) {
// 新建兩個集合
// 保存花色
// 保存數字
List<String> huaSe = new ArrayList<String>();
huaSe.add("♥");
huaSe.add("♠");
huaSe.add("♣");
huaSe.add("♦");
List<String> shuZi = new ArrayList<String>();
shuZi.add("3");
shuZi.add("4");
shuZi.add("5");
shuZi.add("6");
shuZi.add("7");
shuZi.add("8");
shuZi.add("9");
shuZi.add("10");
shuZi.add("J");
shuZi.add("Q");
shuZi.add("K");
shuZi.add("A");
shuZi.add("2");
//把兩個集合的 內容拼裝組合成爲一副牌
Map<Integer,String> PK = new HashMap<Integer,String>();
// 花色
int count = 1;
for(String num : shuZi){
// 數字
for(String hua : huaSe){
PK.put(count,hua+num);
count++;
}
}
PK.put(count++,"SJoker");
PK.put(count,"BJoker");
// System.out.println(PK);
// 洗牌, map的鍵的集合 牌的編號
Set<Integer> keySet = PK.keySet(); // (1 - 54 )
List<Integer> keys = new ArrayList<Integer>();
for(Integer key : keySet){
keys.add(key);
}
// 洗牌
Collections.shuffle(keys);
// 定義三位遊戲玩家 不保存牌面 只保存 每張牌的編號
List<Integer> caocao = new ArrayList<>();
List<Integer> liubei = new ArrayList<>();
List<Integer> sunquan = new ArrayList<>();
List<Integer> dipai = new ArrayList<>();
// 開始發牌
for(int i = 0 ; i < keys.size() ; i++){
if(i > 50){
dipai.add(keys.get(i));
continue;
}
if(i % 3 == 0){
caocao.add(keys.get(i));
}else if(i % 3 == 1){
liubei.add(keys.get(i));
}else{
sunquan.add(keys.get(i));
}
}
Collections.sort(caocao);
// 看牌
for(Integer i : caocao){
System.out.print(PK.get(i));
}
}
}
集合案例
使用集合優化點名程序
使用map集合 存儲多個班級信息
字符串每個字符出現的次數
使用map集合統計從鍵盤上輸入的字符串每個字符出現的次數