Java集合
一、hashCode与equals
这两个方法来自于Object类,所以每个类型都会自动继承这2个方法.这2个方法本质上并没有任何关系,彼此独立,而且这两个方法在脱离集合的环境下也是可以使用的.但这两个方法的理解对于更好的使用集合是必不可少的.
1.equals
Object类对equals的默认实现如下
public boolean equals(Object obj) {
return (this == obj);
}
这种实现只是表示引用相等,字符串对这个方法进行了重写,用于判断两个字符串的值是否相等.比如下面的代码
String s = new String("abc");
String s2 = new String("abc");
System.out.println(s==s2); //false
System.out.println(s.equals(s2));//true
2.hashCode
hashCode方法主要用来支持java 集合框架中的Hash相关的集合,比如HashMap,HashSet等.
在Object类中hashCode的实现如下:
public native int hashCode();
这个是一个本地方法,其默认实现返回的hashCode是对象的内存地址进行hash算法得出的值.在hashCode的注释中,对hashCode的实现要求如下:
-
任何时候,反复调用同一个对象的hashCode方法,返回的值要一样
-
如果两个对象equals相等,那么其hashCode应该要一样
-
如果两个对象非equals相等,其hashCode可以一样,但不强制要求.
Hash 算法又叫散列算法,由 Hash(人名)首先提出。
Hash 算法:把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。
为什么要用 hash 算法?
Hash 算法的输出值是固定长度的,该值可以被用来作为内存地址。 这样取的时候,就可以通过计算其 hash 值来获取所在内存存储单元的地址,而快速的读取其存储的内容。
二、Comparable 与Comparator
jdk中的Comparable接口与Comparator接口都与排序有关,当对象放入到一些有序的集合时会对对象的排序性有要求,所以有必要了解这2个接口
1.Comparable
若一个类实现了Comparable接口,就意味着“该类支持排序” 此接口是jdk 1.2推出的,它的代码如下:
//java.lang jdk 1.2就有
public interface Comparable<T> {
public int compareTo(T o);
}
当一个类实现了此接口时,通常是此实现类与compareTo方法传递过来的参数进行比较,比较的规则是当自身小于传递过来的对象时返回负数,相等就返回0,大于就返回正数.比如下面的代码:
class Student implements Comparable<Student>{
String name;
int age;
Student(String name,int age){
this.name=name;
this.age=age;
}
public int compareTo(Student st){
if(age==st.age)
return 0;
else if(age>st.age)
return 1;
else
return -1;
}
}
实现了Comparable接口后,此对象放入到集合中就可以对集合中的元素进行排序了,比如下面的代码
ArrayList<Student> al=new ArrayList<Student>();
al.add(new Student("a",23));
al.add(new Student("b",27));
al.add(new Student("c",21));
Collections.sort(al);
for(Student st:al){
System.out.println(st.name+" "+st.age);
}
输出结构为:
c 21
a 23
b 27
上面的结果可以看出以升序排序,如果想改为降序排序,必须修改Student类中的实现,把返回1的地方改为返回-1,返回-1的地方改为1.也就是说想改变排序规则必须修改源代码.还有一个不足之处就是只支持固定的排序规则,比如现在的实现只支持按年龄排序,不支持按名字排序.基于这种情况就需要使用Comparator来完成这样的功能.
2.Comparator
此接口在java.util包下,用来实现两个对象之间比较功能的.并且也并不要求被比较的对象实现某个接口.此接口的核心比较代码如下:
public interface Comparator<T> {
int compare(T o1, T o2);
}
compare方法的比较逻辑与Comparable接口的compareTo方法类似,当第一个参数小于第二个参数时返回负数,相等返回0,大于返回正数.比如下面的代码实现了对Student类的年龄的比较
class AgeComparator implements Comparator<Student>{
public int compare(Student s1,Student s2){
if(s1.age==s2.age)
return 0;
else if(s1.age>s2.age)
return 1;
else
return -1;
}
}
下面的代码实现了对名字的比较
class NameComparator implements Comparator<Student>{
public int compare(Student s1,Student s2){
//String类本身实现了Comparable接口
return s1.name.compareTo(s2.name);
}
}
使用方式如下:
ArrayList<Student> al=new ArrayList<Student>();
al.add(new Student("a",23));
al.add(new Student("b",27));
al.add(new Student("c",21));
System.out.println("依据名字进行比较");
Collections.sort(al,new NameComparator());
for(Student st:al){
System.out.println(st.name+" "+st.age);
}
System.out.println("依据年龄进行比较");
Collections.sort(al,new AgeComparator());
for(Student st:al){
System.out.println(st.name+" "+st.age);
}
最终输出的结果为
依据名字进行比较
a 23
b 27
c 21
依据年龄进行比较
c 21
a 23
b 27
三、简介
数组主要存放的是同类型的数据,其最大的一个缺点就是其大小不能动态调整,这对于一些预先不确定数据量大小的情况无法应付,所以java推出了集合框架.
四、集合框架图
集合框架是存储于操纵一组对象的统一架构,它由一系列的接口和实现类组成,这些类型包含了如何存储对象以及操作这些数据的算法,比如排序算法.
整个java集合框架由Collection接口与Map接口代表.当然有一种说法认为java的集合框架只包含Collection接口体系,不包含Map接口代表的体系.下面是包含Collection与Map的整个架构:
下图是Collection接口代表的核心体系架构
五、List
List接口代表线性结构的集合,它以线性的方式来存储数据,并且允许存储重复的数据.此接口的主要实现类如下:
-
ArrayList
-
LinkedList
-
Vector
-
Stack
1.ArrayList
ArrayList是一个内部用数组实现List接口的集合类.
ArrayList<String> list=new ArrayList<String>();
list.add("a");
list.add("b");
在实例化集合对象时最好指定初始容量,容量大小尽量与预计存放的数据量差不多,值建议用2的n次方.这样可以减少集合频繁扩容造成的性能损失.如果不指定容量大小,ArrayList默认的大小为10.
ArrayList<String> list=new ArrayList<String>(4);
ArrayList由于内部是数组实现的集合,它支持通过索引来取得集合中的数据,比如
list.get(0)
通过索引取值的方式,其时间复杂度是个常量值.性能很高.但ArrayList集合删除数据或者往中间插入数据性能不高,因为其要逻动数据.
在遍历集合时,尽量不要进行集合的修改操作,比如删除元素.这非常容易产生bug,比如下面的代码:
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
for (String item : list) {
if ("2".equals(item)) { //把这里的2改为1试试
list.remove(item);
}
}
for (String item : list) {
System.out.println(item);
}
2.LinkedList
LinkedList用双链表来实现List接口,因而此类型往里面插入,删除速度性能较高.但通过索引取值的时间复杂度是O(n),性能不高.基本使用方法如下:
LinkedList<String> al=new LinkedList<String>();
al.add("a");
al.add("b");
六、Iterable与Iterator
实现了此接口的类型支持foreach循环,由于Collection接口的父类型就是Iterable,所以所有的Collection都支持foreach循环.
此接口的主要方法为Iterator<T> iterator()
此Iterator就是真正完成迭代功能的接口.Iterator接口的主要成员如下:
-
hasNext():检查迭代器是否还有元素
-
next():获取迭代器的下一个元素.
-
remove:删除一个元素.
迭代器(Iterator)中完成foreach循环的方法只有hasNext与next,remove主要用来完成在迭代的时候删除元素.
ArrayList<String> list=new ArrayList<String>();
list.add("a");
list.add("b");
Iterator it=list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
在迭代集合的时候,如果要删除元素建议用迭代器实现.
七、Map
Map类型是一种键值对类型,键和值是一一映射(map)的,每一个键值对称之为一个条目(Entry).Map包含唯一的键.Map集合非常适合依据键来搜索,更新,删除元素的情况.map的体系结构如下:
从上图中看出Map的类型主要有以下几个:
-
HashMap:它不维护插入元素的顺序(order)
-
LinkedHashMap:它维护插入的顺序
-
TreeMap:它支持排序.默认是升序排列Map中的元素.
Map不允许有重复的键,但值允许重复.HashMap与LinkedHashMap允许键与值存放null值,但TreeMap的键与值都不允许null值.
1.HashMap
HashMap 首先初始化一块内存(数组)。把内存划分为一个一个的存储空间(bucket),每一个存储空间都有一个地址与其对应。但是,一个存储空间,不限于只存一对键值对(HashMap 使用 LinkedList 存储多个具有相同 hashCode 的键值对。新加的放在最前).基本使用如下:
Map<Integer,String> map=new HashMap<Integer,String>();
map.put(1,"a");
map.put(5,"b");
map.put(2,"b");
map.get(1) //去值,结果为:a
2.存值
存放值的时候会依据键的hasCode值来确定存放的槽位(backet),如果此槽位没内容,直接存放.如果此槽位有内容就比较槽位已有对象与即将插入对象的相等性(equals方式比较),相等的话就覆盖,不相等就直接插入此槽位的linkedList里面去.
3.取值
使用一个object作为键来取HashMap 中对应的值时,HashMap 的工作方法是:通过传入的键的 hashcode() 在Map中找槽位,当找到这个槽位后再通过 equals() 来比较传入的object和地址中的object(s)(可能是多个),结果为 true 就取出 value。
4.遍历
HashMap可以分别对其键,值,键值对进行遍历,效率最高的遍历方式是键值对的遍历方式.因为只遍历键来取值的话其实是把集合遍历了2次
//键的遍历
for(Integer k: map.keySet()) {
System.out.println(k);
}
//值的遍历
for(String v:map.values()) {
System.out.println(v);
}
// 键值对遍历
for(Map.Entry<Integer,String> entry: map.entrySet()) {
System.out.println("key:" + entry.getKey() + " value: " + entry.getValue());
}
5.对象作为key
假设有这么一个类想作为Map集合中的键,在存放到集合中的时候需要注意处理其hashCode与equals方法的重写问题.
public class Person {
private int id;
public Person(int id) {
this.id = id;
}
}
使用的代码如下,最终集合中的数量为2.因为p1与p2是两个不同的对象.
Map<Person,String> map = new HashMap<>(4);
Person p1 = new Person(1);
Person p2 = new Person(1);
map.put(p1, "a");
map.put(p2,"b");
System.out.println(map.size());//输出的结果是2
如果你基于业务的需要觉得两个Person对象的id是一样的话,应该是相等的,那么你此时就应该重写equals方法.按照规范,重写equals方法时应该也相应的重写hashCode方法.利用eclipse帮我们生成的equals与hashCode方法实现,最终person类的代码如下:
public class Person {
private int id;
public Person(int id) {
this.id = id;
}
再次运行上面的代码会发现最终集合中的数量是1.
八、TreeMap
内部用树结构实现的一个有序的Map类型.其排序是依据键来进行排序的,并且要求所有的键实现了Comparable接口
Map<Integer,String> map=new TreeMap<Integer,String>();
map.put(1,"a");
map.put(5,"b");
map.put(2,"b");
for(Map.Entry<Integer,String> entry: map.entrySet()) {
System.out.println("key:" + entry.getKey() + " value: " + entry.getValue());
}
输出的结果为:
key:1 value: a
key:2 value: b
key:5 value: b
如果存放的键的类型没有实现Comparable接口会报ClassCastException.键的类型必须实现了Comparable接口.代码如下:
public class Person implements Comparable<Integer> {
private int id;
public Person(int id) {
this.id = id;
}
public int getId() {
return this.id;
}
// 省略hashCod与equals方法的实现,与上面的一样.
使用此类的代码如下,最终输出的结果为1,3,10的升序排列
Map<Person,String> map = new TreeMap<>();
Person p1 = new Person(10);
Person p2 = new Person(1);
Person p3 = new Person(3);
map.put(p1, "a");
map.put(p2,"b");
map.put(p3,"c");
for(Map.Entry<Person,String> entry:map.entrySet()) {
System.out.println(entry.getKey().getId());
}
九、Set
它主要用来存放不重复的数据,可以存放null值,但只能存放一个null值.set集合是无序的,主要实现类有HashSet,LinkedHashSet,TreeSet.可以利用Set元素唯一的特性快速对一个集合进行去重的操作.
1.HashSet
内部用一个hash map集合来存放数据,存放数据时利用对象的hashcode来确定是否存在重复.基本使用如下:
HashSet<String> set=new HashSet<String>();
set.add("a");
set.add("b");
set.add("b"); // 总数量为2
2.TreeSet
此集合是有序的.基本使用方法如下:
HashSet<String> set=new TreeSet<String>();
set.add("a");
set.add("b");
set.add("c"); // 总数量为2
十、Stack
栈是一种先进后出的数据结构,java集合中用Stack类实现栈这种数据结构,此类是Vector的子类,并添加了push,pop,peek等栈专有的方法.其基本使用方法如下:
Stack<String> stack = new Stack<String>();
stack.push("a");
stack.push("b");
stack.pop();
十一、Queue
队列是一种先进先出的数据结构,在集合框架中用Queue接口代表队列.常见的实现类有:
-
LinkedList
-
PriorityQueue
-
ArrayDeque()
LinkedList
LinkedList<String> ll = new LinkedList<>();
ll.addFirst("a");
ll.addFirst("b");
String value = ll.removeLast();// value为a
十二、总结
十三、Collections
此类是java.util包下的一个工具类,不能实例化,此类包含的都是一些静态的方法,用来操作集合或者返回集合.此类包含的方法主要有以下一些作用
-
排序
-
搜索
-
往集合填充,拷贝数据
-
求取集合的最大值最小值
-
获取集合的只读视图
-
获取线程安全的同步集合
-
获取空的集合
-
获取检查集合
下面的代码演示了Collections类的一些常见用法
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(4);
list.add(3);
list.add(1);
list.add(2);
list.add(3);
// 排序
Collections.sort(list);
// 二分查找的前提是排序好的元素
System.out.println( Collections.binarySearch( list , 8 ) ); // 找不到返回-1
// 反序集合输出
Collections.reverse( list );
System.out.println( list );
// 求最大值
System.out.println( Collections.max( list ) ); // 4
// 使用指定的元素替换指定集合中的所有元素
Collections.fill( list, 5 );
System.out.println( list );
十四、Arrays
此类是java.util包下的一个工具类,不能实例化,主要包含一些操作数组或者返回数据的工具方法.此类的方法主要有如下功能
-
排序
-
二分搜索
-
填充
-
拷贝
-
转换为流
-
字符串显示数组
-
转换为集合视图
下面的代码是一些常见的操作
// 将数组转换为集合视图
ArrayList<Integer> list = new ArrayList<Integer>();
Integer is[] = new Integer[]{6,7,8};
List<Integer> list2 = Arrays.asList(is);
list.addAll( list2 );
System.out.println( list );
// 将List转换为数组
Object [] ins = list.toArray();
System.out.println( Arrays.toString( ins ) );
十五、有序性与稳定性
在阿里巴巴的编码规范中有这么一句话:合理利用集合的稳定性(order)和有序性(sort),避免集合的无序性和不稳定性带来的负面影响。稳定性指集合每次遍历的元素次序是一定的。有序性是指遍历的结果按某种比较规则依次排序的。
如ArrayList是order/unsort,HashMap是unorder/unsort,TreeSet是order/sort
比如下面的代码演示了HashMap的不稳定:
Map<Integer,Integer> map = new HashMap<>();
map.put(1,11);
map.put(2,22);
for(Map.Entry<Integer, Integer> entry:map.entrySet()) {
System.out.println(entry.getKey());
}
System.out.println("------");
map.put(17,17);
for(Map.Entry<Integer, Integer> entry:map.entrySet()) {
System.out.println(entry.getKey());
}
输出的结果是:由于新插入一个值导致两次遍历的结果是不一样的.这就是不稳定性
1
2
------
1
17
2
十六、null的处理
集合类 | Key | Value | 父类 | 说明 |
---|---|---|---|---|
Hashtable | 不允许null | 不允许null | Dictionary | 线程安全 |
ConcurrentHashMap | 不允许null | 不允许null | AbstractMap | 锁分段 |
TreeMap | 不允许null | 允许 | AbstractMap | 线程不安全 |
HashMap | 允许 | 允许 | AbstractMap | 线程不安全 |
十七、异同点
1.ArrayList和LinkedList
(1)ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
(2)对于随机访问get和set,ArrayList绝对优于LinkedList,因为LinkedList要移动指针。
(3)对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。
2.HashTable与HashMap
相同点:
(1)都实现了Map、Cloneable、java.io.Serializable接口。
(2)都是存储"键值对(key-value)"的散列表,而且都是采用拉链法实现的。
不同点:
(1)历史原因:HashTable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现 。
(2)同步性:HashTable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的 。
(3)对null值的处理:HashMap的key、value都可为null,HashTable的key、value都不可为null 。
(4)基类不同:HashMap继承于AbstractMap,而Hashtable继承于Dictionary。
Dictionary是一个抽象类,它直接继承于Object类,没有实现任何接口。Dictionary类是JDK 1.0的引入的。虽然Dictionary也支持“添加key-value键值对”、“获取value”、“获取大小”等基本操作,但它的API函数比Map少;而且Dictionary一般是通过Enumeration(枚举类)去遍历,Map则是通过Iterator(迭代M器)去遍历。 然而由于Hashtable也实现了Map接口,所以,它即支持Enumeration遍历,也支持Iterator遍历。
AbstractMap是一个抽象类,它实现了Map接口的绝大部分API函数;为Map的具体实现类提供了极大的便利。它是JDK 1.2新增的类。
(5)支持的遍历种类不同:HashMap只支持Iterator(迭代器)遍历。而Hashtable支持Iterator(迭代器)和Enumeration(枚举器)两种方式遍历。
3.HashMap、Hashtable、LinkedHashMap和TreeMap比较
Hashmap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度。遍历时,取得数据的顺序是完全随机的。HashMap最多只允许一条记录的键为Null;允许多条记录的值为Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用Collections的synchronizedMap方法使HashMap具有同步的能力。
Hashtable 与 HashMap类似,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢。
LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列,像连接池中可以应用。LinkedHashMap实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。对于LinkedHashMap而言,它继承与HashMap、底层使用哈希表与双向链表来保存所有元素。其基本操作与父类HashMap相似,它通过重写父类相关的方法,来实现自己的链接列表特性。
TreeMap实现SortMap接口,内部实现是红黑树。能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。TreeMap不允许key的值为null。非同步的。
使用场景
一般情况下,我们用的最多的是HashMap,HashMap里面存入的键值对在取出的时候是随机的,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。在Map 中插入、删除和定位元素,HashMap 是最好的选择。
TreeMap取出来的是排序后的键值对。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。
LinkedHashMap 是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列,像连接池中可以应用。
4.HashSet、LinkedHashSet、TreeSet比较
Set接口
Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false。
Set判断两个对象相同不是使用==运算符,而是根据equals方法。也就是说,只要两个对象用equals方法比较返回true,Set就不会接受这两个对象。
HashSet
HashSet有以下特点:
-> 不能保证元素的排列顺序,顺序有可能发生变化。
-> 不是同步的。
-> 集合元素可以是null,但只能放入一个null。
当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值也相等。
注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。
LinkedHashSet
LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
TreeSet类
TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0。
5、Iterator和ListIterator区别
我们在使用List,Set的时候,为了实现对其数据的遍历,我们经常使用到了Iterator(迭代器)。使用迭代器,你不需要干涉其遍历的过程,只需要每次取出一个你想要的数据进行处理就可以了。但是在使用的时候也是有不同的。List和Set都有iterator()来取得其迭代器。对List来说,你也可以通过listIterator()取得其迭代器,两种迭代器在有些时候是不能通用的,Iterator和ListIterator主要区别在以下方面:
(1)ListIterator有add()方法,可以向List中添加对象,而Iterator不能
(2)ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
(3)ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
(4)都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。
因为ListIterator的这些功能,可以实现对LinkedList等List数据结构的操作。其实,数组对象也可以用迭代器来实现。
6、Collection 和 Collections区别
(1)java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
(2)java.util.Collections 是一个包装类(工具类/帮助类)。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,用于对集合中元素进行排序、搜索以及线程安全等各种操作,服务于Java的Collection框架。