日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JAVA集合(笔记)

發布時間:2023/12/3 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JAVA集合(笔记) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

集合簡介

  • 概念:對象的容器,定義了對多個對象進項操作的的常用方法??蓪崿F數組的功能。
  • 和數組的區別
  • 數組長度固定,集合長度不固定。
  • 數組可以存儲基本類型和引用類型,集合只能存儲引用類型。
    • 位置: java.util.*;

    Collection體系集合


    Collection父接口


    • 特點:代表一組任意類型的對象,無序、無下標、不能重復。

    • 方法

      • boolean add(Object obj) //添加一個對象。
      • boolean addAll(Collection c) //講一個集合中的所有對象添加到此集合中。
      • void clear() //清空此集合中的所有對象。
      • boolean contains(Object o) //檢查此集合中是否包含o對象。
      • boolean equals(Object o) //比較此集合是否與指定對象相等。
      • boolean isEmpty() //判斷此集合是否為空。
      • boolean remove(Object o) //在此集合中移除o對象。
      • int size() //返回此集合中的元素個數。
      • Object[] toArray() //姜此集合轉換成數組。
      COPY/*** Collection接口的使用(一)* 1.添加元素* 2.刪除元素* 3.遍歷元素* 4.判斷*/ public class Demo1{pubic static void main(String[] args){//創建集合Collection collection=new ArrayList(); // * 1.添加元素Collection.add("蘋果");Collection.add("西瓜");Collection.add("榴蓮");System.out.println("元素個數:"+collection.size());System.out.println(collection); // * 2.刪除元素collection.remove("榴蓮");System.out.println("刪除之后:"+collection.size()); // * 3.遍歷元素//3.1 使用增強for for(Object object : collection){System.out.println(object);}//3.2 使用迭代器(迭代器專門用來遍歷集合的一種方式)//hasnext();判斷是否有下一個元素//next();獲取下一個元素//remove();刪除當前元素Iterator iterator=collection.Itertor();while(iterator.hasnext()){String object=(String)iterator.next();System.out.println(s);//刪除操作//collection.remove(s);引發錯誤:并發修改異常//iterator.remove();應使用迭代器的方法 // * 4.判斷System.out.println(collection.contains("西瓜"));//trueSystem.out.println(collection.isEmpty());//false}} } COPY/*** Collection接口的使用(二)* 1.添加元素* 2.刪除元素* 3.遍歷元素* 4.判斷*/ public class Demo2 {public static void main(String[] args) {Collection collection=new ArrayList();Student s1=new Student("張三",18);Student s2=new Student("李四", 20);Student s3=new Student("王五", 19);//1.添加數據collection.add(s1);collection.add(s2);collection.add(s3);//collection.add(s3);可重復添加相同對象System.out.println("元素個數:"+collection.size());System.out.println(collection.toString());//2.刪除數據collection.remove(s1);System.out.println("刪除之后:"+collection.size());//3.遍歷數據//3.1 增強forfor(Object object:collection) {Student student=(Student) object;System.out.println(student.toString());}//3.2迭代器//迭代過程中不能使用collection的刪除方法Iterator iterator=collection.iterator();while (iterator.hasNext()) {Student student=(Student) iterator.next();System.out.println(student.toString());}//4.判斷和上一塊代碼類似。} } COPY/*** 學生類*/ public class Student {private String name;private int age;public Student(String name, int age) {super();this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student [name=" + name + ", age=" + age +"]";} }

    Collection子接口


    List集合

    • 特點:有序、有下標、元素可以重復。

    • 方法

      • void add(int index,Object o) //在index位置插入對象o。
      • boolean addAll(index,Collection c) //將一個集合中的元素添加到此集合中的index位置。
      • Object get(int index) //返回集合中指定位置的元素。
      • List subList(int fromIndex,int toIndex) //返回fromIndex和toIndex之間的集合元素。
      COPY/*** List子接口的使用(一)* 特點:1.有序有下標 2.可以重復* * 1.添加元素* 2.刪除元素* 3.遍歷元素* 4.判斷* 5.獲取位置*/ public class Demo3 {public static void main(String[] args) {List list=new ArrayList<>();//1.添加元素list.add("tang");list.add("he");list.add(0,"yu");//插入操作System.out.println("元素個數:"+list.size());System.out.println(list.toString());//2.刪除元素list.remove(0);//list.remove("yu");結果同上System.out.println("刪除之后:"+list.size());System.out.println(list.toString());//3.遍歷元素//3.1 使用for遍歷for(int i=0;i<list.size();++i) {System.out.println(list.get(i));}//3.2 使用增強forfor(Object object:list) {System.out.println(object);}//3.3 使用迭代器Iterator iterator=list.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}//3.4使用列表迭代器,listIterator可以雙向遍歷,添加、刪除及修改元素。ListIterator listIterator=list.listIterator();//從前往后while (listIterator.hasNext()) {System.out.println(listIterator.next()); }//從后往前(此時“遍歷指針”已經指向末尾)while(listIterator.hasPrevious()) {System.out.println(listIterator.previous());}//4.判斷System.out.println(list.isEmpty());System.out.println(list.contains("tang"));//5.獲取位置System.out.println(list.indexOf("tang"));} } COPY/*** List子接口的使用(二)* 1.添加元素* 2.刪除元素* 3.遍歷元素* 4.判斷* 5.獲取位置*/ public class Demo4 {public static void main(String[] args) {List list=new ArrayList();//1.添加數字數據(自動裝箱)list.add(20);list.add(30);list.add(40);list.add(50);System.out.println("元素個數:"+list.size());System.out.println(list.toString());//2.刪除元素list.remove(0);//list.remove(20);很明顯數組越界錯誤,改成如下//list.remove(Object(20));//list.remove(new Integer(20));System.out.println("元素個數:"+list.size());System.out.println(list.toString());//3-5不再演示,與之前類似//6.補充方法subList,返回子集合,含頭不含尾List list2=list.subList(1, 3);System.out.println(list2.toString()); } }

    List實現類

    ArrayList[重點]

    • 數組結構實現,查詢塊、增刪慢;
    • JDK1.2版本,運行效率快、線程不安全。
    • [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cqGEjbf9-1614751593954)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20210228215642063.png)]
    COPY/*** ArrayList的使用* 存儲結構:數組;* 特點:查找遍歷速度快,增刪慢。* 1.添加元素* 2.刪除元素* 3.遍歷元素* 4.判斷* 5.查找*/ public class Demo5 {public static void main(String[] args) {ArrayList arrayList=new ArrayList<>();//1.添加元素Student s1=new Student("唐", 21);Student s2=new Student("何", 22);Student s3=new Student("余", 21);arrayList.add(s1);arrayList.add(s2);arrayList.add(s3);System.out.println("元素個數:"+arrayList.size());System.out.println(arrayList.toString());//2.刪除元素arrayList.remove(s1);//arrayList.remove(new Student("唐", 21));//注:這樣可以刪除嗎(不可以)?顯然這是兩個不同的對象。//假如兩個對象屬性相同便認為其是同一對象,那么如何修改代碼?//3.遍歷元素//3.1使用迭代器Iterator iterator=arrayList.iterator();while(iterator.hasNext()) {System.out.println(iterator.next());}//3.2使用列表迭代器ListIterator listIterator=arrayList.listIterator();//從前往后遍歷while(listIterator.hasNext()) {System.out.println(listIterator.next());}//從后往前遍歷while(listIterator.hasPrevious()) {System.out.println(listIterator.previous());}//4.判斷System.out.println(arrayList.isEmpty());//System.out.println(arrayList.contains(new Student("何", 22)));//注:與上文相同的問題。//5.查找System.out.println(arrayList.indexOf(s1));} }

    :Object里的equals(this==obj)用地址和當前對象比較,如果想實現代碼中的問題,可以在學生類中重寫equals方法:

    COPY@Override public boolean equals(Object obj) {//1.是否為同一對象if (this==obj) {return true;}//2.判斷是否為空if (obj==null) {return false;}//3.判斷是否是Student類型if (obj instanceof Student) {Student student=(Student) obj;//4.比較屬性if(this.name.equals(student.getName())&&this.age==student.age) {return true;}}//不滿足,返回falsereturn false; }

    ArrayList源碼分析

    • 默認容量大小:private static final int DEFAULT_CAPACITY = 10;

    • 存放元素的數組:transient Object[] elementData;

    • 實際元素個數:private int size;

    • 創建對象時調用的無參構造函數:

      COPY//這是一個空的數組 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }

      這段源碼說明當你沒有向集合中添加任何元素時,集合容量為0。那么默認的10個容量怎么來的呢?

      這就得看看add方法的源碼了:

      COPYpublic boolean add(E e) {ensureCapacityInternal(size + 1); // Increments modCount!!elementData[size++] = e;return true; }

      假設你new了一個數組,當前容量為0,size當然也為0。這時調用add方法進入到ensureCapacityInternal(size + 1);該方法源碼如下:

      COPYprivate void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }

      該方法中的參數minCapacity傳入的值為size+1也就是 1,接著我們再進入到calculateCapacity(elementData, minCapacity)里面:

      COPYprivate static int calculateCapacity(Object[] elementData, int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);}return minCapacity; }

      上文說過,elementData就是存放元素的數組,當前容量為0,if條件成立,返回默認容量DEFAULT_CAPACITY也就是10。這個值作為參數又傳入ensureExplicitCapacity()方法中,進入該方法查看源碼:

      COPYprivate void ensureExplicitCapacity(int minCapacity) {modCount++;// overflow-conscious codeif (minCapacity - elementData.length > 0)grow(minCapacity); }

      我們先不要管modCount這個變量。

      因為elementData數組長度為0,所以if條件成立,調用grow方法,重要的部分來了,我們再次進入到grow方法的源碼中:

      COPYprivate void grow(int minCapacity) {// overflow-conscious codeint 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); }

      這個方法先聲明了一個oldCapacity變量將數組長度賦給它,其值為0;又聲明了一個newCapacity變量其值為oldCapacity+一個增量,可以發現這個增量是和原數組長度有關的量,當然在這里也為0。第一個if條件滿足,newCapacity的值為10(這就是默認的容量,不理解的話再看看前面)。第二個if條件不成立,也可以不用注意,因為MAX_ARRAY_SIZE的定義如下:

      COPYprivate static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

      這個值太大了以至于第二個if條件沒有了解的必要。

      最后一句話就是為elementData數組賦予了新的長度,Arrays.copyOf()方法返回的數組是新的數組對象,原數組對象不會改變,該拷貝不會影響原來的數組。copyOf()的第二個自變量指定要建立的新數組長度,如果新數組的長度超過原數組的長度,則保留數組默認值。

      這時候再回到add的方法中,接著就向下執行elementData[size++] = e;到這里為止關于ArrayList就講解得差不多了,當數組長度為10的時候你們可以試著過一下源碼,查一下每次的增量是多少(答案是每次擴容為原來的1.5倍)。


    Vector

    • 數組結構實現,查詢快、增刪慢;

    • JDK1.0版本,運行效率慢、線程安全。

      COPY/*** Vector的演示使用* *1.添加數據*2.刪除數據*3.遍歷*4.判斷*/ public class Demo1 {public static void main(String[] args) {Vector vector=new Vector<>();//1.添加數據vector.add("tang");vector.add("he");vector.add("yu");System.out.println("元素個數:"+vector.size());//2.刪除數據/** vector.remove(0); vector.remove("tang");*///3.遍歷//使用枚舉器Enumeration enumeration=vector.elements();while (enumeration.hasMoreElements()) {String s = (String) enumeration.nextElement();System.out.println(s);}//4.判斷System.out.println(vector.isEmpty());System.out.println(vector.contains("he"));//5. Vector其他方法//firstElement() lastElement() ElementAt();} }

    LinkedList

    • 鏈表結構實現,增刪快,查詢慢。
    COPY/*** LinkedList的用法* 存儲結構:雙向鏈表* 1.添加元素* 2.刪除元素* 3.遍歷* 4.判斷*/ public class Demo2 {public static void main(String[] args) {LinkedList linkedList=new LinkedList<>();Student s1=new Student("唐", 21);Student s2=new Student("何", 22);Student s3=new Student("余", 21);//1.添加元素linkedList.add(s1);linkedList.add(s2);linkedList.add(s3);linkedList.add(s3);System.out.println("元素個數:"+linkedList.size());System.out.println(linkedList.toString());//2.刪除元素/** linkedList.remove(new Student("唐", 21));* System.out.println(linkedList.toString());*///3.遍歷//3.1 使用forfor(int i=0;i<linkedList.size();++i) {System.out.println(linkedList.get(i));}//3.2 使用增強forfor(Object object:linkedList) {Student student=(Student) object;System.out.println(student.toString());}//3.3 使用迭代器Iterator iterator =linkedList.iterator();while (iterator.hasNext()) {Student student = (Student) iterator.next();System.out.println(student.toString());}//3.4 使用列表迭代器(略)//4. 判斷System.out.println(linkedList.contains(s1));System.out.println(linkedList.isEmpty());System.out.println(linkedList.indexOf(s3));} }

    LinkedList源碼分析

    LinkedList首先有三個屬性:

    • 鏈表大小:transient int size = 0;
    • (指向)第一個結點/頭結點:transient Node<E> first;
    • (指向)最后一個結點/尾結點:transient Node<E> last;

    關于Node類型我們再進入到類里看看:

    COPYprivate static class Node<E> {E item;Node<E> next;Node<E> prev;Node(Node<E> prev, E element, Node<E> next) {this.item = element;this.next = next;this.prev = prev;} }

    首先item存放的是實際數據;next指向下一個結點而prev指向上一個結點。

    Node帶參構造方法的三個參數分別是前一個結點、存儲的數據、后一個結點,調用這個構造方法時將它們賦值給當前對象。

    LinkedList是如何添加元素的呢?先看看add方法:

    COPYpublic boolean add(E e) {linkLast(e);return true; }

    進入到linkLast方法:

    COPYvoid linkLast(E e) {final Node<E> l = last;final Node<E> newNode = new Node<>(l, e, null);last = newNode;if (l == null)first = newNode;elsel.next = newNode;size++;modCount++; }

    假設剛開始new了一個LinkedList對象,first和last屬性都為空,調用add進入到linkLast方法。

    首先創建一個Node變量 l 將last(此時為空)賦給它,然后new一個newNode變量存儲數據,并且它的前驅指向l,后繼指向null;再把last指向newNode。如下圖所示:

    如果滿足if條件,說明這是添加的第一個結點,將first指向newNode:

    至此,LinkedList對象的第一個數據添加完畢。假設需要再添加一個數據,我們可以再來走一遍,過程同上不再贅述,圖示如下:


    ArrayList和LinkedList區別

    • ArrayList:必須開辟連續空間,查詢快,增刪慢。
    • LinkedList:無需開辟連續空間,查詢慢,增刪快。


    泛型概述

    • Java泛型是JDK1.5中引入的一個新特性,其本質是參數化類型,把類型作為參數傳遞。
    • 常見形式有泛型類、泛型接口、泛型方法。
    • 語法:
      • <T,…> T稱為類型占位符,表示一種引用類型。
    • 好處:
      • 提高代碼的重用性。
      • 防止類型轉換異常,提高代碼的安全性。

    泛型類

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-JNlbroFK-1614751593971)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20210301155839838.png)]

    COPY/*** 泛型類* 語法:類名<T>* T是類型占位符,表示一種引用類型,編寫多個使用逗號隔開* */ public class myGeneric<T>{//1.創建泛型變量//不能使用new來創建,因為泛型是不確定的類型,也可能擁有私密的構造方法。T t;//2.泛型作為方法的參數public void show(T t) {System.out.println(t);}//泛型作為方法的返回值public T getT() {return t;} } COPY/*** 注意:* 1.泛型只能使用引用類型* 2.不同泛型類型的對象不能相互賦值*/ public class testGeneric {public static void main(String[] args) {//使用泛型類創建對象myGeneric<String> myGeneric1=new myGeneric<String>();myGeneric1.t="tang";myGeneric1.show("he");myGeneric<Integer> myGeneric2=new myGeneric<Integer>();myGeneric2.t=10;myGeneric2.show(20);Integer integer=myGeneric2.getT();} }

    泛型接口

    COPY/*** 泛型接口* 語法:接口名<T>* 注意:不能創建泛型靜態常量*/ public interface MyInterface<T> {//創建常量String nameString="tang";T server(T t); } COPY/*** 實現接口時確定泛型類*/ public class MyInterfaceImpl implements MyInterface<String>{@Overridepublic String server(String t) {System.out.println(t);return t; } } COPY//測試 MyInterfaceImpl myInterfaceImpl=new MyInterfaceImpl(); myInterfaceImpl.server("xxx"); //xxx COPY/*** 實現接口時不確定泛型類*/ public class MyInterfaceImpl2<T> implements MyInterface<T>{@Overridepublic T server(T t) {System.out.println(t);return t;} } COPY//測試 MyInterfaceImpl2<Integer> myInterfaceImpl2=new MyInterfaceImpl2<Integer>(); myInterfaceImpl2.server(2000); //2000

    泛型方法

    COPY/*** 泛型方法* 語法:<T> 返回類型*/ public class MyGenericMethod {public <T> void show(T t) {System.out.println("泛型方法"+t);} } COPY//測試 MyGenericMethod myGenericMethod=new MyGenericMethod(); myGenericMethod.show("tang"); myGenericMethod.show(200); myGenericMethod.show(3.14);

    泛型集合

    • 概念:參數化類型、類型安全的集合,強制集合元素的類型必須一致。

    • 特點

      • 編譯時即可檢查,而非運行時拋出異常。
      • 訪問時,不必類型轉換(拆箱)。
      • 不同泛型指尖引用不能相互賦值,泛型不存在多態。

    之前我們在創建LinkedList類型對象的時候并沒有使用泛型,但是進到它的源碼中會發現:

    COPYpublic class LinkedList<E>extends AbstractSequentialList<E>implements List<E>, Deque<E>, Cloneable, java.io.Serializable{//略}

    它是一個泛型類,而我之前使用的時候并沒有傳遞,說明java語法是允許的,這個時候傳遞的類型是Object類,雖然它是所有類的父類,可以存儲任意的類型,但是在遍歷、獲取元素時需要原來的類型就要進行強制轉換。這個時候就會出現一些問題,假如往鏈表里存儲了許多不同類型的數據,在強轉的時候就要判斷每一個原來的類型,這樣就很容易出現錯誤。


    Set集合概述

    Set子接口

    • 特點:無序、無下標、元素不可重復。
    • 方法:全部繼承自Collection中的方法。
    COPY/*** 測試Set接口的使用* 特點:1.無序,沒有下標;2.重復* 1.添加數據* 2.刪除數據* 3.遍歷【重點】* 4.判斷*/ public class Demo1 {public static void main(String[] args) {Set<String> set=new HashSet<String>();//1.添加數據set.add("tang");set.add("he");set.add("yu");System.out.println("數據個數:"+set.size());System.out.println(set.toString());//無序輸出//2.刪除數據/** set.remove("tang"); System.out.println(set.toString());*///3.遍歷【重點】//3.1 使用增強forfor (String string : set) {System.out.println(string);}//3.2 使用迭代器Iterator<String> iterator=set.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}//4.判斷System.out.println(set.contains("tang"));System.out.println(set.isEmpty());} }

    Set實現類

    HashSet【重點】

    • 基于HashCode計算元素存放位置。
    • 當存入元素的哈希碼相同時,會調用equals進行確認,如結果為true,則拒絕后者存入。
    COPY/*** 人類*/ public class Person {private String name;private int age;public Person(String name,int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Peerson [name=" + name + ", age=" + age + "]";} } COPY/*** HashSet集合的使用* 存儲結構:哈希表(數組+鏈表+紅黑樹)* 1.添加元素* 2.刪除元素* 3.遍歷* 4.判斷 */ public class Demo3 {public static void main(String[] args) {HashSet<Person> hashSet=new HashSet<>();Person p1=new Person("tang",21);Person p2=new Person("he", 22);Person p3=new Person("yu", 21);//1.添加元素hashSet.add(p1);hashSet.add(p2);hashSet.add(p3);//重復,添加失敗hashSet.add(p3);//直接new一個相同屬性的對象,依然會被添加,不難理解。//假如相同屬性便認為是同一個對象,怎么修改?hashSet.add(new Person("yu", 21));System.out.println(hashSet.toString());//2.刪除元素hashSet.remove(p2);//3.遍歷//3.1 增強forfor (Person person : hashSet) {System.out.println(person);}//3.2 迭代器Iterator<Person> iterator=hashSet.iterator();while (iterator.hasNext()) {System.out.println(iterator.next()); }//4.判斷System.out.println(hashSet.isEmpty());//直接new一個相同屬性的對象結果輸出是false,不難理解。//注:假如相同屬性便認為是同一個對象,該怎么做?System.out.println(hashSet.contains(new Person("tang", 21)));} }

    :hashSet存儲過程:

  • 根據hashCode計算保存的位置,如果位置為空,則直接保存,否則執行第二步。
  • 執行equals方法,如果方法返回true,則認為是重復,拒絕存儲,否則形成鏈表。
  • 存儲過程實際上就是重復依據,要實現“注”里的問題,可以重寫hashCode和equals代碼:

    COPY@Override public int hashCode() {final int prime = 31;int result = 1;result = prime * result + age;result = prime * result + ((name == null) ? 0 : name.hashCode());return result; } @Override public boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;Person other = (Person) obj;if (age != other.age)return false;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;return true; }

    hashCode方法里為什么要使用31這個數字大概有兩個原因:

  • 31是一個質數,這樣的數字在計算時可以盡量減少散列沖突。
  • 可以提高執行效率,因為31*i=(i<<5)-i,31乘以一個數可以轉換成移位操作,這樣能快一點;但是也有網上一些人對這兩點提出質疑。

  • TreeSet

    • 基于排序順序實現不重復。
    • 實現了SortedSet接口,對集合元素自動排序。
    • 元素對象的類型必須實現Comparable接口,指定排序規則。
    • 通過CompareTo方法確定是否為重復元素。
    COPY/*** 使用TreeSet保存數據* 存儲結構:紅黑樹* 要求:元素類必須實現Comparable接口,compareTo方法返回0,認為是重復元素 */ public class Demo4 {public static void main(String[] args) {TreeSet<Person> persons=new TreeSet<Person>();Person p1=new Person("tang",21);Person p2=new Person("he", 22);Person p3=new Person("yu", 21);//1.添加元素persons.add(p1);persons.add(p2);persons.add(p3);//注:直接添加會報類型轉換錯誤,需要實現Comparable接口System.out.println(persons.toString());//2.刪除元素persons.remove(p1);persons.remove(new Person("he", 22));System.out.println(persons.toString());//3.遍歷(略)//4.判斷System.out.println(persons.contains(new Person("yu", 21)));} }

    查看Comparable接口的源碼,發現只有一個compareTo抽象方法,在人類中實現它:

    COPYpublic class Person implements Comparable<Person>{@Override//1.先按姓名比//2.再按年齡比public int compareTo(Person o) {int n1=this.getName().compareTo(o.getName());int n2=this.age-o.getAge();return n1==0?n2:n1;} }

    除了實現Comparable接口里的比較方法,TreeSet也提供了一個帶比較器Comparator的構造方法,使用匿名內部類來實現它:

    COPY/*** TreeSet的使用* Comparator:實現定制比較(比較器)*/ public class Demo5 {public static void main(String[] args) {TreeSet<Person> persons=new TreeSet<Person>(new Comparator<Person>() {@Overridepublic int compare(Person o1, Person o2) {// 先按年齡比較// 再按姓名比較int n1=o1.getAge()-o2.getAge();int n2=o1.getName().compareTo(o2.getName());return n1==0?n2:n1;} });Person p1=new Person("tang",21);Person p2=new Person("he", 22);Person p3=new Person("yu", 21);persons.add(p1);persons.add(p2);persons.add(p3);System.out.println(persons.toString());} }

    接下來我們來做一個小案例:

    COPY/*** 要求:使用TreeSet集合實現字符串按照長度進行排序* helloworld tangrui hechengyang wangzixu yuguoming* Comparator接口實現定制比較*/ public class Demo6 {public static void main(String[] args) {TreeSet<String> treeSet=new TreeSet<String>(new Comparator<String>() {@Override//先比較字符串長度//再比較字符串public int compare(String o1, String o2) {int n1=o1.length()-o2.length();int n2=o1.compareTo(o2);return n1==0?n2:n1;} });treeSet.add("helloworld");treeSet.add("tangrui");treeSet.add("hechenyang");treeSet.add("yuguoming");treeSet.add("wangzixu");System.out.println(treeSet.toString());//輸出[tangrui, wangzixu, yuguoming, hechenyang, helloworld]} }

    Map集合概述

    Map體系集合

    • Map接口的特點:

    • 用于存儲任意鍵值對(Key-Value)。
    • 鍵:無序、無下標、不允許重復(唯一)。
    • 值:無序、無下標、允許重復。
    • 特點:存儲一對數據(Key-Value),無序、無下標,鍵不可重復。

    • 方法

      • V put(K key,V value)//將對象存入到集合中,關聯鍵值。key重復則覆蓋原值。

      • Object get(Object key)//根據鍵獲取相應的值。

      • Set<K>//返回所有的key

      • Collection<V> values()//返回包含所有值的Collection集合。

      • Set<Map.Entry<K,V>>//鍵值匹配的set集合

      COPY/*** Map接口的使用* 特點:1.存儲鍵值對 2.鍵不能重復,值可以重復 3.無序*/ public class Demo1 {public static void main(String[] args) {Map<String,Integer> map=new HashMap<String, Integer>();//1.添加元素map.put("tang", 21);map.put("he", 22);map.put("fan", 23);System.out.println(map.toString());//2.刪除元素map.remove("he");System.out.println(map.toString());//3.遍歷//3.1 使用keySet();for (String key : map.keySet()) {System.out.println(key+" "+map.get(key));}//3.2 使用entrySet();效率較高for (Map.Entry<String, Integer> entry : map.entrySet()) {System.out.println(entry.getKey()+" "+entry.getValue());}} }

    Map集合的實現類

    HashMap【重點】

    • JDK1.2版本,線程不安全,運行效率快;允許用null作為key或是value。

      COPY/*** 學生類*/public class Student {private String name;private int id; public Student(String name, int id) {super();this.name = name;this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getId() {return id;}public void setId(int id) {this.id = id;}@Overridepublic String toString() {return "Student [name=" + name + ", age=" + id + "]";}} COPY/*** HashMap的使用* 存儲結構:哈希表(數組+鏈表+紅黑樹)*/public class Demo2 {public static void main(String[] args) {HashMap<Student, String> hashMap=new HashMap<Student, String>();Student s1=new Student("tang", 36);Student s2=new Student("yu", 101);Student s3=new Student("he", 10);//1.添加元素hashMap.put(s1, "成都");hashMap.put(s2, "杭州");hashMap.put(s3, "鄭州");//添加失敗,但會更新值hashMap.put(s3,"上海");//添加成功,不過兩個屬性一模一樣;//注:假如相同屬性便認為是同一個對象,怎么修改?hashMap.put(new Student("he", 10),"上海");System.out.println(hashMap.toString());//2.刪除元素hashMap.remove(s3);System.out.println(hashMap.toString());//3.遍歷//3.1 使用keySet()遍歷for (Student key : hashMap.keySet()) {System.out.println(key+" "+hashMap.get(key));}//3.2 使用entrySet()遍歷for (Entry<Student, String> entry : hashMap.entrySet()) {System.out.println(entry.getKey()+" "+entry.getValue());}//4.判斷//注:同上System.out.println(hashMap.containsKey(new Student("he", 10)));System.out.println(hashMap.containsValue("成都"));}}

      注:和之前說過的HashSet類似,重復依據是hashCode和equals方法,重寫即可:

      COPY@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + id;result = prime * result + ((name == null) ? 0 : name.hashCode());return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;Student other = (Student) obj;if (id != other.id)return false;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;return true;}

      HashMap源碼分析

    • 默認初始化容量:static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

      • 數組最大容量:static final int MAXIMUM_CAPACITY = 1 << 30;
    • 默認加載因子:static final float DEFAULT_LOAD_FACTOR = 0.75f;

    • 鏈表調整為紅黑樹的鏈表長度閾值(JDK1.8):static final int TREEIFY_THRESHOLD = 8;

    • 紅黑樹調整為鏈表的鏈表長度閾值(JDK1.8):static final int UNTREEIFY_THRESHOLD = 6;

    • 鏈表調整為紅黑樹的數組最小閾值(JDK1.8):static final int MIN_TREEIFY_CAPACITY = 64;

    • HashMap存儲的數組:transient Node<K,V>[] table;

    • HashMap存儲的元素個數:transient int size;

      • 默認加載因子是什么?
        • 就是判斷數組是否擴容的一個因子。假如數組容量為100,如果HashMap的存儲元素個數超過了100*0.75=75,那么就會進行擴容。
      • 鏈表調整為紅黑樹的鏈表長度閾值是什么?
        • 假設在數組中下標為3的位置已經存儲了數據,當新增數據時通過哈希碼得到的存儲位置又是3,那么就會在該位置形成一個鏈表,當鏈表過長時就會轉換成紅黑樹以提高執行效率,這個閾值就是鏈表轉換成紅黑樹的最短鏈表長度;
      • 紅黑樹調整為鏈表的鏈表長度閾值是什么?
        • 當紅黑樹的元素個數小于該閾值時就會轉換成鏈表。
      • 鏈表調整為紅黑樹的數組最小閾值是什么?
        • 并不是只要鏈表長度大于8就可以轉換成紅黑樹,在前者條件成立的情況下,數組的容量必須大于等于64才會進行轉換。

      HashMap的數組table存儲的就是一個個的Node<K,V>類型,很清晰地看到有一對鍵值,還有一個指向next的指針(以下只截取了部分源碼):

      COPYstatic class Node<K,V> implements Map.Entry<K,V> {final K key;V value;Node<K,V> next;}

      之前的代碼中在new對象時調用的是HashMap的無參構造方法,進入到該構造方法的源碼查看一下:

      COPYpublic HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}

      發現沒什么內容,只是賦值了一個默認加載因子;而在上文我們觀察到源碼中table和size都沒有賦予初始值,說明剛創建的HashMap對象沒有分配容量,并不擁有默認的16個空間大小,這樣做的目的是為了節約空間,此時table為null,size為0。

      當我們往對象里添加元素時調用put方法:

      COPYpublic V put(K key, V value) {return putVal(hash(key), key, value, false, true);}

      put方法把key和value傳給了putVal,同時還傳入了一個hash(Key)所返回的值,這是一個產生哈希值的方法,再進入到putVal方法(部分源碼):

      COPYfinal V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else{//略}}

      這里面創建了一個tab數組和一個Node變量p,第一個if實際是判斷table是否為空,而我們現在只關注剛創建HashMap對象時的狀態,此時tab和table都為空,滿足條件,執行內部代碼,這條代碼其實就是把resize()所返回的結果賦給tab,n就是tab的長度,resize顧名思義就是重新調整大小。查看resize()源碼(部分):

      COPYfinal Node<K,V>[] resize() {Node<K,V>[] oldTab = table;int oldCap = (oldTab == null) ? 0 : oldTab.length;int oldThr = threshold;if (oldCap > 0);else if (oldThr > 0);else { // zero initial threshold signifies using defaultsnewCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);} @SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];table = newTab;return newTab;}

      該方法首先把table及其長度賦值給oldTab和oldCap;threshold是閾值的意思,此時為0,所以前兩個if先不管,最后else里newCap的值為默認初始化容量16;往下創建了一個newCap大小的數組并將其賦給了table,剛創建的HashMap對象就在這里獲得了初始容量。然后我們再回到putVal方法,第二個if就是根據哈希碼得到的tab中的一個位置是否為空,為空便直接添加元素,此時數組中無元素所以直接添加。至此HashMap對象就完成了第一個元素的添加。當添加的元素超過16*0.75=12時,就會進行擴容:

      COPYfinal V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict){if (++size > threshold)resize();}

      擴容的代碼如下(部分):

      COPYfinal Node<K,V>[] resize() {int oldCap = (oldTab == null) ? 0 : oldTab.length;int newCap;if (oldCap > 0) {if (oldCap >= MAXIMUM_CAPACITY) {//略}else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)}}

      核心部分是else if里的移位操作,也就是說每次擴容都是原來大小的兩倍

    • 注*:額外說明的一點是在JDK1.8以前鏈表是頭插入,JDK1.8以后鏈表是尾插入。


    HashSet源碼分析

    了解完HashMap之后,再回過頭來看之前的HashSet源碼,為什么放在后面寫你們看一下源碼就知道了(部分):

    COPYpublic class HashSet<E>extends AbstractSet<E>implements Set<E>, Cloneable, java.io.Serializable{private transient HashMap<E,Object> map;private static final Object PRESENT = new Object();public HashSet() {map = new HashMap<>();}}

    可以看見HashSet的存儲結構就是HashMap,那它的存儲方式是怎樣的呢?可以看一下add方法:

    COPYpublic boolean add(E e) {return map.put(e, PRESENT)==null;}

    很明了地發現它的add方法調用的就是map的put方法,把元素作為map的key傳進去的。。

    Hashtable

    • JDK1.0版本,線程安全,運行效率慢;不允許null作為key或是value。

    • 初始容量11,加載因子0.75。

      這個集合在開發過程中已經不用了,稍微了解即可。

    Properties

    • Hashtable的子類,要求key和value都是String。通常用于配置文件的讀取。

    它繼承了Hashtable的方法,與流關系密切,此處不詳解。

    TreeMap

    • 實現了SortedMap接口(是Map的子接口),可以對key自動排序。
    COPY/*** TreeMap的使用* 存儲結構:紅黑樹*/ public class Demo3 {public static void main(String[] args) {TreeMap<Student, Integer> treeMap=new TreeMap<Student, Integer>();Student s1=new Student("tang", 36);Student s2=new Student("yu", 101);Student s3=new Student("he", 10);//1.添加元素treeMap.put(s1, 21);treeMap.put(s2, 22);treeMap.put(s3, 21);//不能直接打印,需要實現Comparable接口,因為紅黑樹需要比較大小System.out.println(treeMap.toString());//2.刪除元素treeMap.remove(new Student("he", 10));System.out.println(treeMap.toString());//3.遍歷//3.1 使用keySet()for (Student key : treeMap.keySet()) {System.out.println(key+" "+treeMap.get(key));}//3.2 使用entrySet()for (Entry<Student, Integer> entry : treeMap.entrySet()) {System.out.println(entry.getKey()+" "+entry.getValue());}//4.判斷System.out.println(treeMap.containsKey(s1));System.out.println(treeMap.isEmpty()); } }

    在學生類中實現Comparable接口:

    COPYpublic class Student implements Comparable<Student>{@Overridepublic int compareTo(Student o) {int n1=this.id-o.id;return n1; }

    除此之外還可以使用比較器來定制比較:

    COPYTreeMap<Student, Integer> treeMap2=new TreeMap<Student, Integer>(new Comparator<Student>() {@Overridepublic int compare(Student o1, Student o2) {// 略return 0;} });

    TreeSet源碼

    和HashSet類似,放在TreeMap之后講便一目了然(部分):

    COPYpublic class TreeSet<E> extends AbstractSet<E>implements NavigableSet<E>, Cloneable, java.io.Serializable {private transient NavigableMap<E,Object> m;private static final Object PRESENT = new Object();TreeSet(NavigableMap<E,Object> m) {this.m = m;}public TreeSet() {this(new TreeMap<E,Object>());} }

    TreeSet的存儲結構實際上就是TreeMap,再來看其存儲方式:

    COPYpublic boolean add(E e) {return m.put(e, PRESENT)==null; }

    它的add方法調用的就是TreeMap的put方法,將元素作為key傳入到存儲結構中。


    Collections工具類

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-iJZH8Shy-1614751593973)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20210303135614014.png)]

    • 概念:集合工具類,定義了除了存取以外的集合常用方法。

    • 方法

      • public static void reverse(List<?> list)//反轉集合中元素的順序
      • public static void shuffle(List<?> list)//隨機重置集合元素的順序
      • public static void sort(List<T> list)//升序排序(元素類型必須實現Comparable接口)
      COPY/*** 演示Collections工具類的使用**/ public class Demo4 {public static void main(String[] args) {List<Integer> list=new ArrayList<Integer>();list.add(20);list.add(10);list.add(30);list.add(90);list.add(70);//sort排序System.out.println(list.toString());Collections.sort(list);System.out.println(list.toString());System.out.println("---------");//binarySearch二分查找int i=Collections.binarySearch(list, 10);System.out.println(i);//copy復制List<Integer> list2=new ArrayList<Integer>();for(int i1=0;i1<5;++i1) {list2.add(0);}//該方法要求目標元素容量大于等于源目標Collections.copy(list2, list);System.out.println(list2.toString());//reserve反轉Collections.reverse(list2);System.out.println(list2.toString());//shuffle 打亂Collections.shuffle(list2);System.out.println(list2.toString());//補充:list轉成數組Integer[] arr=list.toArray(new Integer[0]);System.out.println(arr.length);//補充:數組轉成集合 String[] nameStrings= {"tang","he","yu"};//受限集合,不能添加和刪除List<String> list3=Arrays.asList(nameStrings);System.out.println(list3);//注:基本類型轉成集合時需要修改為包裝類} }

    總結

    以上是生活随笔為你收集整理的JAVA集合(笔记)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。