集合浅谈


总体

集合继承结构


List = 排成一长队的小猪  
Set = 一群小猪贴上号,然后赶到一个猪圈里
Map = 放在一个个,有房间号的屋子里面的一群小猪

Java集合主要有 3 种重要的类型:

​ List:有序 可重复

​ Set:无序 不可重复

​ Map:无序 key不允许重复,value可以重复(身份证号—姓名)


1、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)删除指定下标位置的元素
  • 演示
public class ListTest01 {
    public static void main(String[] args) {
        // 创建List类型的集合。
        //List myList = new LinkedList();
        //List myList = new Vector();
        List myList = new ArrayList();

        // 添加元素
        myList.add("A"); // 默认都是向集合末尾添加元素。
        myList.add("B");
        myList.add("C");
        myList.add("C");
        myList.add("D");

        //在列表的指定位置插入指定元素(第一个参数是下标)
        // 这个方法使用不多,因为对于ArrayList集合来说效率比较低。
        myList.add(1, "KING");

        // 迭代
        Iterator it = myList.iterator();
        while(it.hasNext()){
            Object elt = it.next();
            System.out.println(elt);
        }

        // 根据下标获取元素
        Object firstObj = myList.get(0);
        System.out.println(firstObj);

        // 因为有下标,所以List集合有自己比较特殊的遍历方式
        // 通过下标遍历。【List集合特有的方式,Set没有。】
        for(int i = 0; i < myList.size(); i++){
            Object obj = myList.get(i);
            System.out.println(obj);
        }

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

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

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

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

        // 增强for
        for (Object data : myList) {
            System.out.println(data);
        }
    }
}

  • 遍历方式:for、迭代器、forEach

  • List 接口下面主要有两个实现 ArrayList 和 LinkedList,他们都是有顺序的,也就是放进去 是什么顺序,取出来还是什么顺序,也就是基于线性存储,可以看作是一个可变数组

  • ArrayList:查询数据比较快,添加和删除数据比较慢(基于可变数组)

  • LinkedList:查询数据比较慢,添加和删除数据比较快(基于链表数据结构)

  • Vector:Vector 已经不建议使用,Vector 中的方法都是同步的,效率慢,已经被 ArrayList 取代

  • Stack是继承 Vector 实现了一个栈,栈结构是后进先出,目前已经被 LinkedList 取代


01、ArrayList


简介

  • 1、默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10。)
  • 2、集合底层是一个Object[]数组
  • 3、构造方法:
    • new ArrayList();
    • new ArrayList(20);
  • 4、ArrayList集合的扩容:增长到原容量的1.5倍
ArrayList集合底层是数组,怎么优化?
    尽可能少的扩容。因为数组扩容效率比较低,建议在使用ArrayList集合的时候预估计元素的个数,给定一个初始化容量。
  • 5、数组优点:
检索效率比较高。(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,
然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高。)
  • 6、数组缺点:
随机增删元素效率比较低。
另外数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间。)
  • 7、向数组末尾添加元素,效率很高,不受影响。
  • 8、面试官经常问的一个问题?
这么多的集合中,你用哪个集合最多?
    答:ArrayList集合。
    因为往数组末尾添加元素,效率不受影响。
    另外,我们检索/查找某个元素的操作比较多。
  • 7、ArrayList集合是非线程安全的。(不是线程安全的集合。)
  • 8、注意:ArrayList之所以检索效率比较高,不是单纯因为下标的原因。是因为底层数组发挥的作用。

02、单向链表 双向链表


  • 单向链表(每一个节点在内存中存储上在空间位置都是无规律的)

在这里插入图片描述

  • 双向链表

在这里插入图片描述


03、LinkedList


简介

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

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

    ArrayList:把检索发挥到极致。(末尾添加元素效率还是很高的。)
    LinkedList:把随机增删发挥到极致。
    加元素都是往末尾添加,所以ArrayList用的比LinkedList多。
  • LinkedList集合没有初始化容量
    最初这个链表中没有任何元素。first和last引用都是null。
  • LinkedList集合也有下标,但是检索/查找某个元素的时候效率比较低,因为只能从头节点开始一个一个遍历

04、Vector


简介

  • 1、底层也是一个数组。
    2、初始化容量:10
    3、怎么扩容的?

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

    4、ArrayList集合扩容特点:
    ArrayList集合扩容是原容量1.5倍。

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

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

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

2、Set集合


  • 是一个无序集合,不允许放重复的数据
  • 没有下标

01、HashSet


简介

  • 底层实际上是一个HashMap,HashMap底层采用了哈希表的数据结构

  • HashSet其实是HashMap的key部分

  • HashSet集合初始化容量 16,默认加载因子是0.75

  • 扩容:扩容之后是原容量 2倍。

02、TreeSet


简介

  • 无序不可重复,但是存储的元素可以自动按照大小顺序排序
  • 这里的无需指的是存进去的顺序和取出来的顺序不同,而且没有下标
  • TreeSet集合底层实际上是TreeMap(new TreeSet集合的时候,底层实际上new了一个TreeMap集合。)
  • TreeMlap集合底层采用了二叉树数据结构。
数据库中有很多数据:
    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集合放进去,拿出来就是有顺序的

  • 无序不可重复,可以自动按照大小顺序排序
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);
        }

        TreeSet<Integer> ts2 = new TreeSet<>();
        ts2.add(100);
        ts2.add(200);
        ts2.add(900);
        ts2.add(800);
        ts2.add(600);
        ts2.add(10);
        for(Integer elt : ts2){
            // 升序!
            System.out.println(elt);
        }
    }
}
  • 输出(升序)
lisi
wangliu
wangwu
zhangsan
zhangsi

10
100
200
600
800
900

03、TreeSet的排序


对自定义的类型来说,TreeSet可以排序吗?

  • 以下程序中对于Person类型来说,无法排序。因为没有指定Person对象之间的比较规则。谁大谁小并没有说明

  • 运行的时候出现了这个异常: 类型转换异常

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

public class TreeSetTest03 {
    public static void main(String[] args) {
        Person p1 = new Person(32);
        //System.out.println(p1);
        Person p2 = new Person(20);
        Person p3 = new Person(30);
        Person p4 = new Person(25);

        // 创建TreeSet集合
        TreeSet<Person> persons = new TreeSet<>();
        // 添加元素
        persons.add(p1);
        persons.add(p2);
        persons.add(p3);
        persons.add(p4);

        // 遍历
        for (Person p : persons){
            System.out.println(p);
        }
    }
}

class Person {
    int age;
    public Person(int age){
        this.age = age;
    }

    // 重写toString()方法
    public String toString(){
        return "Person[age="+age+"]";
    }
}


改进方式1 (编写比较规则 实现java.lang.Comparable接口 实现compareTo方法)

public class TreeSetTest04 {
    public static void main(String[] args) {
        Customer c1 = new Customer(32);
        Customer c2 = new Customer(20);
        Customer c3 = new Customer(30);
        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.Comparable接口。
// 并且实现compareTo方法。equals可以不写。
class Customer implements Comparable<Customer>{

    int age;
    public Customer(int age){
        this.age = age;
    }
	
  	//编写比较规则
    @Override
    public int compareTo(Customer c) {	
      	// c1.compareTo(c2);
        // this是c1 / c是c2 / c1和c2比较的时候,就是this和c比较
        
      /*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	//升序
        return c.age - this.age;	//降序
    }

    public String toString(){
        return "Customer[age="+age+"]";
    }
}
  • 输出
Customer[age=32]
Customer[age=30]
Customer[age=25]
Customer[age=20]

练习编写比较规则

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

  • compareTo方法的返回值很重要:(底层是自平衡二叉树)

    • 返回0表示相同,value会覆盖。
    • 返回>0,会继续在右子树上找。【10 - 9 = 1 ,1 > 0的说明左边这个数字比较大。所以在右子树上找。】
    • 返回<0,会继续在左子树上找。。

public class TreeSetTest05 {
    public static void main(String[] args) {
        TreeSet<Vip> vips = new TreeSet<>();
        vips.add(new Vip("zhangsi", 20));
        vips.add(new Vip("zhangsan", 20));
        vips.add(new Vip("king", 18));
        vips.add(new Vip("soft", 17));
        for(Vip vip : vips){
            System.out.println(vip);
        }
    }
}

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;
        }
    }
}
  • 输出:
Vip{name='soft', age=17}
Vip{name='king', age=18}
Vip{name='zhangsan', age=20}
Vip{name='zhangsi', age=20}

改进方式2 (使用比较器的方式)

  • 比较器实现java.util.Comparator接口。(Comparable是java.lang包下的。Comparator是java.util包下的。)

public class TreeSetTest06 {
    public static void main(String[] args) {
        // 创建TreeSet集合的时候,需要使用这个比较器。
        // TreeSet<WuGui> wuGuis = new TreeSet<>();//这样不行,没有通过构造方法传递一个比较器进去。

        // 给构造方法传递一个比较器。
        //TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());

        // 编写比较器方式2 匿名内部类
        TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() {
            @Override
            public int compare(WuGui o1, WuGui o2) {
                return o1.age - o2.age;
            }
        });

        wuGuis.add(new WuGui(1000));
        wuGuis.add(new WuGui(800));
        wuGuis.add(new WuGui(810));

        for(WuGui wuGui : wuGuis){
            System.out.println(wuGui);
        }
    }
}

// 乌龟
class WuGui{

    int age;

    public WuGui(int age){
        this.age = age;
    }

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

// 编写比较器方式1 定义类
/*
class WuGuiComparator implements 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接口。

    • Comparator接口的设计符合OCP原则。

3、Map集合


在这里插入图片描述


简介

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

常用方法

方法含义
1V put(K key, V value)向Map集合中添加键值对
2V get(Object key)通过key获取value
3void clear()清空Map集合
4boolean containsKey(Object key)判断Map中是否包含某个key
5boolean containsValue(Object value)判断Map中是否包含某个value
6boolean isEmpty()判断Map集合中元素个数是否为0
7V remove(Object key)通过key删除键值对
8int size()获取Map集合中键值对的个数
9Collection values()获取Map集合中所有的value,返回一个Collection
10Set keySet()获取Map集合所有的key(所有的键是一个set集合)
11Set<Map.Entry<K,V>> entrySet()将Map集合转换成Set集合(Set集合中元素的类型是:Map.Entry)
  • 演示
public class MapTest01 {
    public static void main(String[] args) {
        // 创建Map集合对象
        Map<Integer, String> map = new HashMap<>();
        // 1、向Map集合中添加键值对
        map.put(1, "zhangsan"); // 1在这里进行了自动装箱。
        map.put(2, "lisi");
        map.put(3, "wangwu");
        map.put(4, "zhaoliu");
      
        // 2、通过key获取value
        String value = map.get(2);
        System.out.println(value);
      
        // 8、获取键值对的数量
        System.out.println("键值对的数量:" + map.size());
      
        // 7、通过key删除key-value
        map.remove(2);
      
        // 4、判断是否包含某个key
        // contains方法底层调用的都是equals进行比对的,所以自定义的类型需要重写equals方法。
        System.out.println(map.containsKey(new Integer(4))); // true
      
        // 5、判断是否包含某个value
        System.out.println(map.containsValue(new String("wangwu"))); // true

        // 9、获取所有的value
        Collection<String> values = map.values();
      
        // foreach
        for(String sss : values){
            System.out.println(sss);
        }
		
      	// 10、获取Map集合所有的key
      	// 输出:[1, 3, 4]
      	Set<Integer> qqq = map.keySet();
        System.out.println(qqq);
      
      	// 11、获取Map集合所有的key(所有的键是一个set集合)
        // 输出:[1=zhangsan, 3=wangwu, 4=zhaoliu]	
        Set<Map.Entry<Integer, String>> entries = map.entrySet();
        System.out.println(entries);
      
        // 3、清空map集合
        map.clear();
        System.out.println("键值对的数量:" + map.size());
      
        // 6、判断是否为空
        System.out.println(map.isEmpty()); // true
    }
}

entrySet详解

entrySet:详解:
        
        11、将Map集合转换成Set集合(Set集合中元素的类型是:Map.Entry)
        Set<Map.Entry<K,V>> entrySet()
            将Map集合转换成Set集合
            假设现在有一个Map集合,如下所示:
                map1集合对象
                key             value
                ----------------------------
                1               zhangsan
                2               lisi
                3               wangwu
                4               zhaoliu
				----------------------------
                Set set = map1.entrySet();
                set集合对象
                [1=zhangsan, 
                2=lisi,     
                3=wangwu,
                4=zhaoliu] ---> 这个东西是个什么?Map.Entry
            ----------------------------------------------------------    
                注意:Map集合通过entrySet()方法转换成的这个Set集合,
                Set集合中元素的类型是 Map.Entry<K,V>
            ----------------------------------------------------------    
                Map.Entry和String一样,都是一种类型的名字,
                只不过:Map.Entry是静态内部类,是Map中的静态内部类

01、Map集合的遍历方式


  • 数据准备
Map<Integer, String> map = new HashMap<>();
        map.put(1, "zhangsan");
        map.put(2, "lisi");
        map.put(3, "wangwu");
        map.put(4, "zhaoliu");

  • 遍历方式1 (常用的遍历式)
  • 通过 map.entrySet() 遍历
  • 比较适合于大数据量
  • 效率较高。因为获取key和value都是直接从node对象中获取的属性值
for(Map.Entry<Integer,String> node : map.entrySet()){ //将Map集合转换成Set集合
            System.out.println(node.getKey() + "--->" + node.getValue());
        }

  • 遍历方式2
  • 通过keySet get(key) 遍历
Set<Integer> keys = map.keySet(); //获取Map集合所有的key
for(Integer key : keys){
            System.out.println(key + "=" + map.get(key));
        }

  • 遍历方式3
  • 通过 iterator迭代器和 keySet get(key)遍历
// 获取所有的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);
	}

  • 遍历方式4
  • 通过 iterator迭代器遍历
// Set<Map.Entry<K,V>> entrySet()
// 以上这个方法是把Map集合直接全部转换成Set集合。
// Set集合中元素的类型是:Map.Entry
Set<Map.Entry<Integer,String>> set = map.entrySet(); //把Map集合直接全部转换成Set集合
// 遍历Set集合,每一次取出一个Node
// 迭代器
	Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
    while(it2.hasNext()){
		Map.Entry<Integer,String> node = it2.next();
          /*
          	此时 node:
          		1=zhangsan
                2=lisi     
                3=wangwu
                4=zhaoliu
          */
		Integer key = node.getKey();
		String value = node.getValue();
		System.out.println(key + "=" + value);
	}

  • 遍历方式5
  • Lambda表达式
map.forEach((key,value) -> {
	System.out.println(key + "===" +value);
});

02、HashMap浅谈


HashMap


哈希表或者散列表数据结构


  • 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; // 下一个节点的内存地址。
    	}
    }
    
  • 5、HashMap集合的key部分特点:

    • 无序不可重复
    • 为什么无序? 因为不一定挂到哪个单向链表上。
    • 不可重复是怎么保证的? equals方法来保证HashMap集合的key不可重复。
    • 如果key重复了,value会覆盖
    • 放在HashMap集合key部分的元素其实就是放到HashSet集合中了。
    • HashSet集合中的元素也需要同时重写hashCode()+equals()方法。

  • 6、哈希表HashMap使用不当时无法发挥性能!

        假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了
        纯单向链表。这种情况我们成为:散列分布不均匀。
        什么是散列分布均匀?
            假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,
            是散列分布均匀的。
        假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题?
            不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。
            也是散列分布不均匀。
        散列分布均匀需要你重写hashCode()方法时有一定的技巧。
    
  • 7、重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法

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

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

    重点,记住:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,

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

  • 9、JDK8之后哈希表单向链表的元素超过8个时 ---> 变为红黑树

    ​ 红黑树的节点小于6个时 ---> 变为单向链表


03、HashMap为什么需要重写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、结论:

  • 放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法
  • 使用HashMap,如果key是自定义的类,就必须重写hashcode()和equals()。

4、对于哈希表数据结构来说:

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

  • 定义一个实体类
public class Student {
    private String name;

    public Student() {
    }
    public Student(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    
    // equals(如果学生名字一样,表示同一个学生。)   
    @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);
    }
	
  	// hashCode
    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}
  • 定义一个实现类
public class HashMapTest02 {
    public static void main(String[] args) {

        Student s1 = new Student("zhangsan");
        Student s2 = new Student("zhangsan");

        // 重写equals方法之前是false
        //System.out.println(s1.equals(s2)); // false

        // 重写equals方法之后是true
        System.out.println(s1.equals(s2)); //true (s1和s2表示相等)
		
       	//重写hashCode之前 284720968    (重写hashCode之后 -1432604525)
        System.out.println("s1的hashCode=" + s1.hashCode());
      	//重写hashCode之前 122883338    (重写hashCode之后 -1432604525)
        System.out.println("s2的hashCode=" + s2.hashCode());

        Set<Student> students = new HashSet<>();
        students.add(s1);
        students.add(s2);
        System.out.println(students.size());
    }
}
  • 输出
true
s1的hashCode=-1432604525
s2的hashCode=-1432604525 (如果实体类不重写HashCode方法,这两个HashCode的输出结果是不同的)
1	(如果不重写HashCode方法,此处输出的结果是2

  • HashMap集合key和value允许null
  • 但是要注意:HashMap集合的key null值只能有一个。
public class HashMapTest03 {
    public static void main(String[] args) {

        Map map = new HashMap();

        // HashMap集合允许key和value为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
    }
}

04、HashTable


  • Hashtable的key和value都是不能为null的。
  • Hashtable方法都带有synchronized:线程安全的。
    线程安全有其它的方案,这个Hashtable对线程的处理
    导致效率较低,使用较少了。
  • Hashtable和HashMap一样,底层都是哈希表数据结构。
  • Hashtable的初始化容量是 11,默认加载因子是:0.75f
  • Hashtable的扩容是: 原容量 * 2 + 1
public class HashtableTest01 {
    public static void main(String[] args) {
        Map map = new Hashtable();
		
      	//空指针异常
        map.put(null, "123");
        map.put(100, null);
		
      
    }
}

05、Properties


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

  • Properties被称为 属性类对象

  • Properties是 线程安全的。

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

        // 创建一个Properties对象
        Properties pro = new Properties();

        // 需要掌握Properties的两个方法,一个存,一个取。
        pro.setProperty("url", "jdbc:mysql://localhost:3306/bjpowernode");
        pro.setProperty("driver","com.mysql.jdbc.Driver");
        pro.setProperty("username", "root");
        pro.setProperty("password", "123");

        // 通过key获取value
        String url = pro.getProperty("url");
        String driver = pro.getProperty("driver");
        String username = pro.getProperty("username");
        String password = pro.getProperty("password");

        System.out.println(url);
        System.out.println(driver);
        System.out.println(username);
        System.out.println(password);
    }
}
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值