JavaSE——集合

集合

一、集合概述
  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,不包含返回false
            c.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));//true
            System.out.println(c.contains(3.15));//false
    
            // boolean remove(Object o) 删除集合中的某个元素。
            c.remove(true);
            System.out.println("该集合中元素的个数为:" + c.size());//4
    
            // boolean isEmpty()  判断该集合中元素的个数是否为0
            System.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进行遍历/迭代
            // 第一步:获取集合对象的迭代器对象Iterator
            Iterator 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方法

    @Override
    public 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");
        // 删除s2
        cc.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;
        }
    
        @Override
        public 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;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "age=" + age +
                    '}';
        }
    
        //需要在这个方法中编写比较的规则
        //放在TreeSet集合中的元素需要实现java.lang.Comparable接口。并且实现compareTo方法。equals可以不写。
        @Override
        public 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;
        }
    
        @Override
        public String toString() {
            return "Vip{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        @Override
        public 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;
        }
    
        @Override
        public String toString() {
            return "tortoise{" +
                    "age=" + age +
                    '}';
        }
    }
    //单独编写一个比较器,实现java.util.Comparator接口。(Comparable是java.lang包下的。Comparator是java.util包下的。)
    class tortoiseComparator implements Comparator<tortoise> {
    
        @Override
        public 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>() {
                @Override
                public 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;
        }
    
        @Override
        public String toString() {
            return "tortoise{" +
                    "age=" + age +
                    '}';
        }
    }
    //单独编写一个比较器,实现java.util.Comparator接口。(Comparable是java.lang包下的。Comparator是java.util包下的。)
    /*class tortoiseComparator implements Comparator<tortoise> {
    
        @Override
        public 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获取value
              System.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中是否包含某个key
              System.out.println(map.containsKey(1));//1已经删除,所以是false
              System.out.println(map.containsKey(5));//true
      
              //  boolean containsValue(Object value) 判断Map中是否包含某个value
              System.out.println(map.containsValue("丁七"));//true
      
              //  Collection<V> values() 获取Map集合中所有的value,返回一个Collection
              Collection<String> values = map.values();
              for(String s : values){
                  System.out.println(s);
              }
      
              // void clear()    清空Map集合
              map.clear();
              // boolean isEmpty()   判断Map集合中元素个数是否为0
              System.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();
        //迭代器遍历set
        Iterator<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.Entry
        Set<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,重写之后结果为true
            System.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和getter
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        //如果学生名字一样,表示是同一个学生
    
    
        @Override
        public 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);
        }
    
        @Override
        public 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为null
            map.put(null, null);
            System.out.println(map.size()); // 1
    
            // key重复的话value是覆盖!
            map.put(null, 100);
            System.out.println(map.size()); //1
    
            // 通过key获取value
            System.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获取value
        System.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;
    }

    @Override
    public int compareTo(tortoise2 o) {
        return this.age - o.age;
    }

    @Override
    public String toString() {
        return "tortoise{" +
                "age=" + age +
                '}';
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值