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的方法。

原文地址:https://www.cnblogs.com/SacredOdysseyHD/p/8343567.html