• java——集合框架


    集合框架

    image

    集合大家族


    一、Collection 接口

      Collection所代表的是一种规则,它所包含的元素都必须遵循一条或者多条规则。
      在Java中所有实现了Collection接口的类都必须提供两套标准的构造函数,一个是无参,用于创建一个空的Collection,一个是带有Collection参数的有参构造函数,用于创建一个新的Collection,这个新的Collection与传入进来的Collection具备相同的元素。

    主要的方法有:

    image

    Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。


    1. List接口

    存储有序的,可以重复的元素.
    |------ArrayList(主要的实现类)——动态数组
    |------LinkedList(更适用于频繁的插入、删除操作)——双向链表
    |------Vector(古老的实现类、线程安全的,但效率要低于ArrayList)——动态数组

    ArrayList
      ArrayList是一个动态数组,也是我们最常用的集合。它允许任何符合规则的元素插入甚至包括null。每一个ArrayList都有一个初始容量(10),该容量代表了数组的大小。随着容器中的元素不断增加,容器的大小也会随着增加。在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就会进行扩容操作。所以如果我们明确所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间、效率。

      ArrayList擅长于随机访问。同时ArrayList是非同步的。

    Collection 集合常用三种遍历方法

    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    class User{
    	private String number;
    	
    	private String name;
    	
        //get set 方法
    	
    	public User(){}
    
    	public User(String number, String name) {
    		super();
    		this.number = number;
    		this.name = name;
    	}
    
    	@Override
    	public String toString() {
    		return "User [number=" + number + ", name=" + name + "]";
    	}
    	
    }
    
    public class Main {
    	public static void main(String[] args) {
    		List<User> list = new ArrayList<User>();
    		
    		User u1 = new User("1007","zhangsi");
    		User u2 = new User("1002","lisi");
    		User u3 = new User("1005","wangsi");
    		
    		list.add(u1);
    		list.add(u2);
    		list.add(u3);
    		
    		for (int i = 0; i < list.size(); i++) {
    			System.out.println(list.get(i));
    		}
    		
    		for (User user : list) {
    			System.out.println(user);
    		}
    		
    		Iterator<User> itor = list.iterator();
    		while(itor.hasNext()){
    			System.out.println(itor.next());
    		}
    		
    	}
    }

      Java中的泛型,只在编译阶段有效。成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。


    LinkedList
      LinkedList是一个双向链表。所以它除了有ArrayList的基本操作方法外还额外提供了get,remove,insert方法在LinkedList的首部或尾部插入数据。
      由于实现的方式不同,LinkedList不能随机访问,它所有的操作都是要按照双重链表的需要执行。在列表中索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。这样做的好处就是可以通过较低的代价在List中进行插入和删除操作。
      与ArrayList一样,LinkedList也是非同步的。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List: List list = Collections.synchronizedList(new LinkedList(...));


    Vector
      与ArrayList相似底层都是根据数组来实现,但是Vector是同步的。所以说Vector是线程安全的动态数组。它的操作与ArrayList几乎一样。


    2.Set接口

    存储无序的,不可重复的元素。
    |------HashSet(主要的实现类)——HashMap实现
    |------LinkedHashSet(是HashSet的子类,当我们遍历集合元素时,是按照添加进去的顺序实现的;频繁的遍历,较少的添加、插入操作建议选择此)
    |------TreeSet(可以按照添加进集合中的元素的指定属性进行排序)——TreeMap

    HashSet

      HashSet堪称查询速度最快的集合,因为其内部是以HashCode来实现的。它内部元素的顺序是由哈希码来决定的,所以它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变。

      添加进Set集合中的元素所在的类一定要重写equals() 和 hashCode()。要求重写equals() 和 hashCode()方法保持一致。

    class User{
    	private String number;
    	
    	private String name;
    	
    	public User(){}
    
    	public User(String number, String name) {
    		super();
    		this.number = number;
    		this.name = name;
    	}
    
    	@Override
    	public String toString() {
    		return "User [number=" + number + ", 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;
    		User other = (User) obj;
    		if (name == null) {
    			if (other.name != null)
    				return false;
    		} else if (!name.equals(other.name))
    			return false;
    		return true;
    	}
    
    
    	
    }
    
    public class Main {
    	public static void main(String[] args) {
    		
    		Set<User> users = new HashSet<User>();
    		
    		User u1 = new User("1007","zhangsi");
    		User u2 = new User("1002","lisi");
    		User u3 = new User("1005","wangsi");
    		User u4 = new User("1005","wangsi");
    		/**
    		 *  u3 u4 在程序中 是两个不同的对象
    		 *  而在实际情况中 u3 u4 是属于同一个用户
    		 *  因此,需要根据实际类的唯一标识重写 equals 、hashCode方法
    		 *  这方法是进行判断的依据
    		 */
    		users.add(u1);
    		users.add(u2 );
    		users.add(u3);
    		users.add(u4);//不能进行添加
    		
    		System.out.println(u4.equals(u3));
    		//重写后返回 true
    		
    		for (User user : users) {
    			System.out.println(user);
    		}
    		
    		Iterator<User> itor = users.iterator();
    		while(itor.hasNext()){
    			System.out.println(itor.next());
    		}
    		
    		
    	}
    }

    TreeSet

      基于TreeMap,生成一个总是处于排序状态的set,内部以TreeMap来实现。它是使用元素的自然顺序对元素进行排序,或者根据创建Set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。

      要求重写的compareTo()或者compare()方法与equals()和hashCode()方法保持一致。

      TreeMap是一个有序的二叉树,那么同理TreeSet同样也是一个有序的,它的作用是提供有序的Set集合。

    如下代码由于没有实现比较器,所以报错
    public class Main {
    	public static void main(String[] args) {
    		
    		Set<User> users = new TreeSet<User>();
    		
    		User u1 = new User("1007","zhangsi");
    		User u2 = new User("1002","lisi");
    		User u3 = new User("1005","wangsi");
    		
    		users.add(u1);
    		users.add(u2 );
    		users.add(u3);
    		
    		for (User user : users) {
    			System.out.println(user);
    		}
    	}
    }

     输出结果:java.lang.ClassCastException: User cannot be cast to java.lang.Comparable 

    实现比较器接口(comparable 或 comparator)并覆写方法
    第一种:给数据类型实现 comparable 接口
    class User implements Comparable<User>{
    	private String number;
    	
    	private String name;
    	
    	public User(){}
    
    	public User(String number, String name) {
    		super();
    		this.number = number;
    		this.name = name;
    	}
    
    	@Override
    	public String toString() {
    		return "User [number=" + number + ", name=" + name + "]";
    	}
    
    	@Override
    	public int hashCode() {
    		final int prime = 31;
    		int result = 1;
    		result = prime * result + ((number == null) ? 0 : number.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;
    		User other = (User) obj;
    		if (number == null) {
    			if (other.number != null)
    				return false;
    		} else if (!number.equals(other.number))
    			return false;
    		return true;
    	}
    	@Override
    	public int compareTo(User o) {
    		//调用了 String 的 compareTo()
    		return this.number.compareTo(o.number);
    	}
    
    }
    
    public class Main {
    	public static void main(String[] args) {
    		
    		Set<User> users = new TreeSet<User>();
    		
    		User u1 = new User("1007","zhangsi");
    		User u2 = new User("1002","lisi");
    		User u3 = new User("1005","wangsi");
    		User u4 = new User("1005","wangsiwk");
    		
    		users.add(u1);
    		users.add(u2 );
    		users.add(u3);
    		users.add(u4);//compareTo 返回 0 表示该对象相同0
    		
    		for (User user : users) {
    			System.out.println(user);
    		}
    	}
    }
    第二种:实现Comparator接口的实现类的对象作为形参传递给TreeSet的构造器
    class MyComparator implements Comparator<User>{
    	@Override
    	public int compare(User o1, User o2) {
    		return o1.getNumber().compareTo(o2.getNumber());
    	}
    	
    }
    public class Main {
    	public static void main(String[] args) {
    		
    		Set<User> users = new TreeSet<User>(new MyComparator());
    		...
    	}
    第三种:TreeSet的构造器中匿名内部类(Comparator)
    public class Main {
    	public static void main(String[] args) {
    		
    		Set<User> users = new TreeSet<User>(new Comparator<User>(){
    			@Override
    			public int compare(User o1, User o2) {
    				System.out.println("my");
    				return o2.getNumber().compareTo(o1.getNumber());
    			}
    		});
    		...
    	}

    二、Map接口

      它是由一系列键值对组成的集合,提供了key到Value的映射。同时它也没有继承Collection。实现map的有:HashMap、TreeMap、HashTable、Properties、EnumMap。

    |-----HashMap:主要的实现类,可以添加null键,null值
    |-----LinkedHashMap:是HashMap的子类,可以按照添加进Map的顺序实现遍历
    |-----TreeMap:需要按照key所在类的指定属性进行排序。要求key是同一个类的对象。对key考虑使用自然排序 或 定制排序
    |-----Hashtable:是一个古老的实现类,线程安全的,不可以添加null键,null值不建议使用。
          |-----子类:Properties:常用来处理属性文件

    HashMap

    数组+链表+红黑树(JDK1.8增加了红黑树部分)实现

    public class HashMap<K,V> extends AbstractMap<K,V>
        implements Map<K,V>, Cloneable, Serializable
        "
        AbstractMap是基于Map接口的骨干实现,
        它以最大限度地减少实现此接口所需的工作。
        "

    HashMap 重要的成员属性

    //容器最大值
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //负载因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //集合默认初始化容量(2^n)
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16;
    
    Node<K,V>[] table;

    HashMap的遍历方法

    public static void main(String[] args) {
    		
    		Map<String, User> users = new HashMap<String, User>();
    		
    		User u1 = new User("1007","zhangsi");
    		User u2 = new User("1002","lisi");
    		User u3 = new User("1005","wangsi");
    		
    		users.put(u1.getNumber(),u1);
    		users.put(u2.getNumber(),u2);
    		users.put(u3.getNumber(),u3);
    		//方法一:
    		Set<String> keys = users.keySet();
    		for (String key : keys) {
    			System.out.println(key + "=" + users.get(key));
    		}
    		//方法二![image](http://note.youdao.com/favicon.ico):
    		Set<Map.Entry<String, User>> entrys = users.entrySet();
    		for (Map.Entry<String, User> entry : entrys) {
    			System.out.println(entry.getKey() + "=" + entry.getValue());
    		}
    		//方法三:
    		Iterator<Map.Entry<String, User>> itor = users.entrySet().iterator(); 
    		while(itor.hasNext()){
    			System.out.println(itor.next().getKey() + "=" + itor.next().getValue());
    		}

      HashMap 定义了内部类 Node[K,V] 保存数据 key 和 value,而HashMap类中 Node[] table,即哈希桶数组,默认数组的大小为16,可以在构造时传入参数,初始化集合容量。传入的数应该为2的几次方,这样可以减少HashMap 的内部计算,因为如果参数不是2^n,集合会自动计算为大于并最接近的 2^n 的数。

    static class Node<K,V> implements Map.Entry<K,V> {
            final int hash;
            final K key;
            V value;
            Node<K,V> next;
            ......
        }

      通过 Node<K,V> next 指向下一个 Node[K,V] 对象形成链表,当链表长度太长(默认超过8)时,链表就转换为红黑树。

    static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
            TreeNode<K,V> parent;  // red-black tree links
            TreeNode<K,V> left;
            TreeNode<K,V> right;
            TreeNode<K,V> prev;    // needed to unlink next upon deletion
            boolean red;
            TreeNode(int hash, K key, V val, Node<K,V> next) {
                super(hash, key, val, next);
            }
            .....
    }

    HashMap put方法的详细执行过程

    1.确定哈希桶数组索引位置

      HashMap定位数组索引位置,直接决定了hash方法的离散性能。先看看源码的实现

    方法一:
    static final int hash(Object key) {   //jdk1.8 & jdk1.7
         int h;
         // h = key.hashCode() 为第一步 取hashCode值
         // h ^ (h >>> 16)  为第二步 高位参与运算
         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    方法二:
    static int indexFor(int h, int length) { 
    //jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的
         return h & (length-1);  //第三步 取模运算 等价于 h%(length-1) 
         //位运算效率更好
    }
    
    "
    jdk1.8源码实现直接写在 putVal 方法里面
    
     if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
    "
    2.分析HashMap的put方法

    put方法执行过程可以通过下图来理解

    (http://tech.meituan.com/img/java-hashmap/hashMap put方法执行流程图.png)

    ①.取key的hashCode值、高位运算、取模运算得到数组 Node<k,v>[] 的索引;判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];

    ②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;

    ③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;

    ④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;

    ⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可

    ⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

    Java8系列之重新认识HashMap


    Hashtable

      也是以哈希表数据结构实现的,解决冲突时与HashMap也一样也是采用了散列链表的形式,不过性能比HashMap要低。

    public class Hashtable<K,V>
        extends Dictionary<K,V>
        implements Map<K,V>, Cloneable, java.io.Serializable 
        
        "Dictionary是任何可将键映射到相应值的类的抽象父类"

    与HashMap的区别

      HashMap可以允许存在一个为null的key和任意个为null的value,但是HashTable中的key和value都不允许为null。当HashTable遇到null时,他会直接抛出NullPointerException异常信息。
      Hashtable的方法是同步的,而HashMap的方法不是。从实现上看,实际hashmap比hashtable改进良多,不管hash方案,还是结构上多红黑树,唯一缺点是非线程安全。 但是hashtable的线程安全机制效率是非常差的,现在能找到非常多的替代方案,比如Collections.synchronizedMap,courrenthashmap等


    TreeMap

    键以某种排序规则排序,内部以red-black(红-黑)树数据结构实现,实现了SortedMap接口

    public class TreeMap<K,V>
        extends AbstractMap<K,V>
        implements NavigableMap<K,V>, Cloneable, java.io.Serializable

    Collections工具类

    Collections工具类:操作Collection及Map的工具类,大部分为static的方法。

  • 相关阅读:
    X-Plosives (并查集)
    HDU1272小希的迷宫 (并查集)
    React 初学
    js 插件 issue
    js常用方法
    常用网址
    js 零零散散的总结。
    git 常用命令
    es6 babel编译
    屏幕适配
  • 原文地址:https://www.cnblogs.com/SacredOdysseyHD/p/8343567.html
Copyright © 2020-2023  润新知