Java基础整理(十六)

集合(下)

传送门(上部分)

11. Map集合

(1) 基本概念
  • Map和Collection没有继承关系

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

    key和value都是引用数据类型存储对象的内存地址

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


(2) Map接口常用方法
put
//V put(K key, V value) 向Map集合中添加键值对
Map<Integer, String> map = new HashMap<>();
map.input(1,"zhangsan");
map.input(2,"lisi");
map.input(3,"wangwu");

get
//V get(Object key) 通过key获取value
String value = map.get(2); //lisi

size
//int size() 获取Map集合中键值对的个数
System.out.println(map.size()); //3

remove
//V remove(Object key) 根据key删除键值对
map.remove(2);//删除lisi
map.size(); //2

containsKey/containsValue
//boolean containsKey(Object key) 判断Map中是否包含某个key
//boolean containsValue(Object value) 判断Map中是否包含某个value
map.containsKey(1); //true
map.containValue("zhangsan"); //true

==注:==两个contains方法底层都是通过equals方法进行对比的,所以自定义的类需要重写equals方法


values
//Collection<V> values()  获取Map集合中所有的value 返回一个Collection
Collection<String> values = map.values();
for(String s:values){
    System.out.println(s);
}

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

isEmpty
//boolean isEmpty() 判断集合是否为空
map.isEmpty() //true

keySet
//Set<K> keySet() 获取Map集合所有的key(所有的键是一个set集合)
//示例代码在遍历给出

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

假设目前我有一个Map集合对象map

1----->zhangsan

2----->lisi

我调用方法 Set set = map1.entrySet(); 则得到set集合对象

1=zhangsan

2=lisi

其中Set集合中的元素类型为 <Map.Entry<K,V>>。Map.Entry是Map中的一个静态内部类,就是一种类的名字


(3) Map集合的遍历
  • 第一种遍历方法:获取所有的key,通过遍历key,来遍历value

    public class Demo{
    
        public static void main(String[] args) {
    
            Map<Integer, String> map = new HashMap<>();
            map.put(1,"lisi");
            map.put(2,"zhangsan");
            map.put(3,"zhaoliu");
            map.put(4,"wangwu");
            //获取所有的key,所有key是一个Set集合
            Set<Integer> keys = map.keySet();
            //通过key获取value
            Iterator<Integer> it = keys.iterator();
            while(it.hasNext()){
                Integer key = it.next();
                String value = map.get(key);
                System.out.println(key+"="+value);
            }
        }
    } //也可以for each
    
  • 第二种遍历方法:Set<Map.Entry<K,V>> entrySet() ,把Map集合直接转换成Set集合,Set集合元素类型Map.Entry<K,V>

    public class Demo{
    
        public static void main(String[] args) {
    
            Map<Integer, String> map = new HashMap<>();
            map.put(1,"lisi");
            map.put(2,"zhangsan");
            map.put(3,"zhaoliu");
            map.put(4,"wangwu");
    
            Set<Map.Entry<Integer,String>> set =map.entrySet();
            
            for(Map.Entry<Integer,String> node : set){
                System.out.println(node.getKey()+ "--->"+node.getValue());
            }
            //迭代器
            /*
             Iterator<Map.Entry<Integer,String>> it =set.iterator();
            while(it.hasNext()){
                Map.Entry<Integer,String> node = it.next();
                Integer key = node.getKey();
                String value = node.getValue();
                System.out.println(key + "=" + value);
            }
            */
        }
    }
    

    这种效率比较高,因为获取key和value都是直接从node对象中获取的属性值


12. HashMap

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

(1) 基本概念

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

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

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

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

  • HashMap集合底层源代码

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

哈希表/散列表:一维数组,这个数组中的每一个元素都是一个单向链表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1L136iFN-1638880988470)(G:.截图\JavaSE\哈希表结构.png)]


(2) map.put(k,v)实现原理
  • 先将k,v封装到Node对象当中
  • 底层会调用hashCode()方法得出hash值,然后通过哈希函数/算法,将hash值转换成数组的下标。
  • 下标位置上如果没有任何元素,就把Node添加到这个位置上。
  • 如果说下标对应的位置上有链表,此时会拿着k和链表上每一个节点中的k进行equals,如所有equals方法返回都是false,那么这个新节点将被添加到链表的末尾。如果其中一个equals返回了true,那么这个节点的value将会被覆盖。

(3) 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()方法,那么这两个方法都需要重写


(4) 一些问题
  • 为什么哈希表的随机增删,以及查询效率都高?

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

  • HashMap集合的key为什么无序,不可重复

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

    不可重复是equals()方法来保证HashMap集合的key不可重复,如果key重复,value会被覆盖

  • 同一个单向链表上的所有节点hash值相同,因为它们的数组下标是一样的。但同一个链表上k和k的equals方法肯定返回false,都不相等

  • HashMap使用不当时会无法发挥性能

    • 假设所有的hashCode()方法返回值固定为某个值,那么会导致哈希表变成了单纯的单向链表。(散列表分布不均匀)
    • 假设将所有hashCode()方法返回值都设定为不一样的值,会导致底层哈希表变成了一个单纯的一维数组(散列表分布不均匀)
  • 放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法

  • HashMap集合的默认初始化容量是16默认加载因子是0.75.(默认加载因子是当HashMap集合底层数组的容量达到75%时,数组扩容)

  • HashMap集合初始化容量必须是2的次幂(HashSet与HashMap一样)

  • equals方法返回true,hashCode()方法返回的值必须也是true。

    • hashCode()方法返回true,equals方法不一定也是true
  • 如果HashMap底层哈希表中的单向链表中元素超过8个,单向链表数据结构会转变成红黑树数据结构。当红黑树上的节点数量小于6时,会重新把红黑树变成单向链表的数据结构。这种方式是为了提高检索效率。

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

  • HashMap集合key部分允许是null,但只能有一个,重复的话value被覆盖


13. Hashtable

  • Hashtable方法都带有synchronzied,所有是线程安全的。
  • 底层是哈希表数据结构
  • 初始化容量是11,默认加载因子是0.75
  • 扩容是原容量*2+1
  • Hashtable的key和Value都不能为null(区别于HashMap)

14. Properties

(1) 基本概念
  • Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型
  • Properties被称为属性类对象,是线程安全的。

(2)存取方法
public static void main(String[] args){
    Properties pro = new Properties();
    
    //存
    pro.setProperty("username","root");
    pro.setProperty("password","123456");
    
    //取
    String username = pro.getProperty("username");
    String password = pro.getProperty("password");
}

15. TreeSet/TreeMap

(1) 基本概念
  • TreeSet集合底层实际是一个TreeMap,TreeMap集合底层是一个红黑二叉树,迭代器采用中序遍历
  • 放到TreeSet集合中的元素,等同于放到TreeMap集合key部分
  • TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序
public class Demo{

    public static void main(String[] args) {

        TreeSet<String> t = new TreeSet<>();
        t.add("zhangsan");
        t.add("lisi");
        t.add("zhanger");
        t.add("lisa");
        
        
        for(String s : t){
            System.out.println(s);  
        }
        /*升序
          lisa
		  lisi
		  zhanger
		  zhangsan
        */
    }
}

(2) 自定义类型排序

对自定义类型,TreeSet无法直接进行排序,因为没有指定自定义类型的比较规则。

解决办法:

  • 方法一:自定义类实现java.lang.Comparable接口

    public class Demo{
    
        public static void main(String[] args) {
    
            TreeSet<Customer> t = new TreeSet<>();
            t.add(new Customer(100));
            t.add(new Customer(50));
            t.add(new Customer(80));
            t.add(new Customer(20));
    
            for(Customer c : t){
                System.out.println(c);
            }
            /*
            顾客[年龄=20]
    		顾客[年龄=50]
    		顾客[年龄=80]
    		顾客[年龄=100]
            */
        }
    }
    
    class Customer implements Comparable<Customer>{  //实现Comparable接口
    
        int age;
        
        public Customer(int age){
            this.age = age;
        }
    
        @Override
        public int compareTo(Customer c) { 
            return age - c.age;  //大于0放在右子树 小于0方法左子树 等于0相等
            //return c.age-age; //降序
        }
    
        @Override
        public String toString() {
            return "顾客[" +
                    "年龄=" + age +
                    ']';
        }
    }
    
  • 方法二:使用比较器实现java.util.Comparator接口

    public class Demo{
    
        public static void main(String[] args) {
    
            TreeSet<Customer> t = new TreeSet<>(new CustoComparator()); //必须给构造方法传入比较器
            t.add(new Customer(100));
            t.add(new Customer(50));
            t.add(new Customer(80));
            t.add(new Customer(20));
    
            for(Customer c : t){
                System.out.println(c);
            }
        }
    }
    
    class Customer{
    
        int age;
        public Customer(int age){
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "顾客[" +
                    "年龄=" + age +
                    ']';
        }
    }
    
    class CustoComparator implements Comparator<Customer>{  //自定义一个比较器类实现比较器接口
    
        @Override
        public int compare(Customer c1, Customer c2) {
            return c1.age - c2.age;
        }
    }
    

    也可以直接使用匿名内部类创建比较器:

    public class Demo{
    
        public static void main(String[] args) {
    
            //匿名内部类 没有名字 直接new接口
            TreeSet<Customer> t = new TreeSet<>(new Comparator<Customer>() { 
                @Override
                public int compare(Customer c1, Customer c2) {
                    return c1.age - c2.age;
                }
            });
            t.add(new Customer(100));
            t.add(new Customer(50));
            t.add(new Customer(80));
            t.add(new Customer(20));
    
            for(Customer c : t){
                System.out.println(c);
            }
        }
    }
    
    class Customer{
    
        int age;
        public Customer(int age){
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "顾客[" +
                    "年龄=" + age +
                    ']';
        }
    }
    
    
  • 总结:

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

    • 放到集合中的元素实现java.lang.Comparable接口
    • 在构造TreeSet或者TreeMap集合的时候给它传一个构造器对象

    Comparable和Comparator怎么选择?

    • 当比较规则不会发生改变时,或者说当比较规则只有1个时,建议实现Comparable接口
    • 当比较规则有多个时,并且需要多个比较规则之间频繁切换,建议使用Comparator接口,更符合OCP原则

    String和Integer类型直接可以排序的原因就是它们已经实现了Comparable和Comparator接口


16. Collections工具类

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

  • 使ArrayList变成线程安全的

    List<String> list = new ArrayList<>();
    Collections.synchronizedList(list);
    
  • 对List进行排序

    Collections.sort(list);
    

    :对List集合中元素排序,需要保证List集合元素实现了Comparable接口,或者传入比较器

    Collections.sort(list,比较器对象);
    
  • 对Set集合排序

    public class Demo{
    
        public static void main(String[] args) {
    
            Set<String> set = new HashSet<>();
            set.add("king");
            set.add("lisi");
            set.add("liwu");
            
            List<String> list = new ArrayList<>(set); //将set集合转换成list集合
            Collections.sort(list);
            
            for(String s : list){
                System.out.println(s);
            }
        }
    }
    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值