集合

一、集合概述

  1. 什么是集合?有什么用?

    数组其实就是一个集合。集合实际上就是一个容器。可以用来容纳其他类型的数据。

    在实际开发中,假设连接数据库,数数据库当中有10条记录,那么假设把这10条记录封装成10个java对象,然后将10个java对象放到某一个集合当中,将集合传到前端,然后遍历集合,将一个数据一个数据展出来。

  2. 集合中存储什么?

    集合当中不能存储基本数据类型,另外集合也不能直接存储Java对象,集合当中存储的都是java对象的内存地址。(或者说集合中存储的是引用。)

    list.add(100) // 自动装箱Integer

    **注意:**集合在java中本身就是一个容器,是一个对象。集合任何时候存储的都是“引用”。

  3. 不同集合对应不同数据结构

    在java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到了不同的数据结构当中。

    什么是数据结构?

    数据存储的结构就是数据结构。不同的数据结构,数据存储方式不同。例如:数组、二叉树、链表、哈希表…这些都是常见的数据结构。

    使用不同的集合等同于使用了不同的数据结构。

    new ArrayList(); 创建一个集合,底层是数组。
    new LinkedList(); 创建一个集合对象,底层是链表。
    new TreeSet(); 创建一个集合对象,底层是二叉树。

  4. 集合在java JDK中哪个包下?

    java.util.*;
    所有的集合类和集合接口都在java.util包下。

  5. 集合继承结构图

    (1)Collection继承结构图

(2)Map继承结构图

总结(所有的实现类):

  • ArrayList:底层是数组。

  • LinkedList:底层是双向链表。

  • Vector:底层是数组,线程安全的,效率较低,使用较少。

  • HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分。

  • TreeSet:底层是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分。

  • HashMap:底层是哈希表。

  • Hashtable:底层是哈希表,只不过是线程安全的,效率较低,使用较少。

  • Properties:是线程安全的,并且key和value只能存储字符串String。

  • TreeMap:底层是二叉树。TreeMap集合的key可以自动按照大小顺序排序。

List集合存储元素的特点:有序可重复。

有序:存进去的顺序和取出来的顺序相同,每一个元素都有下标。

可重复:存进去一个1,可以再存一个1。

Set(Map)集合存储元素的特点:无序不可重复。

SortedSet(SortedMap)集合存储元素的特点:无序不可重复。但是集合中的元素是可按照大小排序的。

Map集合的key就是一个Set集合。往Set集合中放数据,实际上放到了Map的key部分。

  1. 在java中集合分为两大类

    一类是单个方式存储元素:

    ​ 单个方式存储元素,这一类集合中超级父接口:java.util.Collection;

    一类是以键对的方式存储元素:

    ​ 以键对的方式存储元素,这一类集合中的超级父接口:java.util.Map;


二、Collection和Iterator

  1. Collection接口中常用方法(关于java.util.Collection接口中常用的方法。)

    (1)1、Collection中能存放的元素

    ​ 没有使用“泛型”之前,Collection中可以存储Object的所有子类型。
    ​ Collection中什么都能存,只要是Object的子类型就行。(集合中不能直接存储基本数据类型,也不能存
    ​ java对象,只是存储java对象的内存地址。)

    ​ 使用了“泛型”之后,Collection中只能存储某个具体的类型。

    (2)Collection中的常用方法

    ​ boolean add(Object e) 向集合中添加元素
    ​ int size() 获取集合中元素的个数
    ​ void clear() 清空集合
    ​ boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false
    ​ boolean remove(Object o) 删除集合中的某个元素。
    ​ boolean isEmpty() 判断该集合中元素的个数是否为0
    ​ Object[] toArray() 调用这个方法可以把集合转换成数组。【作为了解,使用不多。】

    package Collection;import java.util.ArrayList;
    import java.util.Collection;public class CollectionTest01 {public static void main(String[] args) {//创建一个集合对象//Collection c = new Collection();//接口是抽象的,无法实例化。//多态Collection c = new ArrayList();//测试Collection常用方法//  boolean add(Object e) 向集合中添加元素c.add(39);//自动装箱(java5的新特性。),实际上是放进去了一个对象的内存地址。Integer x = new Integer(39);c.add("Hello");c.add(true);c.add(3.14);c.add(new Object());// int size()  获取集合中元素的个数System.out.println("该集合中元素的个数为:" + c.size());//5// void clear() 清空集合c.clear();System.out.println("该集合中元素的个数为:" + c.size());//清空之后个数为0//boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回falsec.add(39);//自动装箱(java5的新特性。),实际上是放进去了一个对象的内存地址。Integer x = new Integer(39);c.add("Hello");c.add(true);c.add(3.14);c.add(new Object());System.out.println(c.contains(39));//trueSystem.out.println(c.contains(3.15));//false// boolean remove(Object o) 删除集合中的某个元素。c.remove(true);System.out.println("该集合中元素的个数为:" + c.size());//4// boolean isEmpty()  判断该集合中元素的个数是否为0System.out.println(c.isEmpty());//false// Object[] toArray()  调用这个方法可以把集合转换成数组。【作为了解,使用不多。】Object[] a = c.toArray();for (int i = 0; i < a.length; i++) {System.out.println(a[i]);}}
    }
    
  2. Collection集合迭代

    (1)以下讲解的遍历方式/迭代方式,是所有Collection通用的一种方式。在Map集合中不能用。在所有的Collection以及子类中使用。

    以下两个方法是迭代器对象Iterator中的方法:
    boolean hasNext()如果仍有元素可以迭代,则返回 true。
    Object next() 返回迭代的下一个元素。

    package Collection;import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;public class CollectionTest02 {public static void main(String[] args) {//创建集合对象Collection c = new ArrayList();// 添加元素c.add("abc");c.add("def");c.add(100);c.add(new Object());// 对集合Collection进行遍历/迭代// 第一步:获取集合对象的迭代器对象IteratorIterator it = c.iterator();// 第二步:通过以上获取的迭代器对象开始迭代/遍历集合。while(it.hasNext()){Object obj = it.next();System.out.println(obj);}}
    }
    

    (2)迭代器是通用的

    package Collection;import java.util.ArrayList;
    import java.util.Collection;
    import java.util.HashSet;
    import java.util.Iterator;public class CollectionTest03 {public static void main(String[] args) {//创建集合对象Collection c1 = new ArrayList();//添加元素c1.add(1);c1.add(2);c1.add(3);c1.add(4);c1.add(1);//有序可重复//迭代集合Iterator it= c1.iterator();while(it.hasNext()){System.out.println(it.next());}//HashSet集合:无序不可重复Collection c2 = new HashSet();c2.add(100);c2.add(200);c2.add(300);c2.add(90);c2.add(400);c2.add(50);c2.add(60);c2.add(100);Iterator it2 = c2.iterator();while(it2.hasNext()){System.out.println(it2.next());}}
    }
    
  3. 深入Collection集合的contains方法和remove方法

    (1)contains方法

    boolean contains(Object o) 判断集合中是否包含某个对象o, 如果包含返回true, 如果不包含返回false。

    contains方法是用来判断集合中是否包含某个元素的方法,
    那么它在底层是怎么判断集合中是否包含某个元素的呢?
    调用了equals方法进行比对。 equals方法返回true,就表示包含这个元素。

    package Collection;import java.util.ArrayList;
    import java.util.Collection;public class CollectionTest04 {public static void main(String[] args) {//创建集合对象Collection c = new ArrayList();// 向集合中存储元素String s1 = new String("abc");c.add(s1); // 放进去了一个"abc"String s2 = new String("def");c.add(s2);// 集合中元素的个数System.out.println("元素的个数是:" + c.size());String x = new String("abc");System.out.println("元素的个数是:" + c.size());// c集合中是否包含x?结果猜测一下是true还是false?System.out.println(c.contains(x)); //判断集合中是否存在"abc" true}
    }
    

package Collection;import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;public class CollectionTest05 {public static void main(String[] args) {//创建集合对象Collection c = new ArrayList();User u1 = new User("dd");c.add(u1);User u2 = new User("dd");//User类中没有重写equals方法之前,底层调用的是Object的equals方法,比较的是内存地址//重写equals方法之后,比较的是内容System.out.println(c.contains(u2));}
}
class User{private String name;public User() {}public User(String name) {this.name = name;}//重写equals方法@Overridepublic boolean equals(Object o) {if(o == null || !(o instanceof User)) return false;if(o == this) return true;User u = (User)o;// 如果名字一样表示同一个人。(不再比较对象的内存地址了。比较内容。)return u.name.equals(this.name);}
}

(2)remove方法

package Collection;import java.util.ArrayList;
import java.util.Collection;public class CollectionTest06 {public static void main(String[] args) {// 创建集合对象Collection cc = new ArrayList();// 创建字符串对象String s1 = new String("hello");// 加进去。cc.add(s1);// 创建了一个新的字符串对象String s2 = new String("hello");// 删除s2cc.remove(s2); // s1.equals(s2) java认为s1和s2是一样的。删除s2就是删除s1。System.out.println(cc.size()); //集合中元素个数是 0}
}
重点:当集合的结构发生改变时,迭代器必须重新获取,如果还是用以前老的迭代器,会出现异常:java.util.ConcurrentModificationException

​ 在迭代集合元素的过程中,不能调用集合对象的remove方法,删除元素:
c.remove(o); 迭代过程中不能这样。 会出现:java.util.ConcurrentModificationException

​ 在迭代元素的过程当中,一定要使用迭代器Iterator的remove方法,删除元素,不要使用集合自带的remove方法删除元素。

**结论:**Collection接口中的remove方法和contains方法底层都会调用equals,存放在一个集合中的类型,一定要重写equals方法。


三、List接口

  1. List接口中常用方法

    (1)List集合存储元素特点:有序可重复
    有序:List集合中的元素有下标。 从0开始,以1递增。
    可重复:存储一个1,还可以再存储1。

    (2)List接口特有的常用的方法:

    ​ void add(int index, Object element) 在列表的指定位置插入指定元素(第一个参数是下标)
    ​ Object set(int index, Object element) 修改指定位置的元素
    ​ Object get(int index) 根据下标获取元素
    ​ int indexOf(Object o) 获取指定对象第一次出现处的索引。
    ​ int lastIndexOf(Object o) 获取指定对象最后一次出现处的索引。
    ​ Object remove(int index)删除指定下标位置的元素

    package List;import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;public class ListTest01 {public static void main(String[] args) {//创建List集合对象List myList = new ArrayList();//添加元素myList.add("A"); // 默认都是向集合末尾添加元素。myList.add("B");myList.add("C");myList.add("D");//在列表的指定位置插入指定元素(第一个参数是下标)myList.add(1,"E");//迭代Iterator it = myList.iterator();while(it.hasNext()){Object elt = it.next();System.out.println(elt);}System.out.println("=======================================");//  Object get(int index)根据下标获取元素System.out.println(myList.get(3));//C//因为有下标,所以List集合有自己比较特殊的遍历方式for(int i = 0; i < myList.size(); i++){System.out.println(myList.get(i));}// int indexOf(Object o) 获取指定对象第一次出现处的索引。System.out.println(myList.indexOf("A"));//0// Object remove(int index)删除指定下标位置的元素myList.remove(0);System.out.println(myList.size());//  Object set(int index, Object element)  修改指定位置的元素myList.set(3, "dd");System.out.println("=======================================");// 遍历集合for(int i = 0; i < myList.size(); i++){Object obj = myList.get(i);System.out.println(obj);}}
    }
    

    增删改查这几个单词要知道:
    增:add、save、new
    删:delete、drop、remove
    改:update、set、modify
    查:find、get、query、select

  2. ArrayList

    (1)默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10。)

    (2)集合底层是一个Object[]数组。

    (3)构造方法:

    ​ new ArrayList();
    ​ new ArrayList(20);

    (4)ArrayList集合的扩容:增长到原容量的1.5倍。
    ArrayList集合底层是数组,怎么优化?
    尽可能少的扩容。因为数组扩容效率比较低,建议在使用ArrayList集合的时候预估计元素的个数,给定一个初始化容量。

    (5)二进制位运算

    二进制右移一位是除以2,左移一位是乘以2.

    (6)数组优点:
    检索效率比较高。(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高。)

    (7)数组缺点:
    随机增删元素效率比较低。另外数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间。)

    (8)向数组末尾添加元素,效率很高,不受影响。

    (9)面试官经常问的一个问题?
    这么多的集合中,你用哪个集合最多?
    答:ArrayList集合。 因为往数组末尾添加元素,效率不受影响。 另外,我们检索/查找某个元素的操作比较多。

    (10)ArrayList集合是非线程安全的。(不是线程安全的集合。)

    (11)ArrayList的另外一个构造方法

    package List;import java.util.ArrayList;
    import java.util.Collection;
    import java.util.HashSet;
    import java.util.List;public class ArrayListTest01 {public static void main(String[] args) {List myList1 = new ArrayList();List myList2 = new ArrayList(100);//创建一个HashSet集合Collection c = new HashSet();c.add(100);c.add(200);c.add(300);c.add(90);// 通过这个构造方法就可以将HashSet集合转换成List集合。List myList3 = new ArrayList(c);for (int i = 0; i < myList3.size(); i++) {System.out.println(myList3.get(i));}}
    }
    
  3. LinkedList

    (1)单向链表

    对于链表数据结构来说:基本的单元是节点Node。

    对于单向链表来说,任何一个节点Node中都有两个属性:

    ​ 第一:存储的数据。第二:下一节点的内存地址。

    package Link;public class Node {// 存储的数据Object data;// 下一个节点的内存地址Node next;public Node(){}public Node(Object data, Node next){this.data = data;this.next = next;}
    }
    

**链表优点:**由于链表上的元素在空间存储上内存地址不连续。所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议 使用LinkedList。

链表缺点: 不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头 节点开始遍历,直到找到为止。所以LinkedList集合检索/查找的效率较低。

ArrayList:把检索发挥到极致。(末尾添加元素效率还是很高的。)
LinkedList:把随机增删发挥到极致。加元素都是往末尾添加,所以ArrayList用的比LinkedList多。

(2)双向链表

(3)LinkedList集合底层采用了 双向链表数据结构

  1. vector

    (1)底层也是一个数组。

    (2)初始化容量:10

    (3)扩容之后是原容量的2倍。
    10–> 20 --> 40 --> 80

    (4)Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的。效率比较低,使用较少。

    (5)怎么将一个线程不安全的ArrayList集合转换成线程安全的呢?
    使用集合工具类: java.util.Collections;

    ​ java.util.Collection 是集合接口。
    ​ java.util.Collections 是集合工具类。

    package List;import java.util.*;public class VectorTest {public static void main(String[] args) {// 创建一个Vector集合List vector = new Vector();// 添加元素,默认容量10个。vector.add(1);vector.add(2);vector.add(3);vector.add(4);vector.add(5);vector.add(6);vector.add(7);vector.add(8);vector.add(9);vector.add(10);// 满了之后扩容(扩容之后的容量是20.)vector.add(11);Iterator it = vector.iterator();while(it.hasNext()){Object obj = it.next();System.out.println(obj);}List myList = new ArrayList(); // 非线程安全的。// 变成线程安全的Collections.synchronizedList(myList); // myList集合就是线程安全的了。myList.add("111");myList.add("222");myList.add("333");}
    }
    

四、泛型机制

  1. JDK5.0之后推出的新特性:泛型

  2. 泛型这种语法机制,只在程序编译阶段起作用,只是给编译器参考的。(运行阶段泛型没用!)

    (1)不使用泛型机制,观察以下代码

    package Generic;import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;public class GenericTest01 {public static void main(String[] args) {List myList = new ArrayList();//准备对象Cat c = new Cat();Bird b = new Bird();//将对象添加到集合当中myList.add(c);myList.add(b);//要求:遍历集合,让动物移动// 获取迭代器Iterator it = myList.iterator();while(it.hasNext()){Object o = it.next();if(o instanceof Animal){Animal a =(Animal) o;((Animal) o).move();}}}
    }
    class Animal{public void move(){System.out.println("动物在移动!");}
    }class Cat extends Animal{public void catchMouse(){System.out.println("猫捉老鼠!");}
    }class Bird extends Animal{public void fly(){System.out.println("鸟儿在飞翔!");}
    }
    

    (2)使用泛型

    package Generic;import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class GenericTest01 {public static void main(String[] args) {List<Animal> myList = new ArrayList<Animal>();//准备对象Cat c = new Cat();Bird b = new Bird();//将对象添加到集合当中myList.add(c);myList.add(b);//要求:遍历集合,让动物移动// 获取迭代器Iterator<Animal> it = myList.iterator();while(it.hasNext()){//使用泛型之后,每一次迭代返回的数据都是Animal类型。Animal a = it.next();//这里不再需要强制类型转换,直接调用a.move();}}}
    class Animal{public void move(){System.out.println("动物在移动!");}
    }class Cat extends Animal{public void catchMouse(){System.out.println("猫捉老鼠!");}
    }class Bird extends Animal{public void fly(){System.out.println("鸟儿在飞翔!");}
    }
    
  3. 泛型的优缺点

    优点: 第一:集合中存储的元素类型统一。
    第二:从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”!

    缺点:导致集合中存储的元素缺乏多样性!
    大多数业务中,集合中元素的类型还是统一的。所以这种泛型特性被大家所认可。

    package Generic;import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class GenericTest01 {public static void main(String[] args) {List<Animal> myList = new ArrayList<Animal>();//准备对象Cat c = new Cat();Bird b = new Bird();//将对象添加到集合当中myList.add(c);myList.add(b);//要求:遍历集合,让动物移动// 获取迭代器Iterator<Animal> it = myList.iterator();while(it.hasNext()){//调用子类型特有的方法还是需要向下转型Animal a = it.next();if(a instanceof Cat){Cat x = (Cat)a;x.catchMouse();}if(a instanceof Bird){Bird y = (Bird)a;y.fly();}}}}
    class Animal{public void move(){System.out.println("动物在移动!");}
    }class Cat extends Animal{public void catchMouse(){System.out.println("猫捉老鼠!");}
    }class Bird extends Animal{public void fly(){System.out.println("鸟儿在飞翔!");}
    }
    
  4. 类型自动推断

    JDK8之后引入了:自动类型推断机制。(又称为钻石表达式)

    package Generic;import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;public class GenericTest02 {public static void main(String[] args) {// ArrayList<这里的类型会自动推断>(),前提是JDK8之后才允许。自动类型推断,钻石表达式!List<Animal> myList = new ArrayList<>();myList.add(new Animal());myList.add(new Cat());myList.add(new Bird());// 遍历Iterator<Animal> it = myList.iterator();while(it.hasNext()){Animal a = it.next();a.move();}List<String> strList = new ArrayList<>();// 类型不匹配。//strList.add(new Cat());strList.add("http://www.4399.com");strList.add("http://www.baidu.com");strList.add("http://www.csdn.com");// 遍历Iterator<String> it2 = strList.iterator();while(it2.hasNext()){// 如果没有使用泛型/*Object obj = it2.next();if(obj instanceof String){String ss = (String)obj;ss.substring(7);}*/// 直接通过迭代器获取了String类型的数据String s = it2.next();// 直接调用String类的substring方法截取字符串。String newString = s.substring(7);System.out.println(newString);}}
    }
    
  5. 自定义泛型
    自定义泛型的时候,<> 尖括号中的是一个标识符,随便写。
    java源代码中经常出现的是: 和
    E是Element单词首字母。
    T是Type单词首字母。


五、foreach(增强for循环)

  1. JDK5.0之后推出了一个新特性:叫做增强for循环,或者叫做foreach
package Foreach;public class ForEachTest01 {public static void main(String[] args) {// int类型数组int[] arr = {39,25,93,33,22,30,239};// 遍历数组(普通for循环)for(int i = 0; i < arr.length; i++) {System.out.println(arr[i]);}// 增强for(foreach)以下是语法/*for(元素类型 变量名 : 数组或集合){System.out.println(变量名);}*/System.out.println("======================================");// foreach有一个缺点:没有下标。在需要使用下标的循环中,不建议使用增强for循环。for(int data : arr) {// data就是数组中的元素(数组中的每一个元素。)System.out.println(data);}}
}
  1. 集合使用foreach

    package Foreach;import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;public class ForEachTest02 {public static void main(String[] args) {//创建集合对象List<String> list = new ArrayList<>();//添加元素list.add("a");list.add("dd");list.add("yy");//遍历,使用迭代器的方式Iterator<String> it = list.iterator();while(it.hasNext()){System.out.println(it.next());}System.out.println("=====================");//使用下标的方式,只针对于有下标的集合)for (int i = 0; i < list.size(); i++) {System.out.println(list.get(i));}System.out.println("=====================");//使用foreach的方式for (String s:list) {System.out.println(s);}}
    }
    

六、Set接口

  1. HashSet集合特点

    (1)无序不可重复:

    ​ 无序:存储时顺序和取出的顺序不同。

    (2)放到HashSet集合中的元素实际上是放到HashMap集合的key部分

    package Set;import java.util.HashSet;
    import java.util.Set;public class HashSetTest01 {public static void main(String[] args) {Set<String> hs = new HashSet<>();hs.add("hello3");hs.add("hello4");hs.add("hello1");hs.add("hello2");hs.add("hello3");hs.add("hello3");for(String s : hs){System.out.println(s);}}
    }
    
  2. TreeSet集合特点

    (1)无序不可重复的,但是存储的元素可以自动按照大小顺序排序!称为:可排序集合。

    ​ 无序:这里的无序指的是存进去的顺序和取出来的顺序不同。并且没有下标。

    (2)TreeSet集合底层实际上是一个TreeMap。

    (3)TreeMap集合底层是一个二叉树。

    (4)放到TreeSet集合中的元素,等同于放到TreeMap集合key部分。

    (5)TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。称为:可排序集合。

    package Set;import java.util.TreeSet;public class TreeSetTest01 {public static void main(String[] args) {TreeSet<String> set = new TreeSet<>();set.add("zhangsan");set.add("lisi");set.add("wangwu");set.add("zhaoliu");for(String s : set){System.out.println(s);//按照升序的顺序排序}}

    (6)TreeSet对自定义类的排序

    package Set;import java.util.TreeSet;public class TreeSetTest02 {public static void main(String[] args) {Person p1 = new Person(23);Person p2 = new Person(32);Person p3 = new Person(30);Person p4 = new Person(25);TreeSet<Person> ts = new TreeSet<>();ts.add(p1);ts.add(p2);ts.add(p3);ts.add(p4);for(Person p : ts){System.out.println(p);}}
    }
    class Person{int age;public Person(int age) {this.age = age;}@Overridepublic String toString() {return "Person{" +"age=" + age +'}';}
    }
    

    对于以上程序来说,无法排序。因为没有指定Person对象之间的比较规则。以下程序运行的时候出现了这个异常:
    java.lang.ClassCastException:
    class com.javase.collection.Person
    cannot be cast to class java.lang.Comparable
    出现这个异常的原因是:Person类没有实现java.lang.Comparable接口。

    package Set;import java.util.TreeSet;public class TreeSetTest02 {public static void main(String[] args) {Person p1 = new Person(23);Person p2 = new Person(32);Person p3 = new Person(30);Person p4 = new Person(25);TreeSet<Person> ts = new TreeSet<>();ts.add(p1);ts.add(p2);ts.add(p3);ts.add(p4);for(Person p : ts){System.out.println(p);}}
    }
    class Person implements Comparable<Person>{int age;public Person(int age) {this.age = age;}@Overridepublic String toString() {return "Person{" +"age=" + age +'}';}//需要在这个方法中编写比较的规则//放在TreeSet集合中的元素需要实现java.lang.Comparable接口。并且实现compareTo方法。equals可以不写。@Overridepublic int compareTo(Person o) {//按照年龄升序排序return this.age - o.age;}
    }
    

    实现java.lang.Comparable接口后可以进行排序。

    (7)比较规则怎么写

    要求:对VIP客户先按照年龄升序,如果年龄一样的再按照姓名升序。

    package Set;import java.util.TreeSet;public class TreeSetTest03 {public static void main(String[] args) {TreeSet<Vip> ts = new TreeSet<>();ts.add(new Vip("dd",25));ts.add(new Vip("yy",25));ts.add(new Vip("d",40));ts.add(new Vip("y",35));ts.add(new Vip("d",39));for(Vip v : ts){System.out.println(v);}}
    }
    class Vip implements Comparable<Vip>{String name;int age;public Vip(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Vip{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic int compareTo(Vip v) {if(this.age == v.age){//姓名是String类型,可以直接调用compareTo来完成比较return this.name.compareTo(v.name);}else{return this.age - v.age;}}
    }
    

    compareTo方法的返回值很重要:
    返回0表示相同,value会覆盖。
    返回>0,会继续在右子树上找。
    返回<0,会继续在左子树上找。

    (8)自平衡二叉树数据结构

    ​ ①遵循左小右大的原则存放。

    ​ ②遍历二叉树的方式有三种:

    ​ 前序遍历:根左右

    ​ 中序遍历:左根右

    ​ 后序遍历:左右根

    ​ 注意:前中后指的是根的位置。

    ​ ③TreeSet/TreeMap采用的是中序遍历方式。即Iterator迭代器采用的是中序遍历。

    (9)实现比较器接口

    ​ TreeSet集合中元素可排序的第二种方式:使用比较器的方式。

    package Set;import java.util.Comparator;
    import java.util.TreeSet;public class TreeSetTest04 {public static void main(String[] args) {//TreeSet<tortoise> ts = new TreeSet<>();这样创建对象的过程中并没有通过构造方法传递一个比较器TreeSet<tortoise> ts = new TreeSet<>(new tortoiseComparator());ts.add(new tortoise(39));ts.add(new tortoise(25));ts.add(new tortoise(53));ts.add(new tortoise(52));for(tortoise t : ts){System.out.println(t);}}
    }
    class tortoise{int age;public tortoise(int age) {this.age = age;}@Overridepublic String toString() {return "tortoise{" +"age=" + age +'}';}
    }
    //单独编写一个比较器,实现java.util.Comparator接口。(Comparable是java.lang包下的。Comparator是java.util包下的。)
    class tortoiseComparator implements Comparator<tortoise> {@Overridepublic int compare(tortoise o1, tortoise o2) {//指定比较规则return o1.age - o2.age;}
    }
    

    使用匿名内部类:

    package Set;import java.util.Comparator;
    import java.util.TreeSet;public class TreeSetTest04 {public static void main(String[] args) {//TreeSet<tortoise> ts = new TreeSet<>();这样创建对象的过程中并没有通过构造方法传递一个比较器//TreeSet<tortoise> ts = new TreeSet<>(new tortoiseComparator());//使用匿名内部类的方式(这个类没有名字。直接new接口)TreeSet<tortoise> ts = new TreeSet<>(new Comparator<tortoise>() {@Overridepublic int compare(tortoise o1, tortoise o2) {return o1.age - o2.age;}});ts.add(new tortoise(39));ts.add(new tortoise(25));ts.add(new tortoise(53));ts.add(new tortoise(52));for(tortoise t : ts){System.out.println(t);}}
    }
    class tortoise{int age;public tortoise(int age) {this.age = age;}@Overridepublic String toString() {return "tortoise{" +"age=" + age +'}';}
    }
    //单独编写一个比较器,实现java.util.Comparator接口。(Comparable是java.lang包下的。Comparator是java.util包下的。)
    /*class tortoiseComparator implements Comparator<tortoise> {@Overridepublic int compare(tortoise o1, tortoise o2) {//指定比较规则return o1.age - o2.age;}
    }*/
    

    总结:

    放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
    第一种:放在集合中的元素实现java.lang.Comparable接口。
    第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。
    Comparable和Comparator怎么选择呢?
    当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。
    如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。Comparator接口的设计符合OCP原则


七、Map接口

  1. Map和Collection没有继承关系。

  2. Map集合以key和value的方式存储数据:键值对
    key和value都是引用数据类型。
    key和value都是存储对象的内存地址。
    key起到主导的地位,value是key的一个附属品。

  3. Map接口中常用方法:
    (1) V put(K key, V value) 向Map集合中添加键值对
    (2) V get(Object key) 通过key获取value
    (3) void clear() 清空Map集合
    (4) boolean containsKey(Object key) 判断Map中是否包含某个key
    (5) boolean containsValue(Object value) 判断Map中是否包含某个value
    (6) boolean isEmpty() 判断Map集合中元素个数是否为0
    (7) V remove(Object key) 通过key删除键值对
    (8) int size() 获取Map集合中键值对的个数。
    (9) Collection values() 获取Map集合中所有的value,返回一个Collection

    ​ (10)Set keySet() 获取Map集合所有的key(所有的键是一个set集合)

    ​ (11)Set<Map.Entry<K,V>> entrySet() 将Map集合转换成Set集合

     package Map;import java.util.Collection;import java.util.HashMap;import java.util.Map;public class MapTest01 {public static void main(String[] args) {// 创建Map集合对象Map<Integer,String> map = new HashMap<>();// V put(K key, V value) 向Map集合中添加键值对map.put(1,"张三");map.put(2,"李四");map.put(3,"王五");map.put(4,"赵六");map.put(5,"丁七");//  V get(Object key) 通过key获取valueSystem.out.println(map.get(2));System.out.println(map.get(3));//  int size() 获取键值对的数量System.out.println(map.size());// V remove(Object key) 通过key删除键值对map.remove(1);System.out.println(map.size());// boolean containsKey(Object key) 判断Map中是否包含某个keySystem.out.println(map.containsKey(1));//1已经删除,所以是falseSystem.out.println(map.containsKey(5));//true//  boolean containsValue(Object value) 判断Map中是否包含某个valueSystem.out.println(map.containsValue("丁七"));//true//  Collection<V> values() 获取Map集合中所有的value,返回一个CollectionCollection<String> values = map.values();for(String s : values){System.out.println(s);}// void clear()    清空Map集合map.clear();// boolean isEmpty()   判断Map集合中元素个数是否为0System.out.println(map.isEmpty());}}
    
  4. 遍历Map集合

package Map;import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;public class MapTest02 {public static void main(String[] args) {Map<Integer,String> map = new HashMap<>();map.put(1,"张三");map.put(2,"李四");map.put(3,"王五");map.put(4,"赵六");map.put(5,"丁七");//第一种方式:获取所有的key,通过遍历key,来遍历value//获取所有的key,所有的key是一个set集合Set<Integer> keys = map.keySet();//迭代器遍历setIterator<Integer> it = keys.iterator();while(it.hasNext()){Integer key = it.next();String value = map.get(key);System.out.println(key + "=" + value);}System.out.println("=================================");//foreach遍历for(Integer k : keys){System.out.println(k + "=" + map.get(k));}System.out.println("=================================");//第二种方式Set<Map.Entry<K,V>> entrySet()//直接将Map集合转换成Set集合,Set集合中元素的类型是:Map.EntrySet<Map.Entry<Integer,String>> set = map.entrySet();for(Map.Entry<Integer,String> node : set){System.out.println(node.getKey() + "--->" + node.getValue());}}
}
  1. 哈希表数据结构

    (1)HashMap集合底层是哈希表/散列表的数据结构。

    (2)哈希表是一个数组和单向链表的结合体。
    数组:在查询方面效率很高,随机增删方面效率很低。
    单向链表:在随机增删方面效率较高,在查询方面效率很低。
    哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。

    (3)HashMap集合底层的源代码:
    public class HashMap{
    // HashMap底层实际上就是一个数组。(一维数组)
    Node<K,V>[] table;

    ​ // 静态的内部类HashMap.Node
    ​ static class Node<K,V> {
    ​ final int hash; // 哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以转换存储成数组的 下标。)
    ​ final K key; // 存储到Map集合中的那个key
    ​ V value; // 存储到Map集合中的那个value
    ​ Node<K,V> next; // 下一个节点的内存地址。
    ​ }
    ​ }
    ​ 哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。)

    (4)最主要掌握的是:
    map.put(k,v)的实现原理:

    ​ 第一步:先将k,v封装到Node对象当中。

    ​ 第二步:底层调用k的hashCode()方法得出hash值。

    ​ 第三步:通过哈希函数/哈希算法,将hash值转换成数组下标。

    ​ 第四步:如果该下标位置上没有任何元素,就把Node添加到这个位置上。

    ​ 如果下标对应的位置上有链表,此时会拿着k和链表上的每一个节点中的k进行equals。

    ​ 如果所有的equals方法返回都是false,那么这个新节点将会被添加到链表的末尾。

    ​ 如果其中一个equals返回了true,则这个节点的value将会被覆盖。

    v = map.get(k)的实现原理:

    ​ 第一步:调用k的hashCode()方法得出哈希值。

    ​ 第二步:通过哈希算法转换成下标,通过数组下标快速地定位到某个位置上。

    ​ 第三步:如果这个位置上什么也没有,返回null。

    ​ 如果这个位置上有单向链表,那么会拿着k和单向链表上的每个节点中的k进行equals。

    ​ 如果所有equals方法返回false,那么get方法返回null。

    ​ 只要有其中一个节点的k和参数k equals的时候返回true,那么此时这个节点的value就是我们要找的value,get方法 最终返回这个value。

    (5)HashMap集合的key部分特点:
    无序不可重复。
    无序—— 因为不一定挂到哪个单向链表上。
    不可重复—— equals方法来保证HashMap集合的key不可重复。如果key重复了,value会覆盖。

    package Map;import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;public class HashMapTest01 {public static void main(String[] args) {Map<Integer,String> map = new HashMap<>();map.put(1111,"zhangsan");map.put(3333,"lisi");map.put(4444,"ddyy");map.put(2222,"wangwu");map.put(2222,"zhaoliu");System.out.println(map.size());//key重复的时候值会被覆盖//遍历map集合Set<Map.Entry<Integer,String>> set = map.entrySet();for(Map.Entry<Integer,String> entry : set){System.out.println(entry.getKey() + "=" + entry.getValue());}}
    }
    

    (6)哈希表HashMap使用不当时无法发挥性能!
    假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。这种情况我们成为:散列分布不 均匀。
    什么是散列分布均匀?
    假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,是散列分布均匀的。
    假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题?
    不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念。也是散列分布不均匀。
    散列分布均匀需要你重写hashCode()方法时有一定的技巧。

    (7)**重点:**放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法。

    ​ ①向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法!equals方法有可能调 用,也有可能不调用。
    ​ 拿put(k,v)举例,什么时候equals不会调用?
    ​ k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。数组下标位置上如果是null,equals不需要执行。
    拿get(k)举例,什么时候equals不会调用?
    ​ k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。 数组下标位置上如果是null,equals不需要执行。

    ​ ②注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写。并且equals方法返回如果是true,hashCode()方 法返回的值必须一样。 equals方法返回true表示两个对象相同,在同一个单向链表上比较。 那么对于同一个单向链 表上的节点来说,他们的哈希值都是相同的。所以hashCode()方法的返回值也应该相同。

    ​ ③hashCode()方法和equals()方法的重写,直接使用IDEA工具生成,但是这两个方法需要同时生成。

    package Map;import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Set;public class HashMapTest02 {public static void main(String[] args) {Student s1 = new Student("zhangsan");Student s2 = new Student("zhangsan");//不重写equals方法之前,结果为false,重写之后结果为trueSystem.out.println(s1.equals(s2));//没有重写hashCode()方法之前,s1和s2内存地址不一致System.out.println("s1的hashCode=" + s1.hashCode());System.out.println("s1的hashCode=" + s1.hashCode());//前面equals方法输出结果为true,说明s1和s2是相同的,往HashSet里放的时候,按理来说只能放一个,Set<Student> set = new HashSet<>();set.add(s1);set.add(s2);System.out.println(set.size());//按理说结果为1,但是没重写前结果为2}
    }
    
    package Map;import java.util.Objects;public class Student {private String name;//无参构造public Student() {}//有参构造public Student(String name) {this.name = name;}//setter和getterpublic String getName() {return name;}public void setName(String name) {this.name = name;}//如果学生名字一样,表示是同一个学生@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;return Objects.equals(name, student.name);}@Overridepublic int hashCode() {return Objects.hash(name);}
    }
    

    ​ ④JDK8对HashMap集合的改进

    ​ 在JDK8之后,如果哈希表单向链表中元素超过八个,单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量 小于六时,会重新把红黑树数据结构变成单向链表数据结构。这种方式也是为了提高检索效率,二叉树的检索会再次缩小检 索范围,提高效率。

    (8)HashMap集合的默认初始化容量是16,默认加载因子是0.75。
    这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容。

    ​ **重点:**HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,这是因为达到散列分布均匀,为了提高HashMap集合的 存取效率,所必须的。

  2. HashMap和Hashtable的区别

    (1)HashMap集合key部分允许null吗?
    允许,但是要注意:HashMap集合的key null值只能有一个。

    package Map;import java.util.HashMap;
    import java.util.Map;public class HashMapTest03 {public static void main(String[] args) {Map map = new HashMap();// HashMap集合允许key为nullmap.put(null, null);System.out.println(map.size()); // 1// key重复的话value是覆盖!map.put(null, 100);System.out.println(map.size()); //1// 通过key获取valueSystem.out.println(map.get(null)); // 100}
    }
    

    (2)Hashtable的key可以为null吗?
    Hashtable的key和value都是不能为null的。 HashMap集合的key和value都是可以为null的。

    ​ Hashtable方法都带有synchronized:线程安全的。线程安全有其它的方案,这个Hashtable对线程的处理
    ​ 导致效率较低,使用较少。

    ​ Hashtable和HashMap一样,底层都是哈希表数据结构。
    ​ Hashtable的初始化容量是11,默认加载因子是:0.75f,Hashtable的扩容是:原容量 * 2 + 1

  3. 属性类Properties

    (1)Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。Properties被称为属性类对象。

package Map;import java.util.Properties;public class PropertiesTest01 {public static void main(String[] args) {Properties pro = new Properties();// 需要掌握Properties的两个方法,一个存,一个取。pro.setProperty("username","ddyy");pro.setProperty("password","ddyy123");//通过key获取valueSystem.out.println(pro.getProperty("username"));System.out.println(pro.getProperty("password"));}
}

八、Collections工具 类

java.util.Collection 集合接口
java.util.Collections 集合工具类,方便集合的操作。

package Collection;import java.util.*;public class CollectionsTest {public static void main(String[] args) {// ArrayList集合不是线程安全的。List<String> list = new ArrayList<>();// 变成线程安全的Collections.synchronizedList(list);// 排序list.add("adc");list.add("ady");list.add("day");Collections.sort(list);for(String s : list){System.out.println(s);}List<tortoise2> list2 = new ArrayList<>();list2.add(new tortoise2(1000));list2.add(new tortoise2(390));list2.add(new tortoise2(520));list2.add(new tortoise2(1300));// 注意:对List集合中元素排序,需要保证List集合中的元素实现了:Comparable接口。Collections.sort(list2);for(tortoise2 t2 : list2){System.out.println(t2);}// 对Set集合怎么排序呢?Set<String> set = new HashSet<>();set.add("king");set.add("kingsoft");set.add("king2");set.add("king1");// 将Set集合转换成List集合List<String> myList = new ArrayList<>(set);Collections.sort(myList);for(String s : myList) {System.out.println(s);}}
}
class tortoise2 implements Comparable<tortoise2>{int age;public tortoise2(int age){this.age = age;}@Overridepublic int compareTo(tortoise2 o) {return this.age - o.age;}@Overridepublic String toString() {return "tortoise{" +"age=" + age +'}';}
}

JavaSE——集合相关推荐

  1. JavaSE基础篇---processon在线脑图分享

    JavaSE基础篇学习笔记脑图 按照学习阶段按java基础知识.变量与运算符.流程控制.数组再到面向对象,结合课程以及学习笔记做成的脑图,内容较多,仅供个人学习.详细见链接脑图.

  2. JAVA-SE基础篇-非静态内部类

    JAVA-SE基础篇-非静态内部类 /*** 非静态内部类* 知识点* 1. 非静态内部类 BattleScore "战斗成绩"* 非静态内部类可以直接在一个类里面定义* 比如:* ...

  3. JavaSE基础篇--Java SE语法02--基本语法

    JavaSE基础篇 Java SE语法02 HelloWorld 基本语法 流程控制 方法 数组 基本语法 标识符 关键字 数据类型 运算符 数据类型转换 运算后结果的数据类型 Java SE语法02 ...

  4. JavaSE基础篇——超详细,Java入门,这一篇就够了

    第一节:Java语言概述 一.java背景知识 1.Java是美国sun公司(Stanford University Network)在1995年推出的一门计算机高级编程语言. 2.Java早期被称为 ...

  5. Java面试基础篇之集合

    文章目录 你知道的集合都有哪些? 哪些集合是线程安全的? Collection 集合类和数组有什么不同? Collection和Collections有什么区别? 如何确保一个集合不能被修改? Lis ...

  6. Linux基础篇大集合

    目录 一.基础篇 (一)基本常识 1.linux的三种网络连接方式 2.虚拟机的克隆 3.虚拟机的快照 4.虚拟机的迁移和删除 (二)目录结构 (三)基本实操 1.远程连接Linux操作系统 2.vi ...

  7. 【JavaSE】《基础篇005》集合

    文章目录 遍历集合的方式 集合与数组之间转换 List接口 ArrayList Vector LinkedList CopyOnWriteArrayList List各子类的区别与对比 说一下aray ...

  8. 【JAVA基础篇】集合框架

    一.集合框架图 Java集合框架主要包含两种类型的容器,一是集合(Collection),存储元素集合,二是图(Map),存储键(key)-值(value)对.Collection接口下面有两个重要的 ...

  9. java如何把查到的对象集合放入 展示对象list中_Java面试整理-基础篇8.集合1

    1.Java中常见的集合及其关系? 2.ArrayList.LinkedList.Vector的区别? 1. LinkedList.ArrayList.Vector都是List接口的子类:Linked ...

  10. 【Java基础篇】集合排序

    所谓集合排序是指对集合内的元素进行排序. 集合工具类Collections中提供了两种排序算法,分别是: Collections.sort(List list) Collections.sort(Li ...

最新文章

  1. css overflow和border-radius一起用 解决圆角和滚动条一起用的问题
  2. python数组去重函数_Python常用功能函数系列总结(一)
  3. 模拟电路技术之基础知识(一)
  4. linux系统中/etc/syslog.conf文件解读
  5. 利用福禄克网线/光纤测试仪规范测试BICSI标准
  6. VR 技术加上 8K 画质! 2016 年里约奥运会亮点十足
  7. HDU 6750 Function(莫比乌斯反演)(2020百度之星初赛1)
  8. 对JSON的一点认识和理解以及JQuery处理JSON
  9. matlab怎么设clim,Python Matplotlib.pyplot.clim()用法及代码示例
  10. 20190901 On Java8 第十五章 异常
  11. java 后台判断浏览器类型
  12. 集成电路模拟版图入门-版图基础学习笔记(一)
  13. 数字电路-逻辑函数化简
  14. 人工智能-线性规划(单纯形法、大M法)和非线性规划(拉格朗日乘子法)python代码
  15. 代码整洁之道-读书笔记
  16. 自动化搜索ARX密码差分特征的方法
  17. 基于FPGA的映射调制实现
  18. 【链世纪对话系列No.9】浪潮存储资深架构师叶毓睿:浪潮如何助力分布式云存储数据中心快速发展?
  19. PPT自动翻页的实现和取消
  20. Java数据类型总结

热门文章

  1. MSU2020硬件编码器比赛
  2. 有没有html代码听力的软件吗,听力软件VoScreen,值得介绍一下
  3. 华为OJ_1960_字符串分割
  4. Unity 蓝湖 关于UI工作流优化的思考
  5. 【考研数据】三.2022年BJTU计算机学院考研录取数据分析
  6. [HXBCTF 2021]easywill writeup(WillPHP源码审计+利用pearcmd.php文件包含getshell)
  7. 电容充放电 课堂笔记1
  8. AWS 解决方案架构师考点(IAM)
  9. 基于FPGA的音乐播放器系统设计_kaic
  10. 2011/05/26 Unable to resolve activity for: Intent错误的解决方法