文章目錄
泛型
- 泛型的本質就是“數據類型的參數化”。 我們可以把“泛型”理解爲數據類型的一個佔位符(形式參數),即告訴編譯器,在調用泛型時必須傳入實際類型
- 泛型參數用一對尖括號<E>表示,使用時需要將類型參數替換爲實際的參數
public class TestGeneric {
public static void main(String[] args) {
MyCollection<String> s= new MyCollection<String>();
s.set("宋江",0);
s.set("盧俊義",1);
s.set("吳用",2);
String a = s.get(1);
System.out.println(a);
}
}
//泛型類
class MyCollection<E>{ //E表示泛型
Object[] objs = new Object[5];
public void set(E obj,int index){
objs[index] = obj;
}
public E get(int index){
return (E)objs[index];
}
}
Collection的常用方法
- 容器相關類都定義了泛型,我們在開發和工作中,在使用容器類時都要使用泛型。這樣,在容器的存儲數據、讀取數據時都避免了大量的類型判斷。使用容器時要在尖括號中指定類型
- Collection接口的常用方法,方法帶All的時候其參數是另一個Collection
import java.util.ArrayList;
import java.util.Collection;
public class TestList {
public static void main(String[] args) {
Collection<String> list = new ArrayList(); //[]
System.out.println("元素個數爲:"+list.size()); //元素個數
System.out.println("是否爲空:"+list.isEmpty());//是否爲空
list.add("唐三藏"); //添加元素
list.add("孫悟空");
list.add("豬八戒");
System.out.println("元素個數爲:"+list.size());
System.out.println(list);//[唐三藏, 孫悟空, 豬八戒]
list.remove("孫悟空"); //移除元素,但是元素還在內存中
System.out.println(list);
System.out.println(list.contains("豬八戒")); //是否包含
Object[] objs = list.toArray(); //轉換出Object數組
list.clear(); //移除所有元素
System.out.println(list);
//兩個Collection之間的操作
Collection<String> list1 = new ArrayList<>();
Collection<String> list2 = new ArrayList<>();
list1.add("孫悟空");
list1.add("楊戩");
list1.add("哪吒");
list2.add("楊戩");
list2.add("玉鼎真人");
list2.add("元始天尊");
//list1.addAll(list2); //兩個容器內容串起來,[孫悟空, 楊戩, 哪吒, 楊戩, 玉鼎真人, 元始天尊]
//list1.removeAll(list2);//移除list1中相交的元素[孫悟空, 哪吒]
list1.containsAll(list2); //是否包含list2的所有元素
list1.retainAll(list2); //保留相交元素[楊戩]
System.out.println(list1);
}
}
List
- 有序:List中每個元素都有索引標記。可以根據元素的索引標記(在List中的位置)訪問元素,從而精確控制這些元素。
可重複:List允許加入重複的元素。更確切地講,List通常允許滿足 e1.equals(e2) 的元素重複加入容器。 - List常用的ArrayList,LinkedList,Vector。ArrayList底層用數組實現。import java.util.List;
- 與索引相關的操作,插入,移除,修改,獲取,
List<Integer> a = new ArrayList<>();
a.add(100);
a.add(200);
a.add(300);
a.add(400);
System.out.println(a);
//指定位置插入元素
a.add(2,700);
System.out.println(a); //100, 200, 700, 300, 400]
//移除指定位置的元素
a.remove(2);
System.out.println(a); //[100, 200, 300, 400]
//修改指定位置的元素
a.set(2,800);
System.out.println(a); //[100, 200, 800, 400]
//獲取元素
System.out.println(a.get(2)); //800
- 獲取元素的索引,indexOf第一次出現的位置,lastIndexOf最後一次出現的位置
a.add(200);
System.out.println(a);//[100, 200, 800, 400, 200]
System.out.println(a.indexOf(200)); //1
System.out.println(a.lastIndexOf(200));//4
ArrayList
- ArrayList底層是用數組實現的存儲。 特點:查詢效率高,增刪效率低,線程不安全。如果頻繁增刪,使用LinkedList
- ArrayList的長度不受限制,動態擴容實現。初始化時可以使用默認長度。
- 動態擴容的源碼如下,右移一位表示除以2。擴容爲原來的1.5倍
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
- 自定義實現ArrayList的一些功能,size,isEmpty,add,get,set,remove,toString
package MyPro09;
public class MyArrayList<E> {
private Object[] elementData;
private int size;
private static final int DEFAULT_CAPACITY = 10;
//構造器
public MyArrayList(){
elementData = new Object[DEFAULT_CAPACITY];
}
public MyArrayList(int capacity){
if(capacity<0){
throw new RuntimeException("capity is less than zero");
}else if(capacity==0){
elementData = new Object[DEFAULT_CAPACITY];
}else{
elementData = new Object[capacity];
}
}
//獲取元素個數的方法size
public int size(){
return size;
}
//是否爲空
public boolean isEmpty(){
return size == 0 ? true : false;
}
//add方法
public void add(E element){
//判斷是否需要擴容,需要的話就擴容
if(size == elementData.length){
//新數組變爲1.5倍長度
Object[] newArray = new Object[elementData.length + (elementData.length >> 1)];
System.arraycopy(elementData,0,newArray,0,elementData.length);
//修改引用地址
elementData = newArray;
}
elementData[size++] = element;
}
//檢查索引
private void checkRange(int index){
if(index < 0 || index >= size){
throw new RuntimeException("Index Out Of Bound..");
}
}
//get
public E get(int index){
checkRange(index);
return (E)elementData[index];
}
//set
public void set(E element,int index){
checkRange(index);
elementData[index] = element;
}
//remove,一個可以給定索引或元素
public void remove(int index){
//數組的拷貝,將索引位置之後的元素拷貝到本身
int numMoved = elementData.length-index-1;
if(numMoved>1){
System.arraycopy(elementData,index+1,
elementData,index,numMoved);
}
//將末尾位置的元素設置爲null,同時size減去1
elementData[--size] = null;
}
//移除給定元素,E 不能是基本數據類型,不會和index衝突
public void remove(E element){
for(int i = 0;i<size;i++){
//容器中的比較用equals方法,不要用兩個等號==
if(element.equals(get(i))){
remove(i); //調用移除index的方法
}
}
}
//toString方法
public String toString(){
if(size == 0){
return "[]";
}
StringBuilder sb = new StringBuilder();
sb.append("[");
for(int i = 0;i<size;i++){
sb.append(elementData[i]+",");
}
sb.setCharAt(sb.length()-1,']');
return sb.toString();
}
}
LinkedList
- LinkedList底層用雙向鏈表實現的存儲。特點:查詢效率低,增刪效率高,線程不安全。
- 雙向鏈表也叫雙鏈表,是鏈表的一種,它的每個數據節點中都有兩個指針,分別指向前一個節點和後一個節點。 所以,從雙向鏈表中的任意一個節點開始,都可以很方便地找到所有節點
- 自定義實現LinkedList
public class MyLinkedList<E> {
public static void main(String[] args) {
MyLinkedList<String> list = new MyLinkedList();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
System.out.println(list);
System.out.println(list.get(2));
list.remove(5);
System.out.println(list);
list.add(2,"haha");
System.out.println(list);
}
private Node first;
private Node last;
private int size;
public void add(E element){
Node node = new Node(element);
//如果開頭是個空list,那麼fist和last都是node自己
if(first == null){
first = node;
last = node;
}else{
//如果已經有存在的list
node.previous = last; //前一個節點是已存在的最後一個節點
node.next = null; //下一個節點是空,因爲是插入到最後
last.next = node; //last的next設置爲當前
last = node; //last設置爲node
}
size++;
}
//insert
public void add(int index,E obj){
checkRange(index);
Node newNode = new Node(obj);
Node temp = getNode(index);
if(temp != null){
Node up = temp.previous;
up.next = newNode;
newNode.previous = up;
newNode.next = temp;
temp.previous = newNode;
}
}
public E get(int index){
checkRange(index);
Node temp = getNode(index);
return temp != null ? (E)temp.element : null;
}
//remove
public void remove(int index){
checkRange(index);
Node temp = getNode(index);
if(temp != null){
Node up = temp.previous;
Node down = temp.next;
if(up != null){
up.next = down;
}
if(down != null){
down.previous = up;
}
if(index == 0){
first = down;
}
if(index == size - 1){
last = up;
}
size--;
}
}
//根據索引獲取節點,便於重複使用
public Node getNode(int index){
Node temp = first;
if(index <(size>>1)){
//從前往後遍歷
for(int i = 0;i<index;i++){
temp = temp.next;
}
}else{
temp = last;
//從後往前遍歷
for(int i = size-1;i>index;i--){
temp = temp.previous;
}
}
return temp;
}
@Override
public String toString() {
if(first == null){
return "[]";
}
StringBuilder sb = new StringBuilder("[");
Node temp = first;
while (temp != null){
//System.out.println(temp.element);
sb.append(temp.element+",");
temp = temp.next;
}
sb.setCharAt(sb.length()-1,']');
return sb.toString();
}
//檢查索引
private void checkRange(int index){
if(index < 0 || index >= size){
throw new RuntimeException("Index Out Of Bound..");
}
}
}
class Node {
Node previous; //上一個節點
Node next; //下一個節點
Object element; //數據
//構造器
public Node(Object element) {
this.element = element;
}
public Node(Node previous, Node next, Object element) {
this.previous = previous;
this.next = next;
this.element = element;
}
}
Vector
- Vector底層是用數組實現的List,相關的方法都加了同步檢查,因此“線程安全,效率低”。 比如,indexOf方法就增加了synchronized同步標記。線程安全的ArrayList
Map
- Map就是用來存儲“鍵(key)-值(value) 對”的。 Map類中存儲的“鍵值對”通過鍵來標識
- Map 接口的實現類有HashMap、TreeMap、HashTable、Properties等
- 常用方法
package MyPro09;
import java.util.HashMap;
import java.util.Map;
public class TestMap {
public static void main(String[] args) {
Map<Integer,String> m1 = new HashMap<>();
m1.put(1,"宋江");
m1.put(2,"盧俊義");
System.out.println(m1);
System.out.println(m1.get(2)); //獲取鍵對應的value
System.out.println("元素個數:"+m1.size());
System.out.println("是否爲空:"+m1.isEmpty());
System.out.println("是否包含某個key:"+m1.containsKey(1));
System.out.println("是否包含某個value:"+m1.containsValue("呼延灼"));
Map<Integer,String> m2 = new HashMap<>();
m2.put(3,"林沖");
m2.put(4,"公孫勝");
m1.putAll(m2);
System.out.println(m1);
m1.put(3,"吳用"); //鍵重複會更新value
System.out.println(m1);
}
}
- Map中存放自定義的對象
import java.util.HashMap;
import java.util.Map;
public class TestMap2 {
public static void main(String[] args) {
Haohan h1 = new Haohan(1,"宋江");
Haohan h2 = new Haohan(1,"盧俊義");
Map<Integer,Haohan> s = new HashMap<>();
s.put(1,h1);
s.put(2,h2);
Haohan t1 = s.get(1);
System.out.println(t1);
}
}
class Haohan{
int id;
String name;
public Haohan(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return String.format("id:%s,name:%s",id,name);
}
}
HashMap與HashTable的區別
1. HashMap: 線程不安全,效率高。允許key或value爲null。
2. HashTable: 線程安全,效率低。不允許key或value爲null
HashMap底層
-
HashMap底層實現採用了哈希表,哈希表的基本結構就是“數組+鏈表”
-
https://www.sxt.cn/Java_jQuery_in_action/nine-hashmap-bottom.html
-
Entry[] table 就是HashMap的核心數組結構,我們也稱之爲“位桶數組”。
-
每一個Entry對象就是一個單向鏈表結構, 一個Entry對象存儲了:
- key:鍵對象 value:值對象
- next:下一個節點
- hash: 鍵對象的hash值
-
自定義實現
public class SxtHashMap04<K,V> {
Node3[] table; //位桶數組。bucket array
int size; //存放的鍵值對的個數
public SxtHashMap04() {
table = new Node3[16]; //長度一般定義成2的整數冪
}
public V get(K key){
int hash = myHash(key.hashCode(), table.length);
V value = null;
if(table[hash]!=null){
Node3 temp = table[hash];
while(temp!=null){
if(temp.key.equals(key)){ //如果相等,則說明找到了鍵值對,返回相應的value
value = (V)temp.value;
break;
}else{
temp = temp.next;
}
}
}
return value;
}
public void put(K key, V value){
//如果要完善,還需要考慮數組擴容的問題!!!
//定義了新的節點對象
Node3 newNode = new Node3();
newNode.hash = myHash(key.hashCode(),table.length);
newNode.key = key;
newNode.value = value;
newNode.next = null;
Node3 temp = table[newNode.hash];
Node3 iterLast = null; //正在遍歷的最後一個元素
boolean keyRepeat = false;
if(temp==null){
//此處數組元素爲空,則直接將新節點放進去
table[newNode.hash] = newNode;
size++;
}else{
//此處數組元素不爲空。則遍歷對應鏈表。。
while(temp!=null){
//判斷key如果重複,則覆蓋
if(temp.key.equals(key)){
keyRepeat = true;
temp.value = value; //只是覆蓋value即可。其他的值(hash,key,next)保持不變。
break;
}else{
//key不重複,則遍歷下一個。
iterLast = temp;
temp = temp.next;
}
}
if(!keyRepeat){ //沒有發生key重複的情況,則添加到鏈表最後。
iterLast.next = newNode;
size++;
}
}
}
@Override
public String toString() {
//{10:aa,20:bb}
StringBuilder sb = new StringBuilder("{");
//遍歷bucket數組
for(int i=0;i<table.length;i++){
Node3 temp = table[i];
//遍歷鏈表
while(temp!=null){
sb.append(temp.key+":"+temp.value+",");
temp = temp.next;
}
}
sb.setCharAt(sb.length()-1, '}');
return sb.toString();
}
public static void main(String[] args) {
SxtHashMap04<Integer,String> m = new SxtHashMap04<>();
m.put(10, "aa");
m.put(20, "bb");
System.out.println(m.get(85));
}
public static int myHash(int v, int length){
// System.out.println("hash in myHash:"+(v&(length-1))); //直接位運算,效率高
// System.out.println("hash in myHash:"+(v%(length-1))); //取模運算,效率低
return v&(length-1);
}
}
class Node3<K,V> {
int hash;
K key;
V value;
Node3 next;
}
TreeMap
- 需要排序的時候用TreeMap,底層是紅黑二叉樹
- 實現不同對象的比較需要實現Comparable接口,實現compareTo方法,返回正數,0,負數,對應大於,等於,小於
import java.util.Map;
import java.util.TreeMap;
public class TestTreeMap {
public static void main(String[] args) {
Map<Integer,String> tm = new TreeMap<>();
tm.put(1,"one");
tm.put(2,"two");
tm.put(3,"three");
//按照key遞增的方式排序
for(Integer key:tm.keySet()){
System.out.println(key+"---"+tm.get(key));
}
Map<Emoloyee,String> tm2 = new TreeMap<>();
tm2.put(new Emoloyee(1,"a",400),"a");
tm2.put(new Emoloyee(2,"b",300),"b");
tm2.put(new Emoloyee(4,"c",200),"c");
tm2.put(new Emoloyee(3,"d",200),"d");
//按照salary遞增的方式
for(Emoloyee key:tm2.keySet()){
System.out.println(key+"---"+tm2.get(key));
}
}
}
//自定義類實現比較功能需要實現Comparable<T>接口
class Emoloyee implements Comparable<Emoloyee>{
int id;
String name;
double salary;
public Emoloyee(int id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
@Override
public int compareTo(Emoloyee o) {
//大於返回正數,等於返回0,小於返回負數
if(this.salary>o.salary){
return 1;
}else if(this.salary<o.salary){
return -1;
}else{
if(this.id>o.id){
return 1;
}else if(this.id < o.id){
return -1;
}else{
return 0;
}
}
}
@Override
public String toString() {
return "id:"+id+",salary:"+salary+",name:"+name;
}
}
Set
- Set接口繼承自Collection,Set接口中沒有新增方法,方法和Collection保持完全一致。我們在前面通過List學習的方法,在Set中仍然適用
- Set容器特點:無序、不可重複。無序指Set中的元素沒有索引,我們只能遍歷查找;不可重複指不允許加入重複的元素。更確切地講,新元素如果和Set中某個元素通過equals()方法對比爲true,則不能加入;甚至,Set中也只能放入一個null元素,不能多個。
- Set常用的實現類有:HashSet、TreeSet等,我們一般使用HashSet。
HashSet
- 基本使用
import java.util.HashSet;
import java.util.Set;
public class TestSet {
public static void main(String[] args) {
Set<Integer> s = new HashSet<>();
s.add(3);
s.add(2);
s.add(2);
System.out.println(s);
s.remove(2);
System.out.println(s);
s.isEmpty();
}
}
- HashSet是採用哈希算法實現,底層實際是用HashMap實現的(HashSet本質就是一個簡化版的HashMap。map爲HashMap<E,Object> map
- 查詢效率和增刪效率都比較高
- 看add()方法,發現增加一個元素說白了就是在map中增加一個鍵值對,鍵對象就是這個元素,值對象是名爲PRESENT的Object對象
- 自定義簡易的HashSet
import java.util.HashMap;
public class MyHashSet {
public static void main(String[] args) {
MyHashSet mmp = new MyHashSet();
mmp.add("d");
mmp.add("b");
mmp.add("c");
System.out.println(mmp);
}
HashMap map;
private static final Object PRESENT = new Object();
public MyHashSet(){
map = new HashMap();
}
public void add(Object o){
map.put(o,PRESENT);
}
public int size(){
return map.size();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for(Object key : map.keySet()){
sb.append(key+",");
}
sb.setCharAt(sb.length()-1,']');
return sb.toString();
}
}
TreeSet
- TreeSet底層實際是用TreeMap實現的,內部維持了一個簡化版的TreeMap,通過key來存儲Set的元素。 TreeSet內部需要對存儲的元素進行排序,因此,我們對應的類需要實現Comparable接口。這樣,才能根據compareTo()方法比較對象之間的大小,才能進行內部排序
import java.util.Set;
import java.util.TreeSet;
public class TestTreeSet {
public static void main(String[] args) {
Set<Integer> t = new TreeSet<>();
t.add(300);
t.add(200);
t.add(100);
System.out.println(t); //[100, 200, 300] 自動排好序
//Employee實現了Comparable接口,打印時會排序
Set<Emoloyee> e = new TreeSet<>();
e.add(new Emoloyee(1,"a",400));
e.add(new Emoloyee(2,"b",300));
e.add(new Emoloyee(4,"c",200));
e.add(new Emoloyee(3,"d",200));
System.out.println(e);
}
}
迭代器
遍歷List
public static void TestList(){
List<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
//while遍歷
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//for遍歷
for(Iterator<String> iter = list.iterator();iter.hasNext();){
String temp = iter.next();
System.out.println(temp);
}
}
遍歷Set
public static void TestSet(){
Set<String> s = new HashSet<>();
s.add("qq");
s.add("ee");
s.add("rr");
//while遍歷
Iterator<String> iterator = s.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//for遍歷
for(Iterator<String> iter = s.iterator();iter.hasNext();){
String temp = iter.next();
System.out.println(temp);
}
}
遍歷Map
//遍歷Map的方式1,直接獲取鍵值對
public static void TestMap1(){
Map<Integer,String> m = new HashMap<>();
m.put(1,"aa");
m.put(2,"bb");
m.put(3,"cc");
Set<Map.Entry<Integer,String>> entries = m.entrySet();
for(Iterator<Map.Entry<Integer,String>> iter = entries.iterator();iter.hasNext();){
Map.Entry<Integer,String> temp = iter.next();
System.out.println(temp.getKey()+"->"+temp.getValue());
}
}
//遍歷Map的方式2,先獲取鍵的集合,再遍歷
public static void TestMap2(){
Map<Integer,String> m = new HashMap<>();
m.put(1,"aa");
m.put(2,"bb");
m.put(3,"cc");
Set<Integer> keySet = m.keySet();
for(Iterator<Integer> iter = keySet.iterator() ;iter.hasNext();){
Integer temp = iter.next();
System.out.println(temp+"-->"+m.get(temp));
}
}
遍歷方式總結
- https://www.sxt.cn/Java_jQuery_in_action/nine-ergodicset.html
- List:通過索引for循環,增強for循環,for循環迭代器,while循環迭代器
- Set: 增強for循環,for循環迭代器,while循環迭代器
- Map: (1)先獲取key的Set,然後同Set的遍歷方式,(2)迭代器entrySet()直接獲取鍵值對
Collections工具類
- java.util.Collections 提供了對Set、List、Map進行排序、填充、查找元素的輔助方法
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TestCollections {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for(int i = 0;i<10;i++){
list.add("abc"+i);
}
System.out.println(list);
//打亂順序
Collections.shuffle(list);
System.out.println(list);
//反轉列表
Collections.reverse(list);
System.out.println(list);
//排序,從小到大,反過來的話再reverse一下
//自定義類需要繼承Comparable接口
Collections.sort(list);
System.out.println(list);
//查找,沒有查到就返回負數
System.out.println(Collections.binarySearch(list,"abc15"));
}
}
容器存儲表格
- 思路1,每行數據用map表示,表頭爲鍵,內容爲值,所有的map放到一個list中。
- 思路2,每行數據用一個類表示,類的屬性爲表頭,所有的類放到一個map或list中
- 思路2的實現
import java.util.*;
public class TestStoreTable {
public static void main(String[] args) {
User u1 = new User(1001,"A",100,"2020-01-20");
User u2 = new User(1002,"B",200,"2019-01-20");
User u3 = new User(1003,"C",300,"2018-01-20");
//數據存入list,使用Ctrl+D快速複製粘貼上一行
List<User> list = new ArrayList<>();
list.add(u1);
list.add(u2);
list.add(u3);
for(User u:list){
System.out.println(u);
}
System.out.println("--------------");
//數據存入map
Map<Integer,User> map = new HashMap<>();
map.put(u1.getId(),u1);
map.put(u2.getId(),u2);
map.put(u3.getId(),u3);
Set<Integer> keys = map.keySet();
for(Integer key:keys){
System.out.println(map.get(key));
}
}
}
class User {
/*
User類,javabean
*/
private int id;
private String name;
private double salary;
private String hiredate;
//一個完整的javabean。要有set和get方法,以及無參構造器!
public User() {
}
public User(int id, String name, double salary, String hiredate) {
super();
this.id = id;
this.name = name;
this.salary = salary;
this.hiredate = hiredate;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String getHiredate() {
return hiredate;
}
public void setHiredate(String hiredate) {
this.hiredate = hiredate;
}
@Override
public String toString() {
return "id:"+id+",name:"+name+",salary:"+salary+",hiredate:"+hiredate;
}
}