Java的第一遍学习笔记 Map集合

Map集合 

都是双列集合,存放 K-V

接口特点 

注:1. Set本来也是 Key - Value 结构,但是它的Value一直都是PRESENT,因此可以看作Key。 

       2. 虽然key不允许重复,但是如果重复添加会导致覆盖。

       3. 用 put 方法输入,用 get 方法指定key返回value。

内部创建EntrySet

   k-v 最后是 HashMap$Node node = newNode(hash,key,value,null)。注意 k-v 为了方便程序员的遍历,还会在内部创建 EntrySet 集合,KeySet集合以及Values集合。其中KeySet里面存放key,Values(当然只是引用(地址)!不是真正的结点,真正的结点在Node中)。而EntrySet则是包括KeySet与Values,里面内容的运行类型为Node。

    entrySet中,存放的元素的类型为Map.Entry<k,v>(entrySet相当于一个数组)。真正存放k-v的地方还是 HashMap$Node。  

   为什么能够转型:因为 Node类实现了Entry接口。当把 Node对象存放到 entrySet 就方便我们的遍历,因为 Map.Entry 提供了重要的方法 getKey 以及 getValue。

   如果只想使用key,那么就用KeySet。如果只想用value,那么用values。如果两者都想用,那么就用EntrySet。

 

    public static void main(String[] args) {
        Map a = new HashMap();
        a.put("张三",18); //put方法加入键值对
        a.put("李四",20);
        a.put("王五",21);
        Set b = a.entrySet(); // 用entrySet遍历,entrySet父类是Set,因此用Set接收
        for (Object obj : b) {
            Map.Entry t = (Map.Entry)obj; //向下转型,编译类型为 Map.Entry
            System.out.println(t.getKey()+" " + t.getValue());
        }
        Set c = a.keySet(); //只能操作key
        Collection d = a.values(); //只能操作value
    }

基本方法

    public static void main(String[] args) {
        Map a = new HashMap();
        a.put("张三",18);
        a.put("李四",9);
        a.put("王五",31);
        a.put("尚",20);
        System.out.println(a);//{李四=9, 张三=18, 王五=31, 尚=20}
        a.remove("李四");  //根据某个Key删除结点
        System.out.println(a);//{张三=18, 王五=31, 尚=20}
        System.out.println(a.get("张三"));//18  根据Key返回value
        System.out.println(a.size());//3 
        System.out.println(a.isEmpty());//false
        System.out.println(a.containsKey("王五"));//true  查找是否有这个Key
        a.clear(); //清除
        System.out.println(a.isEmpty());//true
    }

注:如果要修改元素的内容,也可以用put,因为可以覆盖。 P550 

遍历方法

会在泛型里进一步改写

用keySet

    public static void main(String[] args) {
        Map map = new HashMap(); //这里可以用泛型改写
        map.put("张三",18);
        map.put("李四",9);
        map.put("王五",31);
        map.put("尚",20);
        Set set = map.keySet(); //获取所有的key
        //第一种方法 增强for循环
        for (Object key : set) {
            System.out.println(key + "->" + map.get(key));//用get方法获取value
        }
        //第二种方法 迭代器
        Iterator iterator = set.iterator(); //获取set对象的迭代器
        while (iterator.hasNext()) {
            Object key =  iterator.next();
            System.out.println(key + "->" + map.get(key));
        }
    }

用values

        Collection values = map.values(); //获取values
        for (Object key : values) { //增强for
            System.out.println(key); //注意 Map中没有从value获取key的方法
        }
        Iterator iterator = values.iterator(); //迭代器 
        while (iterator.hasNext()) {
            Object key =  iterator.next();
            System.out.println(key);
        }

用EntrySet

        Set set = map.entrySet(); // entrySet是Set的子类,是Map的内部类
        //1. 增强for
        for (Object entry : set) {
      // entry是Object类,需要向下转型(如果使用了泛型就不需要了)
            Map.Entry t = (Map.Entry)entry; 
            System.out.println(t.getKey() + "->" + t.getValue());
        }
        //2. 迭代器
        Iterator iterator = set.iterator(); 
        while (iterator.hasNext()) {
            Map.Entry h = (Map.Entry)iterator.next(); //一步到位写法
            System.out.println(h.getKey() + "->" + h.getValue());
        }

P534 value为对象需要调用方法的情况 

for (Object o : s) {
    Map.Entry t = (Map.Entry)o;
    if(((Employee)t.getValue()).getSal()>18000)  //注意方法前要加括号,作为一个整体
    if((Employee)t.getValue().getSal()>18000) //这样就是错的
    ...
}

for (Object o : s) {  //可读性更强
    Map.Entry t = (Map.Entry)o;
    Employee ee = (Employee) t;
    if(ee.getSal()>18000)
}

HashMap底层 

HashMap底层源码

  因为HashSet底层就是HashMap,因此底层几乎完全相同。

 1. 执行构造器 new HashMap(),初始化加载因子 loadfactor = 0.75,HashMap$Node[] table = null。

    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    transient Node<K,V>[] table;

2. 执行put,调用hash方法,计算key的hash值(注意要进入一个方法最好使用 force step into)。

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

3. 执行putVal 方法,具体注释已经在 HashSet中给出。

HashTable

扩容

底层有数组 Hashtable$Entry[],初始化大小为 11。临界值 threshold 为8 = 11 * 0.75。

调用put方法里的 addEntry(hash, key, value, index);  当满足 if (count >= threshold)  扩容(rehash)

int newCapacity = (oldCapacity << 1) + 1; //新容量计算方法
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
 // 临界值计算方法

Properties

Java 读写Properties配置文件 - 旭东的博客 - 博客园  感兴趣可以看这篇文章。

注意事项

1. Properties 继承 Hashtable,是无序的。

2. 可以通过 k-v 存放数据,当然key和value不能为null。

3. 常用方法:增 put(key,value),删 remove(key),改 put(相同的key,value),查 get(key)。

开发中如何选择集合实现类

一组对象指的就是只有key,没有value。 

TreeSet

构造方法

正常的TreeSet声明应该是这样的:

    TreeSet a = new TreeSet();

 但是TreeSet有一个构造器,可以传入一个比较器Comparator(匿名内部类)

    public static void main(String[] args) {
        TreeSet a = new TreeSet(new Comparator() { //匿名内部类
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).compareTo((String)o2); //按照字符串大小比较
            }
        });
        a.add("jack");
        a.add("a");
        a.add("sss");
        a.add("mmm");
        System.out.println(a);// [a, jack, mmm, sss]
    }

 要注意一个问题:假设要求按照字符串的length来从小到大排序

    public static void main(String[] args) {
        TreeSet a = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).length() - ((String)o2).length(); //从小到大
            }
        });
        a.add("jack");
        a.add("a");
        a.add("sss");
        a.add("mmm");
        System.out.println(a); // [a, sss, jack]
    }

  可以发现 "mmm" 并没有加入进去,那么我们就需要追一下源码了。

        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        } // 初始化,生成一个新结点
        int cmp;
        Entry<K,V> parent;
        Comparator<? super K> cpr = comparator; //重点在这里,把比较器赋过去
        if (cpr != null) {
            do {   //对整个链表(key)进行循环,给当前key找适当位置
                parent = t;
                cmp = cpr.compare(key, t.key); //比较原结点与要加入的结点的key
                                               //这里会动态绑定到匿名内部类对象
                if (cmp < 0) 
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;  //按照比较结果移动指针
                else  //遍历过程中发现准备添加的key和当前已有的key相等
                    return t.setValue(value); //由于Set的value为PRESENT,因此相当于没加
            } while (t != null); //循环结束后,t就指向结点应该加入的位置
                                 //parent为上一次,因此结束后还需要再移动一次
        }
        ... //没有比较器的情况
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e; //按照比较结果把结点e放在正确位置(parent是原来的t)
        fixAfterInsertion(e);
        size++; //结点个数加一
        modCount++; //修改次数加一
        return null;
    }

   "sss" 是先加入的,长度为3。因为自定义的比较器是比较长度的,而 "mmm" 的长度也为3,因此结果为0,直接不加入了(参考do里面的else情况)。

   由于TreeSet的底层是TreeMap,因此比较器初始化方法在TreeMap里。

    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

 TreeSet和TreeMap的底层都是TreeMap,因此TreeMap的源码不再做解析。

关于TreeSet加入自定义类 

    如果TreeSet没有重写Comparator,并且加入的类也没有实现Comparable接口,那么就会报错,因为add源码里需要赋予一个比较器。

        TreeSet a = new TreeSet();
        a.add(1); //这样是没有问题的,因为1相当于Integer,而Integer实现了Comparable接口
public static void main(String[] args){
    TreeSet a = new TreeSet();
    a.add(new Car("AAA",2331313)); //报错 ClassCastException
}

class Car{
    String name;
    double price;

    public Car(String name, double price) {
        this.name = name;
        this.price = price;
    }
}
        TreeSet a = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return 0;
            }
        }); //这样写就没问题了

class Car implements Comparable{ //这样写也没问题,Car类实现了Comparable接口
    String name;
    double price;

    public Car(String name, double price) {
        this.name = name;
        this.price = price;
    }

    @Override  //重写compareTo方法
    public int compareTo(Object o) {
        return 0;
    }
}

Collections

方法(均为静态) 

    public static void main(String[] args) {
        List a = new LinkedList();
        a.add("Tom");
        a.add("king");
        a.add("milan");
        Collections.reverse(a);
        System.out.println(a); //[milan, king, Tom]
        Collections.shuffle(a);
        System.out.println(a); //[Tom, king, milan]
        Collections.sort(a);
        System.out.println(a);//[Tom, king, milan] (m>k>T) 用字符串的大小比较
        Collections.sort(a, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).length()-((String)o2).length(); //按照长度从小到大
            }
        });
        System.out.println(a); //[Tom, king, milan]
        Collections.swap(a,0,2);
        System.out.println(a); //[milan, king, Tom]
    }

 注意一下copy方法参数位置以及内存不足会报错即可。 

    public static void main(String[] args) {
        List a = new LinkedList();
        a.add("Tom");
        a.add("king");
        a.add("milan");
        Object max = Collections.max(a); //返回值是一个Object
        System.out.println(max); //milan 字符串最大
        Object max2 = Collections.max(a, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).length()-((String)o2).length();
            }
        });
        System.out.println(max2);//milan,长度最大
        System.out.println(Collections.frequency(a,"Tom"));//1
        List b = new LinkedList();
        // Collections.copy(b,a); 报错 这时b还没有内存空间
        //后面是被复制的,根据源码,如果 a.size>b.size 会抛出异常
        for(int i = 0;i<5;i++){
            b.add("");
        }  //扩大b的内存
        Collections.copy(b,a);
        System.out.println(b); //[Tom, king, milan, , ] 剩下俩是""
        Collections.replaceAll(a,"Tom","Jack");
        System.out.println(a); //[Jack, king, milan]
    }

一个关于HashCode的练习题

  当把p1的name改成"CC"时,由于hashCode方法已经改写,因此p1的hash值也因此改变。通过remove的源码得知,删除的原理是根据hash码计算的一个值,因此会计算出p1对应的hash值。但问题是,计算出P1在三号位上(反正不是1号位),但是P1实际上还在一号位,因此删除失败

   加入一个 (1001,"CC")的Person时,根据hashCode计算出安放的位置(当然调用了底层代码,不是直接就是hash值),虽然跟P1hash值完全相同,但是3号位上并没有东西,因此成功安放。

   加入一个 (1001,"AA")的Person时,计算出安放位置为1号位,此时1号位有P1,因此会调用equals方法跟P1进行比较,因为P1已经修改过name了,因此不同,放在P1后面(链表) 。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值