Java数据结构-Map&Set

1:Set

在这里插入图片描述

Set适合动态查找的集合容器。

set底层是纯K模型

  • HashSet:作为set接口的主要实现类,线程不安全,可以存储null值
  • LinkedHashSet:HashSet的子类
  • TreeSet:使用红黑树存储

HashSet:底层为 数组+链表

HashSet:存储元素不重复且无序(存储数据并非按照底层数组的索引顺序添加)

	public class HashSet<E>  extends AbstractSet<E> implements Set<E>, Cloneable,Serializable
	{
    static final long serialVersionUID = -5024744406713321676L;
	//HashSet底层存在一个HashMap,而set存入的值被作为map的key,所以不能存入重复元素
    private transient HashMap<E,Object> map;
	}

	//HashSet的add方法
  	public boolean add(E e) {
        //如果未包含此次存入的数据返回true,如果要存入的数据已存在返回false
        return map.put(e, PRESENT)==null;
    }

HashSet的遍历方式:

  1. foreach
  2. 迭代器
  3. toArray
  Set<String> set=new HashSet<>();
        //可以存储null
        set.add(null);
 		//即使添加顺序为cba但输出为abc
         set.add("c");
         set.add("b");
         set.add("a");
         //foreach
         for (String s :
                 set) {
             System.out.println(s);
         }
         //toArray后遍历
         Object[] objects = set.toArray();
         for(int i=0;i<objects.length;i++){
             System.out.println(objects[i]);
         }
         //迭代器
         Iterator<String> iterator = set.iterator();
         while (iterator.hasNext()){
             System.out.println(iterator.next());
         }

LinkedHashSet:作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据的前一个和后一个数据,因此对于频繁的遍历操作,LinkedHashSet效率高于HashSet,且可以按照添加顺序遍历。

   Set<Object> set=new LinkedHashSet<>();
         set.add(null);
         set.add("c");
         set.add("b");
         set.add("a");
         //[null, c, b, a]
         System.out.println(Arrays.toString(set.toArray()));

TreeSet:底层为红黑树,可以按照对象属性排序输出(Comparable,Comparator)

TreeSet不可以存入空值,会报NullPointerException

排序方式1:自然排序

	//自定义类需要实现Comparable,Comparator接口
	class u implements Comparable{
	 	int id;
    	public u(int id) {
        	this.id = id;
    	}
    	@Override
    	public int compareTo(Object o) {
     	  if(o instanceof u){
              //按照id从大到下
        	    return ((u) o).id-this.id;
       	 }else {
        	    return 0;
       	 }
   		}
   	}

 	 public static void main(String[] args) {
        Set<Object> set=new TreeSet<>();
        set.add(new u(1));
        set.add(new u(2));
        set.add(new u(3));
        set.add(new u(4));

        Iterator<Object> iterator = set.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
            /*
            u{id=4}
			u{id=3}
			u{id=2}
			u{id=1}
           */
       }
    }

排序方式2:定制排序

Comparator<u> comparator=new Comparator<u>() {

            @Override
            public int compare(u o1, u o2) {
                return o1.id-o2.id;
            }
        };

        Set<u> set=new TreeSet<>(comparator);
        set.add(new u(1));
        set.add(new u(2));
        set.add(new u(3));
        set.add(new u(4));

        Iterator<u> iterator = set.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }

2:Map

在这里插入图片描述

Map:Key-Value模型

  • HashMap:作为Map的主要实现类,线程不安全效率高,可以存储null的key和value。
  • LinkedHashMap:HashSet的子类,保证在遍历Map元素按照添加顺序进行遍历。
  • TreeMap:底层采用红黑树保证按照添加的key-value(键值对)进行遍历,实现排序遍历,此时考虑使用key的自然或者定制排序,不能存储null的key和value。
  • Hashtable:作为Map的古老实现类,线程安全效率低,不能存储null的key和value。(多线程推荐使用ConcurrentHashMap)

HashMap:jdk7的底层结构只有数组+链表,jdk8的底层结构是数组+链表+红黑树,树化是为了优化查找效率。

HashMap底层重要常量JDK8:

> 	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 默认长度为16

>  	static final float DEFAULT_LOAD_FACTOR = 0.75f; //负载因子0.75
 	
>  	static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量2的30次方
 	
>  	static final int TREEIFY_THRESHOLD = 8; //树化条件之一,链表长大于8
 	
>  	static final int MIN_TREEIFY_CAPACITY = 64; //树化条件之一,数组长大于64
 	
>  	static final int UNTREEIFY_THRESHOLD = 6; //红黑树变为链表条件,链表长为6

HashMap构造方法JDK8:

	// initialCapacity:指定大小  loadFactor:负载因子
	public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

	//指定大小
	public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
	//负载因子为给定的0.75
	public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

HashMap的创建JDK1.8:当new一个HashMap对象且没有指定容量时,底层数组长度为0,当第一次put元素时,数组长默认为16

> 	//部分源码
> 	final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
>                    boolean evict) {
>         Node<K,V>[] tab; Node<K,V> p; int n, i;
>         if ((tab = table) == null || (n = tab.length) == 0)
>             n = (tab = resize()).length;//此时底层数组的长度为0
>             ......
>                 
>     //第一次put的时候数组长度设置为16
>     else {               // zero initial threshold signifies using defaults
>          newCap = DEFAULT_INITIAL_CAPACITY;//16
>          newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
>     }

HashMap的扩容机制JDK1.8:2倍扩容

> 		if (oldCap > 0) {
>             if (oldCap >= MAXIMUM_CAPACITY) {
>                 threshold = Integer.MAX_VALUE;
>                 return oldTab;
>             }
>             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
>                      oldCap >= DEFAULT_INITIAL_CAPACITY)
>                 newThr = oldThr << 1; // double threshold 二倍扩容
>         }

HashMap的put流程JDK1.8:

  1. 计算哈希值,确认索引位置
  2. 该位置没有元素则直接添加
  3. 该位置有元素,使用equals方法判断是否相对,equals相等则替换,equals不等则插入链表
  4. 插入元素可能发生扩容和树化,如果此时链表节点超过8个需要树化或者扩容,如果数组长小于64则2倍扩容,如果数组大于64则树化。
  5. JDK1.8插入链表使用的尾插法,JDK1.7是头插法

hash()和equals():

  1. 计算哈希值,确定索引位置,如果此位置为空,则插入数据(插入成功情况一)

  2. 如果此位置不为空,代表此位置存在一个或者多个数据(多个数据以链表或者红黑树的形式存在),比较要插入的数据的哈希值和已存在的数据的哈希值:

    1. 如果哈希值不同,则插入数据(插入成功情况二)
    2. 如果哈希值相同,调用插入数据所在类的equals()方法比较数据是否相同
      1. 如果equals()返回false,则插入数据(插入成功情况三)
      2. 如果equals()返回true,则替换之前的数据(修改功能)
   //put方法
	public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    //计算哈希值
    static final int hash(Object key) {
        int h;
        //右移16位和本身进行疑惑,为了让32位数据都参与到哈希值的计算中可以减少哈希冲突
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
	
	  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
          //数组长度n-1 与 哈希值获取index下标 这样计算方式比模运算快
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                   for (int binCount = 0; ; ++binCount) {
                   if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st链表的结点超过8
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }

      //treeifyBin(tab, hash); 如果数组长度小于64则扩容无需树化
            if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();

HashMap的遍历方式:

  1. key和value分开遍历

       HashMap<String,Integer> map=new HashMap<>();>        map.put("1",1);
         map.put("2",2);
         map.put("3",3);
         map.put("4",4);
         //遍历key
         Set<String> keySet = map.keySet();
         Iterator<String> iterator = keySet.iterator();
         while (iterator.hasNext()){
             System.out.println(iterator.next());
         }
         //遍历value
        Collection<Integer> values = map.values();
         for(Integer value:values){
             System.out.println(value);
         }  
    
    
  2. 使用entrySet( )

    	HashMap<String, Integer> map = new HashMap<>();
    
        map.put("1", 1);
        map.put("2", 2);
        map.put("3", 3);
        map.put("4", 4);
    
        //遍历key value
        Set<Map.Entry<String, Integer>> entries = map.entrySet();
        Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
        while (iterator.hasNext()){
            Map.Entry<String, Integer> next = iterator.next();
            System.out.println(next.getKey()+"-->"+next.getValue());
       }

LinkedHashMap:在HashMap的底层结构的基础上,添加了一对指针,指向前一个和后一个元素,对于频繁遍历的操作,此类执行效率高于HashMap,而且可以保证遍历顺序为添加顺序。

LinkedHashMap的内部类Entry:添加了before after 知道了前后的Entry 记录了添加顺序

 static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

TreeMap:底层为红黑树,保证按照添加的key-value(键值对)进行遍历,实现排序遍历,此时考虑使用key的自然或者定制排序。(Comparable,Comparator)

定制排序:Comparator

Comparator<String> comparator=new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o2.compareTo(o1);//key从大到小
            }
        };

        Map<String,Integer> map=new TreeMap<>(comparator);

        map.put("1", 1);
        map.put("2", 2);
        map.put("3", 3);
        map.put("4", 4);

        Set<Map.Entry<String, Integer>> entries = map.entrySet();
        Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }

自然排序:Comparable

//实体类
class U implements Comparable<U>{
    int id;
    @Override
    public int compareTo(U o) {
        return o.id-this.id;//id从大到小
    }
    //toString() hashCode() equals()...
}

//按照id从大到小遍历
Map<U,String> map=new TreeMap<>();

        map.put(new U(1), "一号");
        map.put(new U(2), "二号");
        map.put(new U(3), "三号");
        map.put(new U(4), "四号");

        Set<Map.Entry<U, String>> entries = map.entrySet();
        Iterator<Map.Entry<U, String>> iterator = entries.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值