Java 集合

一、概述

  1. 集合实际上就是一个容器,可以用于容纳其他类型的数据
  2. 集合是对象,在其内只能存储引用数据类型(即存对象的内存地址),即集合中也可以再装集合。
  3. 集合中若存储基本数据类型,会通过自动装箱成包装类进行存储
  4. 应用场景:假设连接数据库,数据库中有n条数据。假如把n条数据查询出来,java程序会将n条数据封装成n个java对象,然后将它们放在一个集合当中,然后将集合通过接口传输到前端,前端遍历集合即可将一个个数据对象渲染到页面上。
  5. 与数组的区别
    • 数组存储的元素必须是同一个数据类型,集合存储的对象可以是不同数据类型;
    • 数组长度不可变,集合长度可变;
    • 数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型;
    • 数组运行较快 ,而集合方法功能比数组强大 ;
  6. 不同的集合,底层对应不同的数据结构。往不同的集合中存储元素,等于将数据放置于不同的数据结构中;而数据结构即数据存储的方式,常见有数组、二叉树、链表、哈希表…。使用不同的集合等同于使用不同的数据结构
  7. 所有集合类和集合接口均处于JDK中java.util包下
  8. 分类
    • 单个方式存储元素
      • 该类集合超级父接口为:java.util.Collection
    • 以键值对方式存储元素
      • 该类集合超级父接口为:java.util.Map
    • 图示:

二、集合继承结构图

  • Iterable接口中有iterator()方法,被继承到了Collection接口中

  • Iterator it=“Collection 对象”.iterator();

  • Collection通过调用iterator()方法得到迭代器对象,it是迭代器对象,用于迭代(遍历)(一个个拿出来)该集合中的元素

  • it迭代器对象中存在三个方法用于完成迭代:hasNext()、next()、remove()

  • Collection与Map之间没有继承关系

    下图体现出的为主要常用内容,而并非所有内容

Collection:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5w4D622B-1621408696919)(/Users/wwc/Library/Application Support/typora-user-images/image-20210518211703573.png)]

Map:

  • 总结(所有的实现类)
    • ArrayList:底层是数组。
    • LinkedList:底层是双向链表。
    • Vector:底层是数组,线程安全的,效率较低,使用较少。
    • HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分了。
    • TreeSet:底层是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分了。
    • HashMap:底层是哈希表。
    • Hashtable:底层也是哈希表,只不过线程安全,效率较低,使用较少。
    • Properties:是线程安全的,并且key和value只能存储字符串String。
    • TreeMap:底层是二叉树。TreeMap集合的key可以自动按大小顺序排序。
  • List集合存储元素的特点:
    • 有序可重复
    • 有序:存进去的顺序和取出的顺序相同,每一个元素都有下标
    • 可重复:存进去一个值,可以再存储一个相同的值
  • Set(Map)集合存储元素的特点:
    • 无序可重复
    • 无序:存进去的顺序和取出的顺序不一定相同。另外Set集合中元素没有下标
    • 不可重复:存进去一个值,就不可以再存储一个相同的值了
  • SortedSet(SortedMap)集合存储元素的特点:
    • 无序不可重复,元素可排序
    • 无序:存进去的顺序和取出的顺序不一定相同。另外Set集合中元素没有下标
    • 不可重复:存进去一个值,就不可以再存储一个相同的值了
    • 可排序:可以按照大小顺序排列
  • Map集合的key,就是一个Set集合。往Set集合中放数据,实际上放到了Map集合的key部分。

三、Collection

  1. 存放元素种类:不指定"泛型"时,可以存放Object的所有子类型。指定"泛型"后,只能存储某个具体的类型。但集中不能存储基本数据类型,也不能存java对象,只是存储java对象的内存地址。

  2. Collection常用方法

    //创建一个集合对象,但不可以new Collection(),因为接口是抽象的,无法实例化
            //多态
            Collection c = new ArrayList();
    
            //测试Collection接口中的常用方法
            //①boolean add(Object e)向集合中添加元素,添加元素到末尾
            c.add(1200);//自动装箱(Java 5的新特性),实际上是放进去了一个对象的内存地址。Integer x=new Integer(1200);
            //②int size()获取集合中元素的个数
            c.size();
            //③void clear()清空集合
            c.clear();
            //④boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false
            c.contains("xxx");
            //⑤boolean remove(Object o) 删除集合中某个元素
            c.remove("xxx");
            //⑥boolean isEmpty() 判断该集合中元素的个数是否为0
            c.isEmpty();
            //⑦Object[] toArray()调用这个方法可以把集合转换成数组
            Object[] objs = c.toArray();
    
  3. Collection集合迭代

    • 获取到的迭代器指向是当前集合元素状态下的迭代器。因此只要集合结构发生改变,迭代器必须重新获取
    • 当集合结构发生了改变,迭代器没有重新获取时,调用next()方法时:java.util.ConcurrentModificationException

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VvhAYLm1-1621408696920)(/Users/wwc/Library/Application Support/typora-user-images/image-20210428153258617.png)]

    //以下遍历方式/迭代方式,是所有Collection通用的一种方式。
            //在Map集合中不能用。在所有的Collection以及子类中使用。
            //创建集合对象
            Collection c = new ArrayList();
    
            //添加元素
            c.add("abc");
            c.add("bcd");
            c.add("cde");
    
            //对集合Collection进行遍历/迭代
            //①获取集合对象的迭代器对象Iterator
            Iterator it = c.iterator();
            //②通过以上获取的迭代器对象开始迭代/遍历结合。
    
            /* 以下两个方法使迭代器对象Iterator中的方法:
                boolean hasNext() 如果仍有元素可以迭代,则返回true。
                Object next() 返回迭代的下一个元素
             */
            while (it.hasNext()){
                //无论存的是什么,取出来统一是Object。存进去是什么类型,取出来还是什么类型,println则会调toString()方法
              	//即next方法返回值必须要Object接收
                Object obj = it.next();
            }
    
  4. contains方法

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

    2. 存放在集合中的元素需要重写equals方法

    //创建集合对象
            Collection c = new ArrayList();
    
            //向集合中存储元素
            String s1 = new String("abc");
            c.add(s1);
    
            String s2 = new String("def");
            c.add(s2);
    
            String x=new String("abc");
            System.out.println(c.contains(x));//true contains方法底层调用了equals方法
    				//用(==)进行比较的时候,比较的是他们在内存中的存放地址
    				//在没有重写equals方法的情况下,比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,在String中重写了equals方法变成了比较内容而非内存地址(即要重写equals方法)
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r1lUtu8o-1621408696921)(/Users/wwc/Library/Application Support/typora-user-images/image-20210428160111333.png)]

  5. remove

    • 底层也调用了equals()方法,存进其中的元素需要重写equals()方法。
    • 在迭代集合元素的过程中,不能调用集合对象的remove方法,删除元素:c.remove(o),因为当集合结构发生了改变,而迭代器没有重新获取时,会出现异常java.util.ConcurrentModificationException
    • 迭代器.remove(); 调用迭代器中的remove方法进行删除。
Iterator it = c.iterator();//获取的迭代器对象用于遍历集合,相当于对当前集合状态做了一个截图,迭代器迭代时会参照该截图进行迭代,若不能对应则会报错。
while (it.hasNext()){
            it.next();
            it.remove();
  					//使用迭代器删除其中所有的元素,会自动更新迭代器,并且更新集合
}

//直接通过集合去删除元素,没有通知迭代器。导致迭代器与集合结构状态不一致,会报错
//while(it.hasNext()){
  	//Object o=it.next();
  	//c.remove(o)  
//}

四、List

  1. List集合存储元素特点:有序可重复;

    • 有序:List集合中的元素有下标。从0开始,以1递增
    • 可重复:存储一个1,还可以再存储1
  2. List既然是Collection接口的子接口,那么肯定List接口有自己的特有方法

    • void add(int index,Object element) 向指定位置添加数据(第一个参数是下标)
    • Object get(int index)
    • int indexOf(Object o)
    • int LastIndexOf(Object o)
    • Object remove(int index)
    • Object set(int index,Object element)
    List myList = new ArrayList();
    //默认都是向集合的末尾添加元素。(使用较多,效率较高)
    myList.add("A");
    myList.add("B");
    myList.add("C");
    myList.add("D");
    
    //①在列表的指定位置插入指定元素(第一个参数是下标)
    myList.add(1,"KING");
    //②根据下标获取元素
    Object o1 = myList.get(1);
    //③.1通过下标遍历list集合(List集合特有的方式,Set没有)
    for (int i = 0; i < myList.size(); i++) { 
        myList.get(i);
    }
    //③.2通过迭代器遍历list集合,所以Collection通用
    Iterator it = c.iterator();       
    while (it.hasNext()){
                Object obj = it.next();
    }
    //③.3通过forEach遍历
    for(String s:list){
      System.out.println(s);
    }
    //④指定对象第一次出现处的索引
    myList.indexOf("king");
    //⑤获取指定对象最后一次出现处的索引
    myList.lastIndexOf("king");
    //⑥删除指定下标位置的元素
    myList.remove(0);
    //⑦修改指定位置的元素
    myList.set(2,"soft");
    

    3.英语单词拓展

    • 增:add、save、new
    • 删:delete、drop、remove
    • 改:update、set、modify
    • 查:find、get、query、select

五、ArrayList(使用频率最多)

  • 默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量为10.)
  • 集合底层是一个Object[ ]数组
  • 构造方法:
    • new ArrayList();
    • new ArrayList(20); 可指定容量
    • new ArrayList(Collection);可以将其他Collection集合转换成ArrayList;
  • 集合的size是获取当前集合的元素个数,而非获取集合的容量
  • ArrayList集合的扩容原理:老容量+老容量右移>>1位(老容量/2),即新容量=1.5老容量;ArrayList底层是数组,通过尽可能少的扩容进行优化,因为数组扩容效率较低,应尽早预估元素的个数,给定初始化容量
  • 数组优点:通常检索/查找某个元素的操作比较多。用数组效率高(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,然后知道下标,可通过数学表达式计算出元素的内存地址,所以检索效率最高。);
  • 数组缺点:随机增删元素效率比较低。另外数组无法存储大数据量。(很难找到一块非常巨大的连续内存空间)
  • 向数组末尾添加元素,效率很高,不受影响。因为在末尾插入元素不需要移动其他元素。
  • ArrayList集合是非线程安全的。
  • 适用于检索较多的业务场景

六、LinkedList

  • 内存地址不连续,充分利用每一个地址空间,因此LinkedList比ArrayList大,能存大数据
  • 查询效率较低(每一次查找某个元素都需要从头节点开始往下遍历)
  • 优点:随机删除元素效率较高(增删元素不涉及到大量元素位移),随机增删业务较多时,优先使用
  • 缺点:不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,查到位置,因此效率较低。
  • LinkedList集合底层也是有下标的 ,但不在检索/查找某个元素时发挥作用。只能从头节点开始遍历。
  • 单向链表图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XtqTkgWD-1621408696922)(/Users/wwc/Library/Application Support/typora-user-images/image-20210429144630467.png)]

  • 单向链表的实现(通过后面的节点无法找到前面的节点)
//Node类
/*
    单链表中的节点。
    节点是单向链表中基本的单元。
    每一个节点Node都有两个属性。
    一个属性:是存储的数据。
    另一个属性:是下一个节点的内存地址
 */

public class Node {
    //存储的数据
    Object element;
    
    //下一个节点的内存地址
    Node next;

    public Node() {
    }

    public Node(Object element, Node next) {
        this.element = element;
        this.next = next;
    }
}
//Link类
/*
    链表类
 */
public class Link {
    //头节点
    Node header=null;
    
    private int size=0;
    
    public int size(){
        return size;
    }
    
    //向链表中添加元素的方法(向末尾添加)
    public void add(Object data){
        //创建一个新的节点对象
        //让之前单链表的末尾节点next指向新节点对象
        //有可能这个元素是第一个,也可能是第二个,也可能是第三个
        if(header==null){
            //说明还没有节点
            //new一个新的节点对象,作为头节点对象。
            //这个时候的头节点既是一个头节点,又是一个末尾节点。
            header=new Node(data,null);
        }else {
            //说明头节点不是空,已经存在
            //找出当前末尾节点,让当前末尾节点的next是新节点
            Node currentLastNode=findLast(header);
            currentLastNode.next=new Node(data,null);
        }
        size++;
    }

    /**
     * 专门查找末尾节点的方法
     * 递归查找
     */
    private Node findLast(Node node){
        if(node.next==null){
            return node;
        }
        return findLast(node.next);
    }
    
    //删除链表中某个数据的方法
    public void remove(Object obj){
        
    }
    
    //修改链表中某个数据的方法
    public void modify(Object newObj){
        
    }
    
    //查找链表中某个元素的方法
    public int find(Object obj){
        return 1;
    }
    
}
//测试类
public class Test {
    public static void main(String[] args) {
        Link link=new Link();
        link.add("abc");
        link.add("bcd");
        link.add("xyz");
        System.out.println(link.size());
    }
}
  • 双向链表图:(中间通过中转变量实现,first始终指向第一个节点,last始终指向最后一个节点)

  • LinkedList无初始化容量,其内无任何元素,一开始first和last都是null
  • 对比ArrayList与LinkedList
    • ArrayList:检索元素优势大。(末尾添加元素效率高)
    • LinkedList:随机增删元素优势大。
    • 加元素都是往末尾加的多,因此ArrayList比LinkedList更常用。

七、Vector

  • Vector底层也是数组

  • 初始化容量是10,容量满了后,扩容为原来的2倍

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

  • 将线程不安全的ArrayList集合转换成线程安全,使用集合工具类

    • java.util.Collection 是集合接口

    • java.util.Collections 是集合工具类

    • 具体如下

    • List list=new ArrayList();//非线程安全
      Collections.synchronizedList(list);//此为线程安全
      

八、泛型

  • 泛型:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型
  • 把类型当作是参数一样传递
  • <数据类型>只能是引用类型
  • 适用于多人合作编程时规范类型
//不使用泛型时
List list=new ArrayList();
List<Object> list = new ArrayList<>();//与上一句完全一致
//instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型
//多用于强制类型转换时,如if(对象 instanceof 类){},保持程序的严谨


//如下,使用泛型List<Animal>之后,表示List集合中只允许存储Animal类型的数据
//用泛型指定集合中存储的数据类型,不符合则编译报错
List<Animal> list=new ArrayList<Animal>();
//此时可以使用泛型迭代器
Iterator<Animal> it=list.iterator();
while(it.hasNext()){
  //使用泛型后,每次迭代返回的都是Animal类型,无需再进行强制类型转换,直接调用,否则需要类型转换
  //在多态中,父类型引用指向子类型对象,只能调用子类中也重写过的父类方法,且调用的是子类重写了的方法。
  Animal a=it.next();
  //调用子类特有的方法还是需要类型转换的
}
  • 泛型机制只在程序编译阶段起作用,供编译器参考,运行阶段泛型无用。
  • 优点:集合中存储的元素类型统一了,从集合中取出的元素类型是泛型指定的类型,不需要进行大量的类型转换
  • 缺点:导致集合中存储的元素缺乏多样性。
  • 自动推断类型机制(钻石表达式):
//JDK8之后ArrayList<>可以省略尖括号中的内容,会自动推断
List<Animal> list=new ArrayList<>();
  • 自定义泛型:泛型定义在类上,用户使用该类的时候,才把类型明确下来;在类上定义的泛型,在类的方法中也可以使用;
//自定义泛型时,<>尖括号中内容的是一个标识符,随便写,通常写作T/E,Type/Element,
//再new对象的时候才用标识符接收指定泛型,如指定参数类型
public class VectorDemo<T> {
  public void doShout(T e){
      System.out.println(e);//abc
  }
}
//测试类中
public class test {
    public static void main(String[] args) {
        VectorDemo<String> vectorDemo = new VectorDemo<>();
        vectorDemo.doShout("abc");
    }
}
//若定义时,写了泛型,却没在使用时指定泛型,则默认为Object类型
  • 泛型定义在方法上:用户传递进来的是什么类型,返回值就是什么类型了
public class VectorDemo {
  public <T> void show(T t) {
        System.out.println(t);
		}
}
//测试类中
public class test {
    public static void main(String[] args) {
        VectorDemo vectorDemo = new VectorDemo<>();
        vectorDemo.show("abc");
    }
}

九、增强for循环

  • 无法改变元素值,只可以使用元素
for (元素类型 变量名:数组或集合) {
            //每个变量要干的事
}
//缺点是没有下标

十、HashSet

  • 存储时顺序和取出的顺序不同
  • 无序不可重复
  • 放到HashSet集合中的元素实际上是放到HashMap集合的Key部分
  • 扩容与HashMap一致

十一、TreeSet

  • 因为无下标,因此遍历需要使用迭代器或者forEach()方法

  • 存储元素无序(存取顺序不同,无下标)不可重复,但是存储的元素可以自动按照大小顺序排序

  • String类型的话,则按照字典顺序进行升序排列

  • Integer类型的话,则按照数字大小升序排列

  • 放在TreeSet/TreeMap中的自定义类型的,想要做到排序,有两种方式

    • 方式一:自定义类需要实现java.lang.Comparable接口才可以进行排序;实现compareTo()方法,编写比较规则;equals可以不写

      • comparaTo方法的返回值很重要:

        • 返回0表示相同,value会覆盖
        • 返回>0,会在右子树上查找
        • 返回<0,会在左子树上查找
      • //实现compareTo()方法举例,单一比较规则
        class Person implements Comparable<Person>{
            int age;
          
            //拿着参数k和集合中的每一个k进行比较,返回值可能是>0,<0,=0
            @Override
            public int compareTo(Person o) {
                return this.age-o.age;//升序,新加入元素节点与逐个节点进行比较
              	return o.age-this.age;//降序
            }
        }
        
        //实现compareTo()方法举例,多重比较规则
        class Student implements Comparable<Person>{
            int age;
          	int name;
          
            //拿着参数k和集合中的每一个k进行比较,返回值可能是>0,<0,=0
            @Override
            public int compareTo(Student o) {
              	if(this.age=o.age){
                  return this.name.comparaTo(v.name);
                }
                return this.age-o.age;//升序
            }
        }
        
    • 方式二:使用比较器方式,在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象(符合OCP原则,开闭原则)

      • //自定义类
        class Person{
            int age;
        }
        
      • //比较器
        import java.util.Comparator;
        //也可以采用匿名内部类的方法去写比较器
        //写一个比较器实现Comparator<指定泛型>接口,实现compare方法,在其内书写比较规则
        public class ComparatorPerson implements Comparator<Person> {
            @Override
            public int compare(Person o1, Person o2) {
                //指定比较规则,拿着参数k和集合中的每一个k进行比较,返回值可能是>0,<0,=0
                return o1.age-o2.age;
            }
        }
        
      • //测试类
        public class Tests {
            public static void main(String[] args) {
                //构造TreeSet集合的时候传入比较器对象
                TreeSet<Person> Persons = new TreeSet<>(new ComparatorPerson());
            }
        }
        
  • 两种方式的选择:

    • 当比较规则不会发生改变的时候,或者比较规则只有一个的时候,由自定义类实现Comparable接口;
    • 当比较规则有多个,并且需要多个比较规则之间进行频繁切换的时候,使用Comparator接口,自定义比较器。
  • TreeSet/TreeMap是底层是红黑树(红黑树(Red Black Tree) 是一种(AVL)自平衡二叉查找树),遵循左小右大原则存放

    • 遍历二叉树的三种方式:前序遍历:根左右;中序遍历:左根右;后序遍历:左右根;
    • 前中后说的是"根"的位置,根在前面是前序,根在中间是中序,根在后面是后序
    • TreeSet/TreeMap集合采用的是中序遍历方式(左根右)、Iterator迭代器采用的也是中序遍历方式(左根右)
    • 存放的过程就是排序的过程,取出来就是自动按照大小顺序排列

十二、Map

  • Map与Collection没有继承关系
  • Map集合以key和value的方式存储数据:键值对
    • key和value都是引用数据类型
    • key和value都是存储对象的内存地址
    • key起主导的地位,value是key的一个附属品
    • 所有的Key是Set集合
  • 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 keySet() 获取Map集合所有的key(所有的键是一个Set集合)
    • V remove(Object key)通过key删除键值对
    • int size()获取Map集合中键值对的个数
    • Collection values()获取Map集合中所有的value,返回一个Collection
    • Set<Map.Entry<K,V>> entrySet()将Map转换为Set,Set中元素类型为Map.Entry<K,V>,Map.Entry静态内部类
//静态内部类举例
public class test {
    //声明一个静态内部类
    private static class test1{
    }
    
    public static void main(String[] args) {
        //使用静态内部类new对象
        test.test1 m= new test.test1();
    }
}
//map常用方法测试
//创建Map集合对象
Map<Integer, String> map = new HashMap<>();
//向集合中添加键值对
map.put(1,"zhangsan");//1.在这里进行了自动装箱
map.put(2,"lisi");
map.put(3,"wangwu");
map.put(4,"zhaoliu");
//通过key获取value
String value=map.get(2);
//获取键值对的数量
map.size();
//通过key删除key-value
map.remove(2);
//判断是否包含某个key
//contains方法底层调用的都是equals方法进行比对的,所以自定义类型需要重写equals方法
map.containsKey(4);
//判断是否包含某个value
map.containsValue("wangwu");
//获取所有的value
map.values();
//清空集合
map.clear();
//判断是否为空
map.isEmpty();
  • 遍历Map集合
//方式一:获取所有的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

        //可以用迭代器进行遍历keys
        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);
        }
				
				//方式一.1
				//也可以通过forEach进行遍历keys
				//for (Integer key:keys) {
            //System.out.println(key+"="+map.get(key));
        //}


//方式二:Set<Map.Entry<K,V>> entrySet();大数据量情况下,效率较高
        //以上这个方法是把Map集合全部转换成Set集合
        //Set集合中的元素类型是Map.Entry
        Set<Map.Entry<Integer, String>> sets = map.entrySet();
        //遍历Set集合,每一个取出一个Node
        
        //迭代器
        Iterator<Map.Entry<Integer, String>> it2 = sets.iterator();
        while (it2.hasNext()){
            Map.Entry<Integer, String> node = it2.next();//取出来是实现了Map.Entry<K, V>接口的node类对象,单链表
            Integer key = node.getKey();
            String value = node.getValue();
        }

				//方式二
				//也可以通过forEach进行遍历
				 for (Map.Entry<Integer, String> node:sets) {
            Integer key = node.getKey();
            String value = node.getValue();
        }

十三、HashMap

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

  • 哈希表数据结构

    • 是数组和单向链表的结合体;数组在查询反面效率很高,随机增删方面效率低;单向链表在随机增删方面效率较高,在查询方面效率很低。
    • 哈希表将以上两种数据结构融合在一起,充分发挥它们各自的优点
    • 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;//下一个节点的内存地址
        }
    }
    //哈希表/散列表:一维数组,这个数组中的每一个元素是一个单向链表。(数组和链表的结合体)
    
    • map.put(k,v)、v=map.get(k)实现原理

    • equals默认比较的是地址,而我们应当比较的是内容
    • 常用的哈希码的算法:(哈希码并不是完全唯一的,它是一种算法,让同一个类的对象按照自己不同的特征尽量的有不同的哈希码,但不表示不同的对象哈希码完全不同。)
      • Object类的hashCode()返回对象的内存地址经过处理后的结构,由于每个对象的内存地址都不一样,所以哈希码也不一样。
      • String类的hashCode()根据String类包含的字符串的内容,根据一种特殊算法返回哈希码,只要字符串所在的堆空间相同,返回的哈希码也相同。
      • Integer类,返回的哈希码就是Integer对象里所包含的那个整数的数值,例如Integer i1=new Integer(100),i1.hashCode的值就是100 。由此可见,2个一样大小的Integer对象,返回的哈希码也一样。
    • 如果o1和o2的hash值相同,一定是放到同一个单链表上(需要重写hashCode()实现),
    • 哈希碰撞:o1和o2不相同,hash值却相同
    • 如果o1和o2的hash值不同,但经过哈希算法执行结束之后转换的数组下标可能相同。
  • HashMap集合的key部分特点

    • 无序:不能确定元素会挂载到哪个哪个单链表上
    • 不可重复:equals方法来保证HashMap集合的key不可重复,若key重复了,value则会被覆盖
    • 放在HashMap集合key部分的元素其实就是放到HashSet集合中了
    • 所以HashSet集合中的元素也需要同时重写hashCode()+equals方法
  • 哈希表HashMap使用不当无法发挥性能

    • 假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表,称为散列分布不均匀。
    • 若是所有的hashCode()方法返回值都不一样,则会导致底层哈希表变成一维数组,没有了链表的概念,也是散列分布不均匀。
  • 放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法,equals方法如果返回的是true,hashCode()方法返回的值必须是一样。使用IDEA生成即可

  • HashMap集合的默认初始化容量是16(初始化容量必须是2的幂,为了达到散列均匀,提高HashMap集合的存取效率),默认加载因子是0.75,即当HashMap集合底层数组的容量达到75%的时候,数组开始扩容,扩容之后容量是原容量的2倍;

  • HashMap集合的key可以为null,但null值也只能有一个,JDK8中若key是null就把下标变成0放在数组的第一个位置上。

十四、Hashtable

  • Hashtable的key和value都不可以为null
  • Hashtable方法都带有synchronized,是线程安全的。但线程安全有其他的方案,这个Hashtable对线程的处理效率较低,较少使用。
  • Hashtable和HashMap一样,底层都是哈希表数据结构
  • Hashtable的初始化容量是11,默认加载因子是0.75,扩容是:原容量*2+1

十五、Properties属性类

  • Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型
  • Properties是线程安全的
    //创建一个Properties对象
        Properties pro = new Properties();
        //存属性
        pro.setProperty("url","jdbc:mysql//localhost:3306/bjpowernode");
        pro.setProperty("driver","com.mysql.jdbc.Driver");
        pro.setProperty("username","root");
        pro.setProperty("password","admin12345");
        
        //取属性
        String url = pro.getProperty("url");
        String driver = pro.getProperty("driver");
        String username = pro.getProperty("username");
        String password = pro.getProperty("password");

十六、TreeMap

  • TreeSet 集合底层实际上是一个TreeMap
  • TreeMap集合底层是一个二叉树。
  • 放到TreeSet 集合中的元素,等同于放到TreeMap集合key部分了。
  • TreeSet 集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。称为可排序集合。

十七、Collections工具类

  • Java.util.Collections:集合工具类,方便对集合的操作
				//1.将非线程安全集合ArrayList变为线程安全
        List<String> list=new ArrayList<>();
        Collections.synchronizedList(list);

        //2.排序
        list.add("abf");
        list.add("abx");
        list.add("abc");
        list.add("abe");
        //只能排序List集合,List集合中若传的是自定义类型要实现Comparable接口或者传入比较器对象
        Collections.sort(list);
        //Collections.sort(list,比较器对象);

        //其他集合都要转化成List集合才可以使用此方法
        //Collections.sort(new ArrayList<>(new HashSet<>()));

十八、集合之间的转换

  • Collection之间可以通过构造方法进行转换如:new ArrayList(set),则是将set集合转换成ArrayList集合
  • Map与Collection之间无之直接关联,但是通过Map.keySet()可以将Map集合的所有key转化成Set,Map.entrySet(),将Map转为Set
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值