Map集合总结

  • Map集合要点
  • 1、Map集合的特点
  • 2、Map集合的定义
  • 3、Map集合常用方法
  • 4、遍历Map集合的两种方式
  • HashMap集合
  • 1、HashMap集合底层是哈希表,哈希将链表的随机增删效率和数组查找效率的优点结合在一起。
  • 2、哈希表是一个一维数组,数组中每个元素是链表节点,类型为Node,Node类是HashMap类中的一个内部类,HashMap类的关于哈希表主要属性和方法源码如下:
  • 3、哈希表查询和随机增删效率都高的原因:
  • 4、HashCode()方法与equals方法的重写
  • 5、HashMap集合的初始化容量必须必须是2的幂,可提高效率。
  • 6、JDK8之后,当hashMap集合中的单向链表结点超过8个,会把这个链表转化成红黑树(平衡二叉B树),这样就可以提高检索效率,若结点从8以上第一次减少到6以下,又重新变成链表
  • TreeMap集合
  • 1、TreeMap底层是二叉树,可以通过一个Comparable接口中的CompareTo方法进行元素的大小比较,进行排序。
  • 2、TreeMap集合排序原理(两种方法)。


Map集合要点

1、Map集合的特点

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

2、Map集合的定义

Map<KeyType,ValueType> map=new HashMap<>()

3、Map集合常用方法

       void clear();
       清空集合

       boolean containskey(Object key);
       判断是否包含某个键

       boolean containsValue(Object value);
       判断是否包含某个键

       V get(Object key);
       通过key获取value

       V put(K key, V value);(Set集合调add方法时底层调了该方法)
       向Map集合中添加键值对

       Set keySet();
       获取Map集合所有的key,(key部分其实是一个Set集 合)

       V remove(Object key);
       通过key删除键值对.

       int size();
       获取Map集合中键值对的个数

       Collection values();
       获取Map集合中所有的value,返回一个Collection.(value部分是一个Collection集合)

       Set<Map.Entry<K, V>> entrySet();
       将Map集合转换成Set集合
       (key部分变为下标,value部分变为set集合元素,所存储的类型是Map.Entry<K, V>,这是一个Map中的静态类)

4、遍历Map集合的两种方式

       第一种
       获取所有的key,放到一个Set集合中,通过遍历Set集合来获取相应的元素

Map<Integer,String> map=new HashMap<Integer, String>();
	map.put(1, "zhangsan");
	map.put(2, "lisi");
	map.put(3,"wangwu");
	map.put(4, "zhaoliu");
		
	Set<Integer> s=map.keySet();
		
	for(Integer key:s) {
		System.out.println(map.get(key));
	}

       第二种(这种方式效率高)
       将Map集合通过entrySet()方法转换成一个Set集合(里面存储的元素类型为Map.Entry<Integer, String>),代码如下:

Map<Integer,String> map=new HashMap<Integer, String>();
		map.put(1, "zhangsan");
		map.put(2, "lisi");
		map.put(3,"wangwu");
		map.put(4, "zhaoliu");
		//转换成Set集合
		Set<Map.Entry<Integer, String>> s=map.entrySet();
		
		Iterator it=s.iterator();
		while(it.hasNext()) {
			Map.Entry<Integer, String> node=(Entry<Integer, String>)it.next();
			/*Map.Entry<Integer, String>是Map集合的一个内部类,有getKey()和
			  getValue()方法,分别用来获取转换后的Set集合元素在原Map集合中对应的key和value*/
			Integer key=node.getKey();
			String value=node.getValue();
			System.out.println(key+"="+value);
		}

HashMap集合

1、HashMap集合底层是哈希表,哈希将链表的随机增删效率和数组查找效率的优点结合在一起。

2、哈希表是一个一维数组,数组中每个元素是链表节点,类型为Node,Node类是HashMap类中的一个内部类,HashMap类的关于哈希表主要属性和方法源码如下:

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    transient Node<K,V>[] table;//存储Node节点的一维数组
    
	static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
 		}
            return false;
        }
    }
}

哈希表的结构如下图:

java map数组对象 java的map集合_java map数组对象


(从get方法实现原理可以发现,Set集合的元素不可重复其实是key不可重复,而key部分主要是放在Set集合中的,set集合根据key对元素进行操作)

哈希表中同一个链表上的hash值是相等的,hash值通过哈“希算法”转换数组下标

3、哈希表查询和随机增删效率都高的原因:

从哈希表结构图可以看到,随机增删是在链表部分进行增删,数组部分不需要变动。
查询先在数组找到对应的链表,再从链表中查找,虽然也要找链表,但不是每个链表都找。

4、HashCode()方法与equals方法的重写

       a、在所有集合中,所有equals方法都需要重新,在HashSet和HashMap集合中,要同时重写这两个方法。
       b、HashCode方法是用于返回一个hash值,必须要有规律,若返回值都相同,则元素都放在一个链表上,哈希表变成了单向链表,无法发挥数组的查询优点。同样,若返回值都不同,则哈希表成了一维数组。
       c、在HashMap集合中,存储和取出元素时要调用equals方法和hashcode方法,首先调用的是HashCode方法, 有可能不调用equals方法(当数组位置没有链表,即为null时,equals方法不调用)。
       d、在HashMap集合中,放在key部分的元素和Value部分的元素,两个都要重新HashCode和equals方法。
       e、eclipse工具下定义类中可自动生成equals方法和HashCode方法,要选择一个属性,作为两个方法的中比较的内容,代码如下(选择的属性是name):

@Override
class Stu{
	String name;
	
	public Stu(String name) {
		this.name=name;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Stu other = (Stu) obj;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	
	
}

可以看到,重写的hashcode方法中返回的值与name.hashcode()有关,而name是字符串,同一个字符串在内存上是相等的,都是在字符串常量池中,故相同的name返回的hash值相等。其他类型的的hashcode方法原理也相同。

5、HashMap集合的初始化容量必须必须是2的幂,可提高效率。

源码中的注释:The default initial capacity - MUST be a power of two

6、JDK8之后,当hashMap集合中的单向链表结点超过8个,会把这个链表转化成红黑树(平衡二叉B树),这样就可以提高检索效率,若结点从8以上第一次减少到6以下,又重新变成链表

TreeMap集合

1、TreeMap底层是二叉树,可以通过一个Comparable接口中的CompareTo方法进行元素的大小比较,进行排序。

2、TreeMap集合排序原理(两种方法)。

第一种方法:实现Comparable接口和该接口中的CompareTo方法。
       a、String类的比较规则:对于多个字符,按从左往右出现第一个不同的字母开始按从小到大的顺序排序(String类已经实现了Comparable接口)。
       b、(TreeMap的put方法添加元素时将key强转成Comparable,故TreeMap中存的元素要实现Comparable接口和Comparable接口中的public int compareTo(T o)方法,该方法中要写比较元素的规则的代码)
TreeMap中ComparTo方法比较结果处理的代码(第二种方法原理类似)

else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);

结果处理解释:第一步:在else语句中,如果返回结果小于0,则往左子树找,大于0则往右子数找,相等则覆盖该结点,此时添加结束。(注意:k是put方法的参数,即Set集合add方法的参数。)第二步:如果else语句结束,都没有相等的值,则根据compareTo方法最终返回的值决定插左或右,小于0左,大于0右。

       c、对于自定义的类型,要想在TreeSet中自动排序,要实现Comparable接口或实现比较器接口comparator。
如下列代码(实现Comparable接口):

public class TreeSet集合 {
	public static void main(String[] args) {
		Set<Student> strs=new TreeSet<Student>();
		
		Student s1=new Student(1);
		Student s2=new Student(4);
		Student s3=new Student(3);
		Student s4=new Student(2);
		strs.add(s1);
		strs.add(s2);
		strs.add(s3);
		strs.add(s4);
		for(Student s:strs) {
			System.out.print(s);
		}
	}
}

class Student implements Comparable<Student>{
	int age;
	public Student(int age) {
		this.age=age;
	}
	
	//在该方法中编写比较的规则
	@Override
	public int compareTo(Student s) {//s1.comparaeTo(s2)
		//this是s1,s是s2
		//所以这里就是this与s的比较
		return this.age-s.age;
	}
	
	@Override
	public String toString() {
		return String.valueOf(age);
	}
}

上述代码compareTo方法中, 由于返回值小于0将add方法中的元素插左,大于0插右。
由于TreeMap底层的二叉树采用中顺遍历取出元素,所以:
如果要this.age(add方法参数)大于s.age(add方法参数的父结点)插左边(结果小于0),则要返回s.age-this.age,此时集合逆序排序
如果要this.age(add方法参数)大于s.age(add方法参数的父结点)插右边(结果大于0),则要返回this.age-s.age,此时顺序输出。

再如下列代码(实现比较器接口comparator):

public class TreeSet集合 {
	public static void main(String[] args) {
		Set<Wugui> strs=new TreeSet<Wugui>(new WuguiCompator());
		
		strs.add(new Wugui(100) );
		strs.add(new Wugui(300) );
		strs.add(new Wugui(200) );
		strs.add(new Wugui(400) );
		
		for(Wugui s:strs) {
			System.out.println(s);
		}
		
	}
}

class Wugui{
	int age;
	public Wugui(int age) {
		this.age =age;
	}
	public String toString() {
		return String.valueOf(age);
	}
}
class WuguiCompator implements Comparator<Wugui>{
	@Override
	public int compare(Wugui o1, Wugui o2) {
		//规则是按年龄比较
		return o1.age-o2.age;
	}
}

以上代码中,比较的原理和CompareTo的过程相似。

自定义集合元素类型实现自动排序的两种方式的选择:
a、如果比较规则不会随意改动,选择实现Comparable接口
b、如果比较规则经常变,而且是在多个规则来回切换,则选择Comparator比较器,因为比较器可以写多个,直接调用就行。