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;
}
}
}
哈希表的结构如下图:
(从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比较器,因为比较器可以写多个,直接调用就行。