文章目录
1.集合
java集合体系可分为Collection与Map两种体系
java集合超详解
Java集合中List,Set以及Map等集合体系详解(史上最全)
java集合(list,set,map)
1.1 继承关系图
1.2 介绍
Collection 接口是List、Set和Queue接口的父接口,该接口里定义的方法既可用于操作Set集合,也可用于操作List 和Queue集合
JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List)实现
Collection子接口之一:Set接口
HashSet LinkedHashSet TreeSet
Collection子接口之二:List接口
ArrayList LinkedList Vector
Map接口
HashMap TreeMap Hashtable
1.3 iterator
Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。
所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了lterator接口的对象。
Iterator仅用于遍历集合,terator本身并不提供承装对象的能力。如果需要创建terator对象,则必须有一个被迭代的集合。
Iterator:迭代器,它是Java集合的顶层接口(不包括 map 系列的集合,Map接口 是 map 系列集合的顶层接口)
除了 map 系列的集合,我们都能通过迭代器来对集合中的元素进行遍历
Iterable :存在于 java.lang 包中。
里面封装了 Iterator 接口。所以只要实现了只要实现了Iterable接口的类,就可以使用Iterator迭代器了。
2.List(有序、可重复)
有序:List里存放的对象插入顺序和遍历出来顺序是一致的
重复:相同的对象可以在List里重复添加
往List集合里插入或删除数据时,会伴随着后面数据的移动
2.1 ArrayList
ArrayList是基于数组的,在初始化ArrayList时,会构建空数组(Object[] elementData={})。ArrayList是一个无序的,它是按照添加的先后顺序排列,当然,他也提供了sort方法,如果需要对ArrayList进行排序,只需要调用这个方法,提供Comparator比较器即可
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程不安全,效率高
注意:contains,indexOf,remove等方法涉及比较的时候,若是自定义对象,则一定要重新实现equals()和hashcode()两个方法,不然无法比较集合中对象是否相同
public class User {
private Integer uid;
private String uname;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(uid, user.uid) &&
Objects.equals(uname, user.uname);
}
@Override
public int hashCode() {
return Objects.hash(uid, uname);
}
增
// 创建一个空的数组链表对象list,list用来存放String类型的数据
ArrayList<User> list = new ArrayList<User>();
// 增加元素到list对象中
list.add(new User(1,"Item1"));
list.add(new User(4,"Item4"));
list.add(new User(4,"Item4"));
list.add(new User(2,"Item2"));
list.add(2, new User(3,"Item3")); // 此条语句将会把“Item3”字符串增加到list的第3个位置。
list.add(new User(4,"Item4"));
查
// 检查元素的位置###
int pos = list.indexOf(new User(2,"Item2"));
System.out.println("User(2,Item2) indexOf: " + pos);
// 检查数组链表是否为空
boolean check = list.isEmpty();
System.out.println("isEmpty: " + check);
// 获取链表的大小
int size = list.size();
System.out.println("size: " + size);
// 检查数组链表中是否包含某元素###
boolean element = list.contains(new User(3,"Item3"));
System.out.println("User(3,Item3) contains: " + element);
// 获取指定位置上的元素
User item = list.get(0);
System.out.println("get(0): " + item);
//遍历arraylist中的元素
public void forList(List<User> list,Integer type){
switch (type){
case 1 :
// 第1种方法: 循环使用元素的索引和链表的大小
System.out.println("fori:");
for (int i = 0; i < list.size(); i++) {
System.out.println("Index: " + i + " - Item: " + list.get(i));
}
break;
case 2 :
// 第2种方法:使用foreach循环
System.out.println("foreach:");
for (User str : list) {
System.out.println("Item is: " + str);
}
break;
case 3 :
// 第3种方法:使用迭代器
// hasNext(): 返回true表示链表链表中还有元素
// next(): 返回下一个元素
System.out.println("iterator:");
for (Iterator<User> it = list.iterator(); it.hasNext();) {
System.out.println("Item is: " + it.next());
}
break;
default:
System.out.println("无效参数");
}
}
改
// 替换元素
forList(list,1);
System.out.println("set(1, new User(22,Item22): ");
list.set(1, new User(22,"Item22"));
forList(list,1);
删
// 移除元素
//移除第2个位置上的元素
forList(list,2);
System.out.println("remove(1): ");
list.remove(1);
forList(list,2);
// 移除第一次找到的 "Item3"元素###
forList(list,3);
System.out.println("remove(User(4,Item4)): ");
list.remove(new User(4,"Item4"));
forList(list,3);
转数组
// 转换 ArrayList 为 Array
User[] simpleArray = list.toArray(new User[list.size()]);
System.out.println("The array created after the conversion of our arraylist is: "
+ Arrays.toString(simpleArray));
2.1 LinkedList
LinkedList是基于链表的,它是一个双向链表,每个节点维护了一个prev和next指针
同时对于这个链表,维护了first和last指针,first指向第一个元素,last指向最后一个元素
LinkedList是一个无序的链表,按照插入的先后顺序排序,不提供sort方法对内部元素排序
优点: 底层数据结构是链表,查询慢,增删快。
缺点: 线程不安全,效率高
用法同ArrayList,只不过把Arraylist< User>换成LinkedList< User>
LinkedList常用方法
// 创建一个空的数组链表对象list,list用来存放String类型的数据
LinkedList<User> list = new LinkedList<User>();
// 增加元素到list对象中
list.add(new User(1,"Item1"));
list.add(new User(4,"Item4"));
list.add(new User(4,"Item4"));
list.add(new User(2,"Item2"));
list.add(2, new User(3,"Item3")); // 此条语句将会把“Item3”字符串增加到list的第3个位置。
list.add(new User(4,"Item4"));
// 显示数组链表中的内容
System.out.println("The arraylist contains the following elements: ");
for (User user : list) {
System.out.println("user = " + user);
}
// 检查元素的位置###
int pos = list.indexOf(new User(2,"Item2"));
System.out.println("User(2,Item2) indexOf: " + pos);
// 检查数组链表是否为空
boolean check = list.isEmpty();
System.out.println("isEmpty: " + check);
// 获取链表的大小
int size = list.size();
System.out.println("size: " + size);
// 检查数组链表中是否包含某元素###
boolean element = list.contains(new User(3,"Item3"));
System.out.println("User(3,Item3) contains: " + element);
// 获取指定位置上的元素
User item = list.get(0);
System.out.println("get(0): " + item);
// 替换元素
forList(list,1);
System.out.println("set(1, new User(22,Item22): ");
list.set(1, new User(22,"Item22"));
forList(list,1);
// 移除元素
//移除第2个位置上的元素
forList(list,2);
System.out.println("remove(1): ");
list.remove(1);
forList(list,2);
// 移除第一次找到的 "Item3"元素###
forList(list,3);
System.out.println("remove(User(4,Item4)): ");
list.remove(new User(4,"Item4"));
forList(list,3);
// 转换 ArrayList 为 Array
User[] simpleArray = list.toArray(new User[list.size()]);
System.out.println("The array created after the conversion of our arraylist is: "
+ Arrays.toString(simpleArray));
3.Set(无序,唯一)
无序:存储和取出顺序不一致
唯一:元素不可重复
不能通过索引获取元素
3.1 HashSet
HashSet底层数据结构采用哈希表实现,元素无序且唯一,线程不安全,效率高,可以存储null元素
元素的唯一性是靠所存储元素类型是否重写hashCode()和equals()方法来保证的,如果没有重写这两个方法,则无法保证元素的唯一性
HashSet是基于HashMap来实现的,操作很简单,更像是对HashMap做了一次“封装”,而且只使用了HashMap的key来实现各种特性,而HashMap的value始终都是PRESENT
HashSet不允许重复(HashMap的key不允许重复,如果出现重复就覆盖),允许null值,非线程安全
覆盖hashCode()方法的原则:
1、一定要让那些我们认为相同的对象返回相同的hashCode值
2、尽量让那些我们认为不同的对象返回不同的hashCode值,否则,就会增加冲突的概率。
3、尽量的让hashCode值散列开(两值用异或运算可使结果的范围更广)
HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成,我们应该为保存到HashSet中的对象覆盖hashCode()和equals()
因为将对象加入到HashSet中时,会首先调用hashCode方法计算出对象的hash值,接着根据此hash值调用HashMap中的hash方法,得到的值& (length-1)得到该对象在hashMap的transient Entry[] table中的保存位置的索引,接着找到数组中该索引位置保存的对象,并调用equals方法比较这两个对象是否相等,如果相等则不添加
注意:所以要存入HashSet的集合对象中的自定义类必须覆盖hashCode(),equals()两个方法,才能保证集合中元素不重复。在覆盖equals()和hashCode()方法时, 要使相同对象的hashCode()方法返回相同值,覆盖equals()方法再判断其内容。为了保证效率,所以在覆盖hashCode()方法时, 也要尽量使不同对象尽量返回不同的Hash码值。
集合转数组的toArray()和toArray(T[] a)方法
//新增
Set<User> set = new HashSet<>();
set.add(new User(1,"Item1"));
set.add(new User(1,"Item1"));
set.add(null);
set.add(null);
set.add(new User(2,"Item2"));
set.add(new User(3,"Item3"));
forSet(set);
System.out.println("set elements:");
Set<User> set2 = new HashSet<>();
set2.add(new User(3,"Item3"));
set2.add(new User(4,"Item4"));
//把set2的元素加载到set里
set.addAll(set2);
System.out.println("addAll(set2):");
forSet(set);
System.out.println("set2 elements: ");
forSet(set2);
//是否为空
System.out.println("set.isEmpty(): " + set.isEmpty());
//元素大小
System.out.println("set.size(): " + set.size());
//集合中是否含有
System.out.println("set.contains(new User(4,Item4)): " + set.contains(new User(4,"Item4")));
//删除
set.remove(new User(3,"Item3"));
System.out.println("remove(new User(3,Item3)):");
forSet(set);
//转数组
User[] users = set.toArray(new User[set.size()]);
System.out.println("set.toArray: " + Arrays.toString(users));
//清空
set.clear();
System.out.println("clear:");
System.out.println("set.size(): " + set.size());
forSet(set);
3.2 LinkedHashSet
底层采用 链表 和 哈希表的算法。链表保证元素的添加顺序,哈希表保证元素的唯一性
线程不安全,效率高
LinkedHashSet 是Hashset的子类
LinkedHashSet根据元素的hashCode值来决定元素的存储位置,但它同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的
LinkedHashSet插入性能略低于Hashset,但在迭代访问Set里的全部元素时有很好的性能
不允许集合元素重复
3.3 TreeSet
底层数据结构是红黑树。(唯一,有序)
1.如何保证元素排序的呢?
自然排序
比较器排序
2.如何保证元素唯一性的呢?
根据比较的返回值是否是0来决定
3.因为涉及比较,所以元素不允许有Null
3.3.1 自然排序
若集合元素是自定义的类,那么这个类一定要实现Comparable,不然TreeSet会报错
public class User implements Comparable{
private Integer uid;
private String uname;
@Override
public int compareTo(Object o) {
return this.uid.compareTo(((User)o).uid);
}
//新增
Set<User> set = new TreeSet<>();
set.add(new User(8,"Item8"));
set.add(new User(1,"Item1"));
set.add(new User(6,"Item6"));
set.add(new User(3,"Item3"));
forSet(set);
System.out.println("set elements:");
Set<User> set2 = new HashSet<>();
set2.add(new User(3,"Item3"));
set2.add(new User(4,"Item4"));
//把set2的元素加载到set里
set.addAll(set2);
System.out.println("addAll(set2):");
forSet(set);
System.out.println("set2 elements: ");
forSet(set2);
//是否为空
System.out.println("set.isEmpty(): " + set.isEmpty());
//元素大小
System.out.println("set.size(): " + set.size());
//集合中是否含有
System.out.println("set.contains(new User(4,Item4)): " + set.contains(new User(4,"Item4")));
//删除
set.remove(new User(3,"Item3"));
System.out.println("remove(new User(3,Item3)):");
forSet(set);
//转数组
User[] users = set.toArray(new User[set.size()]);
System.out.println("set.toArray: " + Arrays.toString(users));
//清空
set.clear();
System.out.println("clear:");
System.out.println("set.size(): " + set.size());
forSet(set);
3.3.2 定制排序
可以定义一个实现Comparator接口的类,然后在定义TreeSet的时候指定这个类
public class User{
private Integer uid;
private String uname;
//新增
Set<User> set = new TreeSet<>(new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o1.getUid().compareTo(o2.getUid());
}
});
set.add(new User(8,"Item8"));
set.add(new User(1,"Item1"));
set.add(new User(6,"Item6"));
set.add(new User(3,"Item3"));
forSet(set);
4.Map(键值对、键唯一、值不唯一)
Map集合中存储的是键值对,键不能重复,值可以重复。根据键得到值,对map集合遍历时先得到键的set集合,对set集合进行遍历,得到相应的值。
严格来说 Map 并不是一个集合,而是两个集合之间 的映射关系
Map接口有三个比较重要的实现类,分别是HashMap、TreeMap和HashTable
- TreeMap是有序的,HashMap和HashTable是无序的。
- Hashtable的方法是同步的,HashMap的方法不是同步的。这是两者最主要的区别。
这就意味着:
- Hashtable是线程安全的,HashMap不是线程安全的。
- HashMap效率较高,Hashtable效率较低。
- 如果对同步性或与遗留代码的兼容性没有任何要求,建议使用HashMap。 查看Hashtable的源代码就可以发现,除构造函数外,Hashtable的所有 public 方法声明中都有 synchronized关键字,而HashMap的源码中则没有。
- Hashtable不允许null值,HashMap允许null值(key和value都允许)
- 父类不同:Hashtable的父类是Dictionary,HashMap的父类是AbstractMap
4.1 HashMap
数组方式存储key/value,线程非安全,允许null作为key和value,key不可以重复,value允许重复,不保证元素迭代顺序是按照插入时的顺序,key的hash值是先计算key的hashcode值,然后再进行计算,每次容量扩容会重新计算所以key的hash值,会消耗资源,要求key必须重写equals和hashcode方法
当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
我们可以看到在HashMap中要找到某个元素,需要根据key的hash值来求得对应数组中的位置。如何计算这个位置就是hash算法。前面说过HashMap的数据结构是数组和链表的结合,所以我们当然希望这个HashMap里面的 元素位置尽量的分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,而不用再去遍历链表,这样就大大优化了查询的效率。
来源: http://blog.csdn.net/fg2006/article/details/6400319
根据上面 put 方法的源代码可以看出,当程序试图将一个key-value对放入HashMap中时,程序首先根据该 key的 hashCode() 返回值决定该 Entry 的存储位置:如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry的 value,但key不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部——具体说明继续看 addEntry() 方法的说明。
从上面的源代码中可以看出:从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。
归纳起来简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry,若equals返回false,表示这个不是想找的对象,继续next,若equals为true,那么就返回该对象。
public void testMap(){
HashMap<Integer, User> map = new HashMap<>();
//新增
map.put(1,new User(1,"Iterm1"));
map.put(2,new User(2,"Iterm2"));
map.put(3,new User(3,"Iterm3"));
map.put(4,new User(4,"Iterm4"));
map.put(5,new User(5,"Iterm5"));
System.out.println("map: ");
forMap(map.entrySet());
//新增
HashMap<Integer, User> map2 = new HashMap<>();
map2.put(3,new User(3,"Iterm3"));
map2.put(88,new User(88,"Iterm88"));
map.putAll(map2);
System.out.println("map.putAll(map2): ");
forMap(map.entrySet());
//获取
User user = map.get(2);
System.out.println("map.get(2): " + user);
//是否为空
System.out.println("map.isEmpty(): "+ map.isEmpty());
//元素长度
System.out.println("map.size(): "+ map.size());
//获取key集合
Set<Integer> keySet = map.keySet();
for (Integer integer : keySet) {
System.out.println("map.keySet: " + integer);
}
//获取value集合
Collection<User> values = map.values();
for (User value : values) {
System.out.println("map.values: " + value);
}
//获取entry集合
Set<Map.Entry<Integer, User>> entries = map.entrySet();
for (Map.Entry<Integer, User> entry : entries) {
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
//是否存在key
System.out.println("map.containsKey(2): " + map.containsKey(2));
//是否存在value
System.out.println("map.containsValue(new User(3,Iterm3)): "+ map.containsValue(new User(3,"Iterm3")));
//删除
map.remove(3);
System.out.println("remove(3): ");
forMap(map.entrySet());
//清空
System.out.println("clear(): ");
map.clear();
System.out.println("map.size(): " +map.size());
}
private void forMap(Set<Map.Entry<Integer, User>> entries2) {
for (Map.Entry<Integer, User> entry : entries2) {
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
}