当前位置: 首页 > news >正文

JavaSE进阶--集合(2万字总结)

学习视频501-JavaSE进阶-集合概述_哔哩哔哩_bilibili

目录

集合概述

集合的存储

与数据结构的关系

Java中集合的分类

集合继承结构图

Collection接口

 2.Collection中的常用方法

Collection集合迭代

迭代器是通用的

深入Collection集合的contains方法:

List接口

ArrayList集合

ArrayList集合的另一个构造方法

LinkedList集合 

单向链表数据结构

Vector集合

泛型机制

自动类型推断

自定义泛型 

foreach(增强for循环)

HashSet集合特点     

TreeSet集合特点

Map接口

Map集合的遍历

哈希表数据结构

map.put(k,v)实现原理:

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

equals和hashCode的重写

Java8对HashMap集合的改进 

HashTable集合

属性类Properties类

TreeSet集合

TreeSet自定义数据类型比较规则 

自平衡二叉树数据结构 

实现比较接口

Collections工具类           


集合概述 

什么是集合?有什么用?

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

集合为什么说在开发中使用较多?

集合是一个容器,一个载体,可以一次容纳多个对象。在实际开发中,假设连接数据库,当中有10条记录,那么假设把这10条记录查询出来,在java程序中会将10条数据封装成10个java对象,然后将10个java对象放到某一个集合当中,将集合传到前端,然后遍历集合,将一个数据一个数据展现出来.

集合的存储

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

集合在java中本身是一个容器,是一个对象,在任何时候存储的都是"引用"

例如

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

这里的100不是直接存的基本数据类型,实际上是自动装箱过的

与数据结构的关系

在java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到了不同的数据结构当中。什么是数据结构?数据存储的结构就是数据结构。不同的数据结构,数据存储方式不同。例如:

                数组、二叉树、链表、哈希表...

Java中集合的分类

一类是单个方式存储元素

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

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

        以键值对的方式存储元素,这一类集合中超级父接口: java.util.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)集合存储元素的特点:

        无序不可重复。

        无序:存进去的顺序和取出的顺序不一定相同

        不可重复:存进去一个1,不可再存储一个1

SortedSet(SortedMap)集合存储元素的特点:

        首先是无序不可重复的,但是SortedSet集合中的元素是可排序的。

        无序:存进去的元素和取出的顺序不一定相同,另外Set集合中元素没有下标。

        不可重复:存进去1,不能再存储1了

        可排序:可以按照大小顺序排序。

Map集合的key,就是一个Set集合。

在Set集合中放数据,实际上放到了Map集合的key部分。


Collection接口

1.Collection中能存放什么元素?

没有使用"泛型"之前,Collection中可以存储Object的所有子类型。

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

Collection中什么都能存,只要是Object的子类型就行。(集合中不能直接存储基本数据类型,也不能存java对象,只是存储java对象的内存地址。)

 2.Collection中的常用方法

(1)boolean add(E e) 向集合中添加元素
 

import java.util.*;
public class Collectiontest01 {
    public static void main(String[] args) {
        //多态
        Collection c =new ArrayList();
        //测试Collection接口中的常用方法
        c.add(100);//自动装箱 Integer x=new Integer(100); 实际上放的是x
        c.add(3.14);
        c.add(new Object());
        c.add(true);
        c.add(new Student());
    }
}
class  Student{
}

(2) int size() 获取集合中元素的个数

  //获取集合中元素的个数
        System.out.println("集合中元素的个数是:"+c.size());//5

(3)void clear() 清空集合

  //清空集合
        c.clear();
        System.out.println("集合中元素的个数是:"+c.size());//0

(4) boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false.

     //再向集合中添加元素
        c.add("hello");//实际上hello的内存地址放了进去
        c.add("萧炎");
        c.add("药尘");
        c.add("白小纯");
        //判断集合中是否包含白小纯
        System.out.println(c.contains("白小纯"));

(5)boolean remove(Object o) 删除集合中的某个元素

    System.out.println(c.size());//4
        c.remove("hello");
        System.out.println(c.size());//3

(6)boolean isEmpty() 判断该集合中元素的个数是否为0

   //判断集合是否为空
        System.out.println(c.isEmpty());//false
        c.clear();
        System.out.println(c.isEmpty());//true

(7) Object[] toArray() 调用这个方法可以把集合转换成数组

      c.add(111);
        c.add("白小纯");
        c.add("顾长歌");
        c.add("changan");
        //转换成数组
        Object[] objs=c.toArray();
        for(int i=0;i<objs.length;i++)
        {
            Object o=objs[i];
            System.out.println(o);//自动调用toString()
        }

Collection集合迭代

  //以下的遍历方式/迭代方式,是所有Collection通用的一种方式
        //在Map集合中不能调用。在所有的Collection以及子类中使用
        //创建集合对象
        Collection c=new HashSet();//后面的集合无所谓,主要是看前面的Collection接口怎么遍历和迭代
        //添加元素
        c.add("白小纯");
        c.add(123);
        c.add(new Object());
        c.add("abc");
        //对集合Collection进行迭代/遍历
        //第一步:获取集合对象的迭代器对象Iterator
        Iterator it=c.iterator();
        //第二步:通过以上获取的迭代器对象开始迭代/遍历集合
        /*
        方法摘要
     boolean hasNext()
          如果仍有元素可以迭代,则返回 true。
     E next() 返回迭代的下一个元素。
       void remove() 从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。
         */
    while(it.hasNext())
    {
        //不管你存进去是什么,取出来都是Object
        Object obj=it.next();
        System.out.println(obj);
        //一直取,不判断会出现异常
    }

 笔记:迭代器是一个对象(it)

   Collection c=new ArrayList();
        //添加元素
        c.add(1);
        c.add(2);
        c.add(3);
        c.add(4);
        c.add(1);
        //迭代集合
        Iterator it=c.iterator();
        while (it.hasNext())
        {
            Object obj=it.next();
            /*if(obj instanceof  Integer)
            {
                System.out.println("Integer类型");
            }*/
            //存进去是什么类型,取出来还是什么类型。
            //只不过在输出的时候会转换成字符串。因为这里println会调用toString()方法
            System.out.println(obj);
        }

迭代器是通用的

HashSet 集合:无序不重复(存进去的和取出来的时候顺序不一定一样,存了100不可能再存100)

        Collection c1=new HashSet();
        c1.add(100);
        c1.add(200);
        c1.add(300);
        c1.add(90);
        c1.add(400);
        Iterator it1= c1.iterator();
        while(it1.hasNext())
        {
            System.out.println(it1.next());
        }

下面为打印结果 


深入Collection集合的contains方法:

boolean contains(Object o)

public class Collectionstest04 {
    public static void main(String[] args) {
        //创建集合对象
        Collection c=new ArrayList();
        //向集合中存储元素
        String s1=new String("abc");//s1=0x111
        c.add(s1);
        String s2=new String("def");//s2=0x222
        c.add(s2);

        //集合中元素的个数
        System.out.println(c.size());//2
        //新建的对象String
        String x=new String("abc");//x=0x555
        System.out.println(c.contains(x));//true
        //contains底层调用了equals方法,与字符串常量池无关,与开辟在堆区的内存地址也无关
     
    }
}

String 的equals方法重写了,比较的是内容

import java.util.*;
public class Collectionstest05 {
    public static void main(String[] args) {
        //创建集合对象
        Collection c=new ArrayList();
        //创建用户对象
        User u1=new User("jack");
        User u2=new User("jack");
        //加入集合
        c.add(u1);
        //判断集合是否包含u2
        //没有重写equals之前,这个结果是false
        System.out.println(c.contains(u2));//false,重写后是true

    }
}

class  User{
    private String name;
    public User(){
    }
    public User(String name)
    {
        this.name=name;
    }
    //重写equals方法
    //将来调用equals方法时,一定是调用这个重写的equals方法
    //这个equals方法的比较原理是:只要姓名一样就表示同一个用户
    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);
    }

}

存放在集合中的类型,一定要重写equals方法

        //创建集合对象
        Collection s=new ArrayList();
        //创建字符串对象
        String ss=new String("hello");
        //添加
        s.add(ss);
        //创建一个新的字符串对象
        String str=new String("hello");
        //删除str
        s.remove(str);
        //集合中的元素个数
        System.out.println(s.size());//0


public class Collectiontest05 {
    public static void main(String[] args) {
        //创建集合
        Collection c=new ArrayList();
        //注意:此时获取的迭代器,指向的是那是集合中没有元素状态下的迭代器。
        //一定要注意:集合结构只要发生改变,迭代器必须重新获取
        //当集合结构发生了改变,迭代器没有重新获取时,调用next()方法时:java.util.ConcurrentModificationException
        Iterator it=c.iterator();

        //添加元素
         c.add(1);
         c.add(2);
         c.add(3);
         //获取迭代器
       // Iterator it=c.iterator();
         while (it.hasNext())
         {
             //编写代码时,next()方法返回值类型必须是Object
             Object obj=it.next();
             System.out.println(obj);
         }
    }
}

remove()方法解析

import java.util.*;
public class Collectiontest06 {
    public static void main(String[] args) {
        Collection c2=new ArrayList();
        c2.add("abc");
        c2.add("def");
        c2.add("xyz");
        Iterator s2=c2.iterator();
        while (s2.hasNext())
        {
            Object obj=s2.next();
            //删除元素
            //删除元素之后,集合的结构发生了变化,应该重新去获取迭代器
            //但是,循环下一次的时候并没有重新获取迭代器,所以会出现异常java.util.ConcurrentModificationException
          //  c2.remove(obj);
           //出现异常的根本原因:集合中元素删除了,但是没有更新迭代器(迭代器不知道)
            //使用迭代器来删除可以吗?
            s2.remove();//删除的一定是迭代器指向的当前元素.
            System.out.println(obj);
        }
        System.out.println(c2.size());//0
    }
}

迭代器的解析:       Iterator it=c.iterator();

        获取的迭代器对象,迭代器用来遍历集合,此时相当于对当前集合的状态拍了一个快照

        迭代器迭代的时候会参照这个快照进行迭代。


List接口

import  java.util.*;
public class ListTest01 {
    public static void main(String[] args) {
        /*
        测试List接口常用方法
        1.list集合存储元素特点:有序可重复
        有序:List集合中的元素有下标
        从0开始,以1递增
        可重复:存储一个1,还可以再存储1
        2.List接口既然是Collection接口的子接口,那么List接口有自己"特色"的方法:
        void add(int index,E element)
        E get(int index)
        int indexOf(Object o)
        int lastIndexOf(Object o)
        E remove(int index)
        E set(int index,E element)
         */

        //创建List类型的集合
        //List mylist=new LinkedList();
        //List mylist=new Vector();
        List mylist=new ArrayList();

        //添加元素(向集合尾添加元素)
        mylist.add("A");
        mylist.add("B");
        mylist.add("C");
        mylist.add("D");
        mylist.add("A");
        //在列表指定位置添加元素(第一个参数是下标)
        //这个方法使用不多,因为对ArrayList集合来说效率比较低
        mylist.add(1,"king");

        //迭代
        Iterator it=mylist.iterator();
        while (it.hasNext())
        {
            Object obj=it.next();
            System.out.println(obj);
        }
        //根据下标获取元素
        Object first =mylist.get(0);
        System.out.println(first);

        //因为有下标,所以list集合有自己比较特殊的遍历方式
        //通过下标遍历。list集合的特有方式,set没有
        for (int i=0;i<mylist.size();i++)
        {
            Object obj=mylist.get(i);
            System.out.println(obj);//A king B C D A
        }

        //获取指定对象第一次出现的索引。
        System.out.println(mylist.indexOf("A"));//0

        //获取指定对象最后一次出现的索引.
        System.out.println(mylist.lastIndexOf("A"));//5

        //删除指定下标位置的元素
        //删除下标为0的元素
        mylist.remove(0);
        System.out.println(mylist.size());//5

        //修改指定位置的元素
        mylist.set(2,"soft");

        //遍历集合
        for(int i=0;i<mylist.size();i++)
        {
            Object obj=mylist.get(i);
            System.out.println(obj);//king B soft D A
        }
    }
}

ArrayList集合

ArrayList集合
1.默认初始化容量为10(JDK13:底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10.)
2.集合底层是一个Object[]数组
3.构造方法
 new ArrayList()
 new ArrayList(20)
4.ArrayList集合的扩容
 原容量的1.5倍   
 ArrayList集合底层是数组,怎么优化?
     尽可能少的扩容。因为数组扩容效率比较低,建议在使用ArrayList集合的时候预估计元素的个数,给定一个初始化容量
5.数组的优点:检索效率比较高
6. 数组的缺点:随机增删元素效率比较低(向数组末尾添加元素效率还是比较高的)
     另外,数组无法存储大数据量(很难找到一块非常巨大的连续的内存空间)
7.面试问题: 这么多的集合中,你用哪个集合最多?
 答:ArrayList集合
     因为往数组末尾添加元素,效率不受影响。
     另外,我们检索/查找某个元素的操作比较多。
8.ArrayList集合是非线程安全的(不是线程安全的集合)

位运算左移是乘以,右移是除以2的n次方倍,n为移动的倍数 

public class ArrayListTest01 {
    public static void main(String[] args) {

        //默认初始化容量10
        List  list1=new ArrayList();
        //集合中的size()方法是获取当前集合中元素的个数,不是获取集合的容量
        System.out.println(list1.size());//0

        //指定初始化容量
        //数组的长度是20
        List list2=new ArrayList(20);
        //集合的size()方法是获取当前集合中元素的个数,不是获取集合的容量
        System.out.println(list2.size());//0

    }
}

ArrayList集合的另一个构造方法

import java.util.*;
public class ArrayListTest02 {
    public static void main(String[] args) {
        //默认初始化容量10
        List list1=new ArrayList();

        //指定初始化容量100
        List list2=new ArrayList(100);

        //创建一个HashSet集合
        Collection c=new HashSet();
        c.add(100);
        c.add(200);
        c.add(30);

        //通过这个方法就可以将HashSet集合转换成List集合
        List list3=new ArrayList(c);
        for(int i=0;i<list3.size();i++)
        {
            System.out.println(list3.get(i));
        }
    }
}

LinkedList集合 

单向链表数据结构

import java.util.*;
public class LinkedListTest01 {
    public static void main(String[] args) {
        //LinkedList集合底层也是有下标的
        //注意:ArrayList之所以检索效率比较高,不是单纯的因为下标原因,是因为底层数组发挥的作用
        //LinkedList集合照样有下标,但是检索/查找某个元素的时候效率比较低,因为只能从头结点开始一个一个遍历
        LinkedList list=new LinkedList();
        list.add(1);
        list.add(2);
        list.add(3);

        for (int i=0;i<list.size();i++)
        {
            System.out.println(list.get(i));
        }
    }
}

链表的优点:

        由于链表上的元素在空间存储上内存地址不连续。

        所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。

        在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用LinkedList。

链表的缺点:

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

 注意:

        LinkedList集合没有初始化容量,最初这个链表中没有任何元素。first和last引用都是null.

        不管是LinkedList还是ArrayList,以后写代码时都不需要关心具体是哪个集合。

        因为我们要面向接口编程,调用的方法都是接口中的方法。

  List list3=new ArrayList();//这样写底层是数组
        List list4=new LinkedList();//这样写底层是双向链表

Vector集合

Vector:
1.底层也是一个数组
2.初始化容量:10
3.怎么扩容的?
    扩容之后是原容量的2倍
    10->20->40->80
4.Vector中所有方法都是线程同步的,都带有synchronized关键字
是线程安全的。效率比较低,使用较少.
public class VectorTest01 {
    public static void main(String[] args) {
    Vector vector=new Vector();
    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())
    {
        System.out.println(it.next());
    }
    List mylist=new ArrayList();//非线程安全的
    //转换成线程安全的
      Collections.synchronizedList(mylist);

      //mylist就是线程安全的了
        mylist.add("111");
        mylist.add("222");

    }
}

泛型机制

这是不适用泛型机制的情况下 

import java.util.*;
public class GenericTest01 {
    public static void main(String[] args) {
        //不适用泛型机制
        List mylist=new ArrayList();
        //准备对象
        Cat c=new Cat();
        Bird d=new Bird();
        //将对象添加到集合中
        mylist.add(c);
        mylist.add(d);

        //遍历集合,让动物移动
        Iterator it=mylist.iterator();
        while (it.hasNext())
        {
            //没有这个语法,通过迭代器取出的就是Object
            //Animal a=it.next();
            Object obj=it.next();
            //obj 中没有move方法,无法调用,需要向下转型
            if(obj instanceof Animal)
            {
                Animal a=(Animal) obj;
                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("鸟儿在飞");
    }
}

这是使用泛型机制的情况下

import java.util.*;
public class GenericTest01 {
    public static void main(String[] args) {
        //使用JDK5之后的泛型机制
        //使用泛型List<Animal>之后,表示List集合中只允许存储Animal类型的数据
        //用泛型来指定集合中存储的数据类型
        List<Animal> list=new ArrayList<Animal>();

        //指定List集合中只能存储Animal,那么存储String就编译报错了
        //这样用了泛型之后,集合中元素的数据类型更加统一了
        Cat c=new Cat();
        Bird d=new Bird();
        list.add(c);
        list.add(d);

        //获取迭代器
        Iterator<Animal> it=list.iterator();
        while(it.hasNext())
        {
          //使用泛型之后,每一次迭代返回的数据都是Animal类型。
            Animal a=it.next();
            //这里不需要进行强制类型转换
            a.move();
              //调用子类型特有的方法还是需要向下转型的
            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("鸟儿在飞");
    }
}

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

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

3.使用泛型的好处:

        第一:集合中存储的元素类型统一了。

        第二:从集合中取出的元素类型是泛型指定的类型,不需要大量的向下转型

4.泛型的缺点:

        导致集合中存储的元素缺乏多样性。


自动类型推断

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

import java.util.*;
public class GenericTest02 {
    public static void main(String[] args) {
        //自动类型推断
        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(123);
       strlist.add("abcdfasfl;salkfa;l");
       strlist.add("abcdefghijklmn");
     //遍历
        Iterator<String> it2=strlist.iterator();
        while(it2.hasNext())
        {
            String s1=it2.next();
            String s2=s1.substring(7);
            System.out.println(s2);
        }

    }
}


自定义泛型 

自定义泛型的时候,<>尖括号中的是一个标识符,随便写。
java源代码中经常出现的是:
    <E>和<T>
  E是element单词首字母
  T是Type单词首字母
public class GenericTest03<abc> {//abc标识符随便写
        public void ds(abc o)
        {
            System.out.println(o);
        }

    public static void main(String[] args) {
            //new对象的时候指定了泛型是:String类型
        GenericTest03<String >gt=new GenericTest03<>();
        //类型不匹配
        //gt.ds(11);
        gt.ds("ab");
        MyIterator<String > m=new MyIterator<>();
        String s1=m.get();
        MyIterator<Animal> a=new MyIterator<>();
        Animal a1=a.get();
        GenericTest03 gt3=new GenericTest03();
        gt3.ds(new Object());//定义了但是不用泛型就是Object
    }
}
class MyIterator<T>
{
    public T get()
    {
        return  null;
    }
}

foreach(增强for循环)

JDK5.0之后推出了一个新特性:叫做增强for循环,或者叫foreach

foreach有一个缺点:没有下标,在需要使用下标的循环中不建议使用增强for

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

HashSet集合特点     

HashSet集合:
1.无序不可重复 :存储时顺序和取出时顺序不同且不可重复。
2.放到HashSet集合中的元素实际上是放到HashMap集合的key部分了。
public class HashSetTest01 {
    public static void main(String[] args) {
        //演示HashSet集合特点
        Set<String > s=new HashSet<>();

        //添加元素
        s.add("hello3");
        s.add("hello1");
        s.add("world");
        s.add("hello2");
        s.add("hello6");
        s.add("hello4");

        //遍历
        for(String s1:s)
        {
            System.out.println(s1);
        }

    }
}


TreeSet集合特点

TreeSet集合存储元素特点:

        1.无序不可重复的,但是存储的元素可以自动按照大小顺序排序!

称为:可排序集合.

        2.这里的无序是指存进去的顺序和取出来的顺序不同,并且没有下标

import java.util.*;
public class TreeSetTest01 {
    public static void main(String[] args) {
    //创建集合对象
        Set<String >s=new TreeSet<>();
        s.add("A");
        s.add("B");
        s.add("Z");
        s.add("Y");
        s.add("D");

        //遍历
         for(String s1:s)
         {
             System.out.println(s1);
         }//A B D Y Z 从小到大自动排序 
    }
}

Map接口

        常用方法:

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

2.Map集合以key和value的方式存储数据:键值对

        key和value都是引用数据类型。

        key和value都是存储对象的内存地址。

        key起到主导的地位,value是key的一个附属品。

3.Map接口中常用的方法:

        V put(K key,V value)向Map集合中添加键值对

        V get(Object key) 通过key获取value

        void clear() 清空Map集合

        boolean containsKey(Object key)判断Map中是否包含某个key

        boolean containsValue(Object value)判断Map中是否包含某个value

        boolean isEmpty() 判断Map集合中元素个数是否为0

        Set<K> keySet() 获取Map集合中的所有key(所有的键是一个Set集合)

        V remove(Object key) 通过key删除键值对

        int size() 获取Map集合中键值对的个数

        Collection<V> values() 获取Map集合中所有的value,返回一个Collection

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

                        假设现在有一个Map集合,如下所示:

        map1集合对象

        key                                                             value

        ------------------------------------------------------------------------------------

        1                                                                  zhangsan

        2                                                                  lisi

        3                                                                   wangwu

        Set set=map1.entrySet();

        set集合对象

        1=zhangsan  [注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中元素的类型是Map.Entry<K,V>]

        [Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类,是Map中的静态内部类。        

        2=lisi

        3=wangwu

import java.util.*;
public class MapTest01 {
    public static void main(String[] args) {
        //创建Map集合对象
        Map<Integer,String > map=new HashMap<>();
        //向Map集合中添加键值对
        map.put(1,"zhangsan");//1在这里进行了自动装箱
        map.put(2,"lisi");
        map.put(3,"wangwu");
        //通过key获取value
      String value=  map.get(2);
        System.out.println(value);
        //获取键值对的数量
        System.out.println("键值对的数量:"+map.size());//3
        //通过key删除key-value
        map.remove(2);
        System.out.println("键值对的数量:"+map.size());//2
        //判断是否包含某个key
        System.out.println(map.containsKey(3));//true
        //判断是否包含某个value
        System.out.println(map.containsValue("wangwu"));//true
        //获取所有的value
        Collection<String>values=map.values();
        for(String s:values)
            System.out.println(s);//zhangsan wangwu
        //清空map集合
        map.clear();
        System.out.println(map.size());//0
        //判断是否为空
        System.out.println(map.isEmpty());//true
    }
}

Map集合的遍历

第一种方式:获取所有的key,通过遍历所有的key,来遍历所有的value

import java.util.*;
public class MapTest02 {
    public static void main(String[] args) {
        //第一种方式:获取所有的key,通过遍历key,来遍历value
    Map<Integer,String >map=new HashMap<>();
    map.put(1,"zhangsan");
    map.put(2,"lisi");
    map.put(3,"wangwu");
    map.put(4,"zhaoliu");
    //遍历map集合
    //获取所有的key,所有的key是一个Set集合
      Set<Integer> keys=map.keySet();
      //遍历key,通过key获取value
      //迭代器
       Iterator<Integer> it=keys.iterator();
       while (it.hasNext())
       {
           //取出其中一个key
           Integer key=it.next();
           //通过key获取value
           String value=map.get(key);
           System.out.println("key ="+value);
       }
     //foreach形式
     for(Integer i:keys)
     {
         System.out.println("key="+map.get(i));
     }
    }
}

 第二种方式:

import java.util.*;
public class MapTest02 {
    public static void main(String[] args) {
        //第一种方式:获取所有的key,通过遍历key,来遍历value
    Map<Integer,String >map=new HashMap<>();
    map.put(1,"zhangsan");
    map.put(2,"lisi");
    map.put(3,"wangwu");
    map.put(4,"zhaoliu");
  //第二种方式:Set<Map.Entry<K,V>> entrySet()
     //以上这个方法是把Map集合直接转换成Set集合
     //Set集合中的数据类型是:Map.Entry
        Set<Map.Entry<Integer,String>> set=map.entrySet();
     //遍历Set集合,每一次取出一个value
        Iterator<Map.Entry<Integer,String>> it1=set.iterator();
        while(it1.hasNext())
        {
            Map.Entry<Integer,String> Node=it1.next();
            Integer key=Node.getKey();
            String  value=Node.getValue();
            System.out.println(key+"="+value);
        }
    //foreach
        for(Map.Entry<Integer,String> Node:set )
        {
            System.out.println(Node.getKey()+"----->"+Node.getValue());
        }

    }
}

 第二种方式效率比较高,这种方式key和value都是直接从Node对象获取的属性值,这种方式比较适用于大数据量


哈希表数据结构

  HashMap集合:

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

2.哈希表是一个怎样的数据结构?

        哈希表是一个数组和单向链表的结合体

        数组:在查询方面效率很高,随机增删方面效率很低

        单向链表:在随机增删方面效率很高,在查询方面效率很低

        哈希表将以上两种数据结构融合在一起,充分发挥他们各自的优点

哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。)

 HashMap集合的默认初始化容量为16,默认加载因子是0.75

        这个默认加载因子是当HashMap集合底层的数组的容量达到75%的时候,数组开始扩容

HashMap集合初始化容量必须是2的倍数,这也是官方推荐的。

这是因为达到散列分布均匀,为了提高HashMap集合的存取效率所必须的。


HashMap集合的底层源码:
public class HashMap{

    //HashMap底层实际上就是一个数组。(一维数组)
    Node<K,V>[] table;

    //静态的内部类HashMap.Node
    static class Node<K,V>{
        final int hash;//哈希值(哈希值是key的hashCode()方法的执行结果。哈希值通过哈希函数/算法,可以转换存储成数组的下标。)
        final K key;//存储到Map集合中的那个key
        V value;//存储到Map集合中的那个value
        Node<K,V> next;//下一个结点的内存地址

最主要掌握的是: map.put(k,v)  v=map.get(k) 

map.put(k,v)实现原理:

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

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

        然后通过哈希函数/算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上了。如果说下标对应的位置上有链表,此时会拿着k和链表上每一个结点中的k进行equals,如果所有的equals方法返回的都是false,那么这个新的结点将被添加到链表的末尾。如果其中有一个equals返回了true,那么这个结点的value将会被覆盖。

public class HashMapTest01 {
    public static void main(String[] args) {
        //测试HashMap集合key部分元素的特点。
        //Integer是key,它的hashCode和equals都重写了
        Map<Integer,String> map=new HashMap<>();
        map.put(111,"zhangsan");
        map.put(222,"lisi");
        map.put(333,"wangwu");
        map.put(444,"zhaoliu");
        map.put(111,"king");//key重复的时候value会自动覆盖

        System.out.println(map.size());//4

        //遍历map集合
        Set<Map.Entry<Integer,String>> set=map.entrySet();
        for (Map.Entry<Integer,String> s:set)
        {
            //验证结果,HashMap集合key部分元素:无序不可重复。
            System.out.println(s.getKey()+"="+s.getValue());
        }
    }
}

 

 

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

        先调用k的hashCode()方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置上,如果这个位置上什么也没有,返回null。如果这个位置上有单向链表,那么会拿着参数k和单向链表上的每个结点的k进行equals,如果所有的equals方法返回false,那么get方法返回null,只要其中有一个结点的k和参数k equals的时候返回true,那么此时这个结点的value就是我们要找的value,get方法最终返回这个要找的value.

    哈希表的增删是在链表上完成,查询也不需要都扫描,只需要扫描部分即可。

        哈希表没有纯数组或纯链表的查询或增删效率高(相当于一个中间体)

HashMap集合的key会先后调用两个方法,一个方法是hashCode(),一个方法是equals(),那么这两个方法都需要重写。(equals()方法必须重写,因为其默认比较的是内存地址,而我们实际需要比较的是内容)

HashMap集合的key部分特点:

        无序,不可重复。

        因为不一定挂到哪一个单向链表上。

        不可重复是怎么保证的?equals方法来保证hashMap集合的key不可重复。

如果key重复了,value会覆盖掉。

       放在hashMap集合key部分的元素其实是放到hashSet集合中了。

        所以hashSet集合中的元素也需要重写hashCode()+equals()方法

注意:同一个单向链表上所有结点的hash值相同,因为他们的数组下标一样。

        同一个链表上k和k的equals方法肯定返回的是false,都不相等.

如果所有的hashCode()方法返回的是固定的值,那么会导致哈希表底层变成了纯单向链表。

这种情况我们称为散列分布不均匀

如果所有的hashCode()方法返回的值都不相同, 这时候底层变成了纯一维数组,这时候也是散列分布不均匀

          散列分布均匀:100个元素,10个单向链表,每个单向链表上10个结点,这时候是散列分布均匀的。


equals和hashCode的重写

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

1.向Map集合中存,以及Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法!

equals方法有可能调用,也有可能不调用。

    拿put(k,v)举例,什么时候equals不会被调用?

        k.hashCode()方法返回哈希值,

        哈希值经过哈希算法转换成数组下标。

        数组下标位置上如果是null,equals不需要执行

    拿get(k)举例,什么时候equals不会调用?

          k.hashCode()方法返回哈希值,

        哈希值经过哈希算法转换成数组下标。

        数组下标位置上如果是null,equals不需要执行        

2.注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写。

并且equals方法返回的是true,hashCode()方法返回的值必须一样。

        equals方法返回true表示两个对象相同,在同一个单向链表上比较。

        那么对于同一个单向链表上的结点来说,他们的哈希值都是相同的。

        所以hashCode()方法的返回值也应该相同。

3.hashCode()方法和equals方法不用研究了,直接使用IDEA工具生成,但是这两个方法需要同时生成。

直接alt+insert

 

 4.终极结论:

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

  5.对于哈希表数据结构来说:

        如果o1和o2的hash值相同,一定是放到同一个单向链表上

         当然如果o1和o2的hash值不同,但由于哈希表算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。

import java.util.*;
public class HashMapTest02 {
    public static void main(String[] args) {
    students s1=new students("zhangsan");
    students s2=new students("zhangsan");
    //重写equals方法之前是false
      //  System.out.println(s1.equals(s2));//false
    //重写equals方法之后是true
        System.out.println(s1.equals(s2));//true(s1和s2表示相等)
        System.out.println("s1的hashCode="+s1.hashCode());//1324119927 重写hashCode()之后-1432604525
        System.out.println("s2的hashCode="+s2.hashCode());//81628611   重写hashCode()之后-1432604525

    //s1.equals(s2)结果已经是true了,表示s1和s2是完全一样的,相同的,那么往HashSet集合中放的话,
        //按理说只能放进去一个。(HashSet集合特点:无序不可重复)
        Set<students> student=new HashSet<>();
        student.add(s1);
        student.add(s2);
        System.out.println(student.size());//这个结果按理说应该是1,但实际上是2.显然不符合HashSet集合存储特点
    }
}

重写之后


Java8对HashMap集合的改进 

在JDK8之后,如果哈希表单向链表中元素个数超过8个,单向链表这种数据结构会变成红黑树数据结构。当红黑树上的结点数量小于6时,会重新把红黑树变成单向链表数据结构。

这种方式也是为了提高检索效率,二叉树的检索会再次缩小扫描范围。提高效率。 


有可能面试遇到这个问题:

 HashMap集合允许key部分为null(但是要注意:HashMap集合的key null值只能有一个

import java.util.*;
public class HashMapTest03 {
    public static void main(String[] args) {
        Map map=new HashMap();
        map.put(null,null);
        System.out.println(map.size());//1
    }
}

HashTable集合

         HashTable的key可以为null吗?

       HashTable集合的key和value都是不能为null的。

相反:HashMap集合的key和value都是可以为null的。 

1.HashTable方法都带有synchronized:线程安全的。

线程安全有其他方案,这个HashTable对线程的处理导致效率较低,使用较少了。

2. HashTable和HashMap一样,底层都是哈希表结构。

HashTable的初始化容量为11,默认加载因子是0.75f,

HashTable的扩容是:原容量*2+1


属性类Properties类

       Properties是一个Map集合,继承HashTable,Properties的key和value都是String类型。

       Properties被称为属性类对象。

       Properties是线程安全的。

import java.util.*;
public class PropertiesTest01 {
    public static void main(String[] args) {
        //创建一个Properties类对象
        Properties pro=new Properties();
        //需要掌握Properties的两个方法,一个存一个取
        pro.setProperty("driver","sjaflfjsalfj;sa");
        pro.setProperty("safjjlsa","sfajlfas");

        //通过key获取value
       String ur = pro.getProperty("driver");//如果输错的话会返回null
        String uu=pro.getProperty("safjjlsa");

    
        System.out.println(ur);//sjaflfjsalfj;sa
        System.out.println(uu);//sfajlfas
    }
}

TreeSet集合

1.TreeSet集合底层实际上是一个TreeMap

2.TreeSet集合底层是一个二叉树

3.放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了

4.TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序

                称为:可排序集合

例如编写程序从数据库中取出数据,在页面展示用户信息的时候按照生日升序或降序,这个时候

可以使用TreeSet集合,因为TreeSet集合放进去,拿出来就是有顺序的。

import java.util.*;
public class TreeSetTest02 {
    public static void main(String[] args) {
        //创建一个TreeSet集合
        TreeSet<String> ts=new TreeSet<>();
        ts.add("zhangsan");
        ts.add("lisi");
        ts.add("wangwu");
        //遍历
        for(String s:ts)
        {   //升序
            System.out.println(ts);//lisi wangwu zhangsan
        }
        TreeSet<Integer> ts1=new TreeSet<>();
        ts1.add(1);
        ts1.add(100);
        ts1.add(30);
        for(Integer i:ts1)
        {
            System.out.println(i);//1 30 100
        }
    }
}

TreeSet自定义数据类型比较规则 

TreeSet无法对自定义类型数据排序:因为没有指定对象之间的比较规则

谁大谁小没有说明-->出现异常。

        出现这个异常的原因是:

      Customer类并没有实现java.lang.Comparable接口。

import java.util.*;
public class TreeSetTest03 {
    public static void main(String[] args) {
    Customer c1=new Customer(11);
    Customer c2=new Customer(30);
    Customer c3=new Customer(17);
    Customer c4=new Customer(25);

    //创建TreeSet集合
        TreeSet<Customer> customers=new TreeSet<>();
        //添加元素
        customers.add(c1);
        customers.add(c2);
        customers.add(c3);
        customers.add(c4);

        //遍历
        for(Customer c:customers)
        {
            System.out.println(c);
        }
    }
}
//放在TreeSet集合中的元素需要实现java.lang.Compare接口
// 并且实现compareTo方法,equals可以不写。
class Customer implements Comparable<Customer>{
    int age;
    public Customer(int age)
    {
        this.age=age;
    }
    //需要在这个方法中编写比较的逻辑/规则
    //k.compareTo(k.key)
    //拿着参数k和集合中的每一个k进行比较,返回值可能是>0 <0 =0
    @Override
    public int compareTo(Customer c) {//c1.compareTo(c2) this是c1
        /*int age1=this.age;
        int age2=c.age;
        if(age1==age2)
        {
            return 0;
        }else if (age1>age2)
        {
            return 1;
        }
        else
        {
            return -1;
        }*/
        return this.age-c.age;//=0 >0 <0
    }
    public  String toString()
    {
        return "Customer[age="+age+"]";
    }
}

 compareTo方法中的比较规则升序,则结果是升序输出

 或者有两个属性:年龄和姓名时

compareTo方法的返回值很重要

        返回0表示相同,value会覆盖。

        返回>0,会继续在右子树上找。

        返回<0,会继续在左子树上找。


自平衡二叉树数据结构 

1.TreeSet/TreeMap 是自平衡二叉树,遵循左小右大原则。

2.遍历二叉树的时候有三种方式:

        前序:根左右

        中序: 左根右

        后序:左右根

3.TreeSet/TreeMap集合采用的是:中序遍历方式

Iterator迭代器采用的是中序遍历方式。

       100 200 50 60 80  120  140  130 135 180 666 40 55

采用中序遍历取出:40 50 55 60 80 100 120 130 135 140 180  200 666


实现比较接口

import java.util.*;
public class TreeSetTest06 {
    public static void main(String[] args) {
        //创建TreeSet集合的时候需要使用这个比较器
       // TreeSet<WuGui> s=new TreeSet<>();这样不行,没有通过构造方法传递一个比较器进去
        //给构造方法传递一个比较器
        TreeSet<WuGui> s=new TreeSet<>(new WuGuiComparator());
        s.add(new WuGui(10));
        s.add(new WuGui(100));
        s.add(new WuGui(800));
        s.add(new WuGui(30));
        for (WuGui wugui:s)
        {
            System.out.println(wugui);
        }
    }
}
class WuGui{
    int age;
    public WuGui(int age)
    {
    this.age=age;
    }

    @Override
    public String toString() {
        return "小乌龟{" +
                "age=" + age +
                '}';
    }
}

//单独在这里编写一个比较器
//比较器实现java.util.Comparator接口。(Comparator是java.lang包下的。Comparator是java.util包下的。)
class  WuGuiComparator implements Comparator<WuGui>
{
    @Override
    public int compare(WuGui o1,WuGui o2)
    {
        //指定比较规则,按年龄排序
        return o1.age-o2.age;
    }
}

 也可以搞匿名内部类的形式

TreeSet<WuGui> s=new TreeSet<>(new Comparator<WuGui>() {
            @Override
            public int compare(WuGui o1, WuGui o2) {
                return o1.age-o2.age;
            }
        }  );

最终的结论:

        放到TreeSet或者TreeMap集合key部分的元素想做到排序,包括两种方式:

                第一种:放在集合中的元素实现java.lang.Comparable接口。

                第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。
Comparable和Comparator怎么选择呢?

     当比较规则不会发生改变时,或者说当比较规则只有1个的时候,建议实现Comparable接口。

     如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。


Collections工具类           

import java.util.*;
public class Collectiontest09 {
    public static void main(String[] args) {
        //ArrayList集合不是线程安全的
        List<String> list = new ArrayList();

        //变成线程安全的
        Collections.synchronizedList(list);

        //排序
        list.add("av");
        list.add("cc");
        list.add("ff");
        Collections.sort(list);
        for(String s:list)
        {
            System.out.println(s);
        }

        List<WuGui2> wuGuis=new ArrayList<>();
        wuGuis.add(new WuGui2(1000));
        wuGuis.add(new WuGui2(1111));
        wuGuis.add(new WuGui2(400));
        //注意:对List集合中元素排序,需要保证List集合中的元素实现了:Comparable接口。
        Collections.sort(wuGuis);
        for(WuGui2 ss:wuGuis)
        {
            System.out.println(ss);
        }
        //对Set集合怎么排序呢?
        Set<String> set=new HashSet<>();
        set.add("avaj");
        set.add("king");
        set.add("kingsoft");
        //将Set集合转换为List
        List<String> mylist=new ArrayList<>(set);
        Collections.sort(mylist);
        for (String a:mylist)
        {
            System.out.println(a);
        }
        //这种方法也可以排序
        //Collections.sort(list集合,比较器对象)
    }
}
class  WuGui2   implements Comparable<WuGui2>
{
    int age;
    public WuGui2(int age)
    {
        this.age=age;
    }
    @Override
    public int compareTo(WuGui2 o)
    {
        return this.age-o.age;
    }

    @Override
    public String toString() {
        return "乌龟{" +
                "age=" + age +
                '}';
    }
}


这两天肝集合比较疲惫,不过也比较充实嘿嘿。

        学习如逆水行舟,不进则退。和小吴一起加油!

相关文章:

  • CKA考题 [k8s1.21]
  • AcWing第 70 场周赛题解
  • 读FFA-net: Feature Fusion Attention Network for Single Image Dehazing
  • 测试需求平台4-Flask实现API服务入门实战
  • js单行代码-----dom
  • 模型压缩常用方法简介
  • css常用属性
  • 【Android】Android Binder进程间通信AIDL示例与过程分析
  • C#教程 - 模式匹配(Pattern matching)
  • 动手学习深度学习 05:深度学习计算
  • 状体模式-优雅解决物流状态
  • CTF之加密解密训练
  • gnome-terminal用法解析
  • ceres优化库的使用
  • TypeScript基础教程
  • 【node学习】协程
  • Android组件 - 收藏集 - 掘金
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • download使用浅析
  • ES6 学习笔记(一)let,const和解构赋值
  • gcc介绍及安装
  • HashMap剖析之内部结构
  • JavaScript 基础知识 - 入门篇(一)
  • JSDuck 与 AngularJS 融合技巧
  • leetcode98. Validate Binary Search Tree
  • React Native移动开发实战-3-实现页面间的数据传递
  • React-生命周期杂记
  • React组件设计模式(一)
  • 闭包--闭包之tab栏切换(四)
  • 工作中总结前端开发流程--vue项目
  • 关于 Linux 进程的 UID、EUID、GID 和 EGID
  • 聚簇索引和非聚簇索引
  • 坑!为什么View.startAnimation不起作用?
  • 聊聊redis的数据结构的应用
  • 如何打造100亿SDK累计覆盖量的大数据系统
  • 项目实战-Api的解决方案
  • 一些css基础学习笔记
  • 找一份好的前端工作,起点很重要
  • #Ubuntu(修改root信息)
  • #我与Java虚拟机的故事#连载07:我放弃了对JVM的进一步学习
  • (1)安装hadoop之虚拟机准备(配置IP与主机名)
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (5)STL算法之复制
  • (阿里巴巴 dubbo,有数据库,可执行 )dubbo zookeeper spring demo
  • (二)WCF的Binding模型
  • (十六)Flask之蓝图
  • (转)jdk与jre的区别
  • (转)项目管理杂谈-我所期望的新人
  • ******之网络***——物理***
  • ... 是什么 ?... 有什么用处?
  • .NET CORE Aws S3 使用
  • .net 中viewstate的原理和使用
  • .NET/C# 编译期间能确定的相同字符串,在运行期间是相同的实例
  • .NET性能优化(文摘)
  • .Net转Java自学之路—SpringMVC框架篇六(异常处理)