java集合-Map接口

        Map接口可以将键(Key)映射到值(Value)的对象,一个键可以映射到最多一个值。这意味着在Map中,每个键 (K) 都是唯一的,但值 (V) 可以重复。

        Set接口在内部也是使用键值对(K-V)的方式来存储元素的(这是基于它们底层使用的Map实现)。在Set接口中,键(K)就是Set中存储的元素本身,而值(V)通常是一个固定的占位符对象,通常是内部类的一个实例,例如HashMap中的PRESENT,它是一个静态的Object实例。

Map介绍

        1、Map和Collection并列存在,用于保存具有映射关系的数据(K-V双列元素)。
        2、Map中的 key 和 value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中
        3、Map中的 key 不允许重复(value可重复),如有重复,则会将新的K-V替换掉原本的value值
        4、Map中的K和V都可为空,但K只能有一个为空,V可以有多个为空

        Map myMap = new HashMap();
        myMap.put("key1","value1");
        myMap.put(null,null);//k和v都为空
        myMap.put(null,"value");//k也为空,v会替换之前的值
        myMap.put("key2",null);//v为空
        myMap.put("key3",null);//v再次为空

        此时Map:{key1=value1, null=value, key2=null, key3=null}

        5、一般使用String来作Map的key,其他类型的数据也可,但不推荐
        6、一对k-v存放在一个HashMap$Node = newNode(hash, key, value, null)中
        7、为方便遍历,还会创建EntrySet集合,该集合存放的元素类型为 Entry ,而每一个Entry实际存放的是HashMap$Node(因为static class Node<K,V> implements Map.Entry<K,V>),以便使用Entry的 getKey 和 getValue 方法,同时还提供了 KeySet (属Set接口) 和 values (属Collection接口) 类以便单独查看K和V

常用方法

        1、put():添加元素,依次输入k和v的值
        2、remove():删除指定元素,有两种重载版本
remove(Object key):删除该键下的所有元素,删除成功返回该k对应的v值,失败返回null;remove(Object key,Object value):只有当键存在且与给定的值相匹配时,才会从映射中移除该键值对。成功返回true,失败返回false
       
3、get():查看元素,输入k,返回对应的v值,不存在则返回null,返回的元素类型由value的类型确定
        4、size():查看长度,返回该集合的元素个数
        5、isEmpty():是否为空:查看该集合是否存在元素
        6、clear():清空该集合内的所有元素
        7、containsKey():查找该键是否存在;containsValue()查找该值是否存在

遍历方式

        上文我们介绍了为方便遍历,还会创建EntrySet集合以及对应的KeySet和values,所以其至少包含三种遍历方式。

一、通过Key遍历

        Map myMap = new HashMap();
        //添加元素......
        Set keySet = myMap.keySet();//创建Set类实例接收keyset集合
        for (Object o :keySet) {//增强for循环
            System.out.println(o+"-"+myMap.get(o));
        }//o为key,get查看key对应的value值
        Iterator iterator = keySet.iterator();//迭代器
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next+"-"+myMap.get(next));
        }

二、遍历Value

        Map myMap = new HashMap();
        //添加元素......
        Collection values = myMap.values();//创建Collection类实例接收values集合
        for (Object o :values) {//增强for循环
            System.out.println(o);
        }
        Iterator iterator = values.iterator();//迭代器
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next);

三、通过EntrySet遍历

        这一类型略微复杂,上文我们提到系统创建的EntrySet集合存放的元素类型为 Entry ,而每一个Entry实际存放的是Node,以便使用Entry的 getKey 和 getValue 方法。
        所以我们可以直接输出Node类型的数据,也可将其转为编译类型Entry,再使用Entry的getKey 和 getValue 方法来单独查看Key或Value。

        Map myMap = new HashMap();
        //添加元素......
        Set entrySet = myMap.entrySet();//创建Set类实例接收entrySet集合
        for (Object entry :entrySet) {//增强for循环
            //此时entry编译类型为Object,运行类型为Node
            System.out.println(entry);//直接输出Node类型元素
            Map.Entry newEntry=(Map.Entry)entry;//将entry的编译类型由Object转为Entry向下转型
            //此时entry编译类型为Entry,运行类型为Node,可调用getKey 和 getValue方法
            System.out.println("[key="+newEntry.getKey()+"   value="+newEntry.getValue()+"]");
        }
        Iterator iterator = entrySet.iterator();//迭代器
        while (iterator.hasNext()) {
            //同理
            Object next =  iterator.next();
            System.out.println(next);
            Map.Entry newEntry=(Map.Entry)next;
            System.out.println("[key="+newEntry.getKey()+"   value="+newEntry.getValue()+"]");
        }

        例题:使用HashMap添加多个员工对象,要求:
1、键(key)为员工id,值(value)为员工对象。
2、自定义员工类,包含姓名、工资、员工id。
3、使用至少两种遍历方式输出工资>18000的员工

public class Go {
    public static void main(String[] args) {
        HashMap myMap = new HashMap();
        Employee staff1 = new Employee("一一", 3000, "1st");
        Employee staff2 = new Employee("二二", 1000, "2nd");
        Employee staff3 = new Employee("三三", 1500, "3rd");
        Employee staff4 = new Employee("四四", 2000, "4th");
        myMap.put(staff1.getId(), staff1);
        myMap.put(staff2.getId(), staff2);
        myMap.put(staff3.getId(), staff3);
        myMap.put(staff4.getId(), staff4);
        System.out.println("———————————————通过EntrySet遍历———————————————");
        for (Object Entry : myMap.entrySet()) {
            Map.Entry newEntry = (Map.Entry) Entry;
            Employee emp = (Employee) newEntry.getValue();
            if (emp.getSal() > 1800)
                System.out.println(newEntry);
        }
        System.out.println("————————————————通过KeySet遍历————————————————");
        for (Object key : myMap.keySet()) {
            Employee emp=(Employee)myMap.get(key);
            if (emp.getSal()>1800)
            System.out.println(key+"="+myMap.get(key));
        }

        System.out.println("————————————————通过values遍历————————————————");
        for (Object o : myMap.values()) {
            Employee emp=(Employee)o;
            if (emp.getSal()>1800)
                System.out.println(emp.getId()+"="+o);
        }
        System.out.println("————————————————通过迭代器遍历————————————————");
        Iterator iterator = myMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry newNext = (Map.Entry) iterator.next();
            Employee employee = (Employee) newNext.getValue();
            if (employee.getSal() > 1800)
                System.out.println(newNext);
        }
    }
}
class Employee {省略......}

执行结果

———————————————通过EntrySet遍历———————————————
1st=Employee{name='一一', sal=3000.0, id='1st'}
4th=Employee{name='四四', sal=2000.0, id='4th'}
————————————————通过KeySet遍历————————————————
1st=Employee{name='一一', sal=3000.0, id='1st'}
4th=Employee{name='四四', sal=2000.0, id='4th'}
————————————————通过values遍历————————————————
1st=Employee{name='一一', sal=3000.0, id='1st'}
4th=Employee{name='四四', sal=2000.0, id='4th'}
————————————————通过迭代器遍历————————————————
1st=Employee{name='一一', sal=3000.0, id='1st'}
4th=Employee{name='四四', sal=2000.0, id='4th'}

HashMap

        由于HashMap与Map的特点高度重和,因此仅简单总结一下:HashMap是Map接口使用频率最高的实现类;以K-V的方式来存储Node型数据,k不能重复但v可以重复,再次添加已有的k会覆盖原k-v,可添加null;HashMap未实现同步,因此线程是不安全的(无synchronized)。与HashSet相同,其取出的顺序也不能保证有序,因为其底层也是根据hash值来存储的。
        1、HashMap的底层维护了Node类型的数组table,默认为null;
        2、首次添加元素数组大小为16,加载因子为0.75(LoadFactor),临界值为16*0.75=12。之后再扩容则直接扩至2倍;当哈希桶(链表)添加第9个元素时,数组会进行2倍扩容,之后每多添加一个扩容一次,直至满足64个元素,开始树化。
        3、添加k-v时,通过hash值得到索引,判断该索引处是否有元素,无则直接添加,有则先判断两k是否相等,相等则替换v,不相等则判断为树结构还是链表结构并进行相应操作,容量不足则扩容;

Hashtable

        Hashtable和HashMap属同一级,都实现了Map接口,其大体也与HashMap相同:
1、存放的元素为键值对K-V。
2、常用方法基本相同。
        但也有一定的区别:
1、Hashtable的键和值都不能为null,否则会抛出空指针异常。HashMap可以为null
2、Hashtable为线程安全、HashMap为非线程安全(无synchronized)
3、Hashtable的底层维护了Hashtable$Entry,初始大小为11,加载因子为0.75(LoadFactor),临界值为11*0.75=8,之后再扩容则直接扩至(2倍+1);

Properties

        Properties为Hashtable的子类,也是使用键值段来保存数据。Properties还可用于从XXX.properties文件中加载数据到Properties类对象,并进行读取和修改。

        XXX.properties文件通常为配置文件,后续IO流则会提及

        比如在保存数据库密码时,如果将账号密码放入java文件中,那么每次修改都需重新编译,而保存在Properties文件中,修改相对简单

        增/改:put()添加一对键值,如键已存在,则修改对应的值
        删:remove()两种重载,1,remove(Object key)删除指定键所对应的所有对象;2 remove(Object key,Object value)删除指定键值对
        查:两种方法:1、get()查找该键所对应的所有值,查找到则返回Object类,未找到返回null;2、pro.getProperty()两种重载:a、pro.getProperty(String key)同get方法,但只能查找String,返回值也为String,b、pro.getProperty(String key,String xxx)查找失败则返回String xxx,查找成功则等同上文

        Properties pro = new Properties();
        pro.put(111,"第一");
        pro.put(222,"第二");
        pro.put(333,"第三");
        pro.remove(111);
        pro.put(222,"新元素");
        System.out.println(pro);
        System.out.println(pro.get(222));
        System.out.println(pro.getProperty("222","该元素不存在"));

 执行结果:

{333=第三, 222=新元素}
新元素
该元素不存在

TreeMap

        其与TreeSet极其相似,在此仅举例说明

        //使用无参构造器
        TreeMap ts = new TreeMap();
        ts.put("jjjj", "aaaa");
        ts.put("aaaa", "aaaa");
        ts.put("cccc", "aaaa");
        ts.put("kkkk", "aaaa");
        ts.put("kkkk", "aaaa");//重复元素不添加,仅替换value
        System.out.println(ts);
输出:{aaaa=aaaa, cccc=aaaa, jjjj=aaaa, kkkk=aaaa}
        //自定义比较器-----------------------------
        TreeMap ts = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).length()-((String)o2).length();
            }
        });
        ts.put("j", "aaaa");
        ts.put("aa", "aaaa");
        ts.put("ccc", "aaaa");
        ts.put("kkkk", "aaaa");
        ts.put("pppp", "aaaa");//长度重复不添加,仅替换value
        System.out.println(ts);
输出:{j=aaaa, aa=aaaa, ccc=aaaa, kkkk=aaaa}

        当添加第一个元素时,也会调用比较器,但此时数组内只有一个元素,所以系统会将两值的key相比较,此举只起检查是否为空值的作用,对结果无影响。 

总结:如何选择类

        在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类的特性进行选择:

        先判断存储的类型(一组对象[单列]还是一组键值对[双列])

一、一组对象[单列]:Collectionj接口

  1. 允许重复:List
    1. 增删少:LinkedList(底层维护了一个双向链表)
    2. 增删多:ArrayList(底层维护了Object类型的可变数组)
  2. 不允许重复:Set
    1. 无序:HashSet(底层为HashMap,维护了一个哈希表,即数组+链表+红黑树)
    2. 排序:TreeSet
    3. 插入取出顺序一致:LinkedHashSet(维护数组+链表,底层为LinkedHashMap)

二、一组键值对[双列]:Map

  1. 无序:HashMap(jdk8以上为数组+链表+红黑树)
  2. 排序:TreeMap
  3. 插入取出顺序一致:LinkedHashMap(维护数组+链表)
  4. 便于读取修改:Properties
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值