集合 - Collection & Map

1、集合

1.1、集合的相关概念:

  • 集合不能直接存储基本数据类型。(可以是基本数据类型的包装类)
  • 集合不能直接存储java对象。(只能是对象的内存地址)
  • 集合中存储的是java对象的内存地址。(或者说是引用)

1.2、集合的存储方式:

  • 单个方式存储元素,这一类集合中超级父接口:java.util.Collection
  • 键值对方法存储元素,这一类集合中超级父接口:java.util.Map

1.3、集合的存储类图:

单个方式存储

[========]

键值对方式存储

上面两个类图的总结:

集合实例讲解

2.1、Collection接口常用方法

//创建对象
Collection c = new ArrayList();	//Collection是接口,不能直接new;因此这里是多态。

//向集合中添加元素
c.add(1); // 自动装箱,存进去的是对象的内存地址。Integer x = new Integer(1);

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

//清空集合
c.clear();

//判断集合中是否包含元素1
System.out.println(c.contains(1)); // true

//删除某个元素
c.remove(1);

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

//将集合转换成Object数组
Object[] objs = c.toArray();
for(int i = 0; i < objs.length; i++){
	// 遍历数组
	Object o = objs[i];
	System.out.println(o);	//集合中的1是Integer类型,Integer类型已经重写了toString方法。
}

重点:迭代器Iterator

package com.bjpowernode.javase.collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionTest02 {
    public static void main(String[] args) {
        // 在Map集合中不能用。在所有的Collection以及子类中使用。
        // 创建集合对象
        Collection c = new ArrayList(); 
        // 添加元素
        c.add("abc");
        c.add("def");
        c.add(100);
        c.add(new Object());
        
        // 第一步:获取集合对象的迭代器对象Iterator
        Iterator it = c.iterator(); //c继承了Iterable,Iterable有iterator方法,因此可以调用,返回的是Iterator。
        // 第二步:通过以上获取的迭代器对象开始迭代/遍历集合。
        /*
            以下两个方法是迭代器对象Iterator中的方法:
                boolean hasNext()如果仍有元素可以迭代,则返回 true。
                Object next() 返回迭代的下一个元素。
         */
        while(it.hasNext()){
            Object obj = it.next();
            System.out.println(obj);
        }
    }
}

迭代器图解:

image-20210411144051657

深入contains

package com.bjpowernode.javase.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"); // s1 = 0x1111
        c.add(s1); // 放进去了一个"abc"

        String s2 = new String("def"); // s2 = 0x2222
        c.add(s2);

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

        // 新建的对象String
        String x = new String("abc"); // x = 0x5555
        // c集合中是否包含x?结果猜测一下是true还是false?
        System.out.println(c.contains(x)); //true
    }
}

解析:

  • contains比较的是内容(底层也是使用equals比较的),不是内存地址。元素是String类型的,已经重写toString()方法了,因此比较的是内容,即c包含abc。
  • 如果不是String类型的话,是自己创建的类的话,需要手动重写;如果不重写,调用的是Object的equals,Object的equals比较的是内存地址。

关于上面代码的图解:

image-20210411160215358

remove的使用

  • 当集合的结构发生改变时,迭代器必须重新获取;否则会出现异常。
  • 不要使用集合中的remove方法删除元素,要使用迭代器Iterator的remove方法删除元素。

图解集合和迭代器中remove方法:

迭代器迭代的时候参照一个快照进行迭代(也可以理解为迭代快照),每迭代一次快照中的元素,都会和集合进行对比,看迭代的该元素在集合中是否存在,若存在,则又继续迭代下一个;若不存在,则直接报错。这就可以解释两个remove方法的使用了。如果使用集合中的remove方法删除集合中的元素,此时,迭代器并没有更新(快照中的该元素没有删除),也就是说,这时快照中的元素和集合中的元素对应不上,所以报错。如果使用迭代器的remove方法删除元素,快照中的元素和集合中的元素同时删除,这时,迭代元素就对应上了。

image-20210416210341662

实例:

package com.bjpowernode.javase.collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionTest06 {
    public static void main(String[] args) {
        // 创建集合
        Collection c2 = new ArrayList();
        
        //添加元素
        c2.add("abc");
        c2.add("def");
        c2.add("xyz");
        
        //在添加元素进集合之后才获取迭代器
        Iterator it2 = c2.iterator();
        
        //迭代集合
        while(it2.hasNext()){
            Object o = it2.next();	//返回值类型必须是Object
            
            // 这是使用集合中的remove方法删除元素,在迭代过程中删除集合中的元素后,并没有更新迭代器。导致迭代器的快照和原集合状态不同。
            //c2.remove(o); 
            
            // 使用迭代器的remove方法删除元素,会自动更新迭代器,并且更新集合(删除集合中的元素)。
            it2.remove(); // 删除的一定是迭代器指向的当前元素。
            System.out.println(o);
        }

        System.out.println(c2.size()); //0
    }
}

List接口

  • 常用方法:
List myList = new ArrayList();
myList.add(1,"king");	//在指定下标添加元素。

System.out.println(myList.get(1));	//根据下标取元素

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

System.out.println(myList.lastIndexOf("C")); // 获取指定对象最后一次出现处的索引。

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

System.out.println(myList.set(2, "Soft"));	// 修改指定下标元素的元素
  • List是有序可重复的,因此有特别的遍历方式:
for(int i = 0; i < myList.size(); i++){
	Object obj = myList.get(i);
	System.out.println(obj);
}
ArrayList集合
  • 非线程安全。

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

  • 集合底层是一个Object[]数组。

  • ArrayList集合的扩容:是增长到原容量的1.5倍。

  • ArrayList集合的优化:底层是数组,在初始化时预估计一下元素的个数,给定一个初始化容量,较少扩容。

    • 数组优点:检索效率高。(每个元素占用空间相同、内存地址连续、知道首元素内存地址)
    • 数组缺点:随机增删元素效率比较低;无法存储大容量数据。
    • 注:在末尾添加元素效率很高,不受影响。
  • 转换成线程安全的:

    List myList = new ArrayList();
    Collections.synchronizedList(myList);	//java.util.Collections里面的方法
    
ArrayList的构造方法
  • 无参构造。List myList1 = new ArrayList();

  • 有参构造,参数是初始化容量的。List myList2 = new ArrayList(100);

  • 有参构造,参数是集合,是将该集合转换成ArrayList集合。List myList3 = new ArrayList(c);,其中c是集合。

单向链表的数据结构
image-20210417105705472

链表的优点:

  • 链表上的元素在空间存储上内存地址不连续。所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。

链表的缺点:

  • 不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止。所以LinkedList集合检索/查找的效率较低。
ArrayListLinkedList
  • ArrayList:把检索发挥到极致。(末尾添加元素效率还是很高的。)
  • LinkedList:把随机增删发挥到极致。
  • 加元素都是往末尾添加,所以ArrayList用的比LinkedList多。
  • 两者使用起来都差不多,使用那个看情况而定。
双向链表的数据结构
image-20210417153003433
Vector集合
  • 使用方法和ArrayList差不多。
  • 底层也是数组。
  • 默认容量是10;扩容是原来的2倍。10→20→40→80
  • 线程安全,下效率低,使用少。

Set集合

  • 无序不可重复。
HashSet集合
  • 无序不可重复。
  • 放到HashSet集合中的元素实际上放到HashMap集合中的key部分。

Map接口常用方法

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

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class maptest {
    public static void main(String[] args) {
        //创建Map集合对象
        Map<Integer,String> map=new HashMap<>();

        //往集合中添加键值对
        map.put(1,"你好");

        //通过key获取value
        System.out.println(map.get(1));

        //获取键值对的数量
        System.out.println(map.size());

        //通过key删除key和value
        map.remove(1);

        //往集合中添加键值对
        map.put(2,"张三");
        map.put(3,"李四");

        //判断集合中是否包含某个key
        //contains方法底层调用的都是equals进行比对的,所以自定义的类型需要重写equals方法。
        System.out.println(map.containsKey(2));

        //判断集合中是否包含某个value
        System.out.println(map.containsValue("张三"));

        //获取集合中所有value
        Collection<String> collection=map.values();
        for (String s:collection){
            System.out.println(s);
        }

        //清空集合
        map.clear();

        //判断集合是否为空
        System.out.println(map.isEmpty());

    }
}
Map集合的遍历
package com.bjpowernode.javase;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class maptest01 {
    public static void main(String[] args) {
        //创建集合
        Map<Integer,String> map=new HashMap<>();

        //往集合中添加元素
        map.put(1,"张三");
        map.put(2,"李四");
        map.put(3,"王五");

        //获取集合中的所有key,把map集合中的key转成set集合
        Set<Integer> set=map.keySet();

        //获取迭代器
        Iterator<Integer> it=set.iterator();

        //使用while遍历
        while (it.hasNext()){
            //取出其中一个key
            Integer integer=it.next();
            //通过key获取value
            String string=map.get(integer);
            //输出
            System.out.println(integer+"="+string);
        }

        //使用for增强遍历
        for (Integer i:set){    //Integer是迭代的类型,i是标识符,set是需要迭代的集合元素
            System.out.println(i+"="+map.get(i));
        }

        System.out.println("======================================");

        //第二种方式
        //把Map集合直接全部转换成Set集合
        //Set集合中的元素类型是:Map.Entry
        //这种方式效率比较高,获取key和value都是直接从node对象中获取的属性值。
        //适合大数据量。
        Set<Map.Entry<Integer,String>> set1=map.entrySet();

        //遍历Set集合,每一次取出一个node
        //获取迭代器
        Iterator<Map.Entry<Integer,String>> it2=set1.iterator();

        //使用while遍历
        while(it2.hasNext()){
            Map.Entry<Integer,String> node=it2.next();

            Integer key=node.getKey();
            String value=node.getValue();

            System.out.println(key+"="+value);
        }

        //使用for增强遍历
        for (Map.Entry<Integer,String> node:set1){
            Integer key1=node.getKey();
            String value1=node.getValue();

            System.out.println(key1+"--->"+value1);
        }
    }
}

第二种遍历方法图解:

image-20210418225133791
HashMap集合
  • 非线程安全。
  • HashMap的key可以为null。
  • 在jdk8之后,如果哈希表单向链表中元素超过8个,单向链表会变成红黑树;当红黑树上的节点数量小于6时,会重新把红黑树变成单向链表。
  • HashMap集合的默认初始化容量为16,默认加载因子是0.75;即HashMap集合底层数组的容量达到75%的时候,数组开始扩容。注:初始化容量必须是2的倍数。

哈希算法的数据结构图解:

image-20210419165454241

上面图解重点解析:
  • 哈希表是一个数组和单向链表的结合体,充分发挥各自的优点:【数组:查询效率高,随机增删效率低】、【单向链表:查询效率低,随机增删效率高。】
  • HashMap集合底层是哈希表/散列表的数据结构。哈希表/散列表:实际上就是一维数组,数组中的每一个元素是一个单链表。
  • 哈希值是keyhashCode()方法的执行结果。hash值通过哈希函数/算法,可以转换存储成数组的下标。
  • 哈希表需要分布均匀才能发挥性能。
  • HashMap集合key部分的元素其实就是放到HashSet集合中了;所以HashSet集合中的元素也需要同时重写hashCode()equals()方法。
  • HashMap的特点:无序不可重复;
    • 无序:不一定挂在哪个单链表上。
    • 不可重复:使用equals方法来保证HashMap集合的key不可重复,如果key重复,value会覆盖。
图解中的两个重要方法:
  • map.put(k,v)

  • v = map.get(k)

hashCodeequals讲解
  • Map集合中存,以及从Map集合中取,都是先调用keyhashCode方法,然后再调用equals方法!
  • equals方法有可能调用,也有可能不调用。
    • put(k,v)get(k)举例。k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。数组下标位置上如果是null,equals不需要执行。
  • 如果一个类的equals方法重写了,那么hashCode()方法必须重写。如果不重写hashCode的话,可以放进两个相同的key,两个相同的key各占数组中的一个元素,也就是两个key存放的数组下标(哈希值)不同。如果重写hashCode的话,equals方法返回如果是true,hashCode()方法返回的值必须一样。equals方法返回true表示两个对象相同,在同一个单向链表上比较。那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。
  • 终极结论:放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。
  • 对于哈希表数据结构来说:
    • 如果o1和o2的hash值相同,一定是放到同一个单向链表上。
    • 当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。
Hashtable集合
  • Hashtable的key和value都不能为null;否则会报空指针异常。
  • Hashtable是线程安全的,效率较低,使用较少。
  • Hashtable的初始化容量是11;默认加载因子是:0.75f;扩容是:原容量*2+1。
属性类:Properties
  • 继承Hashtable,Properties的key和value都是String类型。
  • Properties被称为属性类对象。
  • Properties是线程安全的。

实例:

package com.bjpowernode.javase;

import java.util.Properties;

public class properties {
    public static void main(String[] args) {	
        Properties pro=new Properties();

        //存
        pro.setProperty("url","jdbc:mysql://localhost:3306/bjpowernode");
        pro.setProperty("driver","com.mysql.jdbc.Driver");

        //取
        String url=pro.getProperty("url");

        System.out.println(url);
    }
}

SorteMap集合
TreeSet集合
  • 无序不可重复,但可排序;也称可排序集合。

  • TreeSet集合底层实际上是一个TreeMapTreeMap集合底层是一个二叉树。放到TreeSet集合中的元素,等同于放到TreeMap集合key部分。

  • 对自定义的类型来说,TreeSet无法排序。例如:自定义类(Person),因为没有指定Person对象之间的比较规则。谁大谁小并没有说明,所以会出现异常以下异常

    java.lang.ClassCastException:
    	class com.bjpowernode.javase.collection.Person
        cannot be cast to class java.lang.Comparable
    出现的原因:    Person类没有实现java.lang.Comparable接口。            
    

实例:

package com.bjpowernode.javase.collection;

import java.util.TreeSet;

public class TreeSetTest02 {
    public static void main(String[] args) {
        // 创建一个TreeSet集合
        TreeSet<String> ts = new TreeSet<>();
        // 添加String
        ts.add("zhangsan");
        ts.add("lisi");
        ts.add("wangwu");
        ts.add("zhangsi");
        ts.add("wangliu");
        // 遍历
        for(String s : ts){
            // 按照字典顺序,升序!
            System.out.println(s);
        }
    }
}

/*
数据库中有很多数据:
    userid  name     birth
    -------------------------------------
    1       zs          1980-11-11
    2       ls          1980-10-11
    3       ww          1981-11-11
    4       zl          1979-11-11

    编写程序从数据库当中取出数据,在页面展示用户信息的时候按照生日升序或者降序。
    这个时候可以使用TreeSet集合,因为TreeSet集合放进去,拿出来就是有顺序的。
 */

posted @ 2021-04-21 17:42  LFR  阅读(54)  评论(0)    收藏  举报