java——集合框架
集合框架
集合大家族
一、Collection 接口
Collection所代表的是一种规则,它所包含的元素都必须遵循一条或者多条规则。
在Java中所有实现了Collection接口的类都必须提供两套标准的构造函数,一个是无参,用于创建一个空的Collection,一个是带有Collection参数的有参构造函数,用于创建一个新的Collection,这个新的Collection与传入进来的Collection具备相同的元素。
主要的方法有:
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等
键以某种排序规则排序,内部以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的方法。