Java集合类
两耳不闻窗外事,一心只读圣贤书。
1.概述
Java集合类主要是Collection接口下的单列集合和Map接口下的K-V键值对类型的集合。
2.Collection
2.1 Collection 中的常用方法
contains(Object o)
contains方法的作用是查询当前集合中是否存在所传入的值。
查询的方式是查询传入对象的hashcode。
举例:
Collection coll = new ArrayList();
// 对比测试实例化一个对象的hashCode
Person per1 = new Person("Tom");
Person per2 = new Person("Tom");
System.out.println(per1.hashCode());
System.out.println(per2.hashCode());
// 对比测试一个字符串对象的hashCode
String jack1 = new String("Jack");
String jack2 = new String("Jack");
System.out.println(jack1.hashCode());
System.out.println(jack2.hashCode());
coll.add(new Person("Tom"));
coll.add(new String("Jack"));
// 返回结果为false,hashCode不同。
System.out.println(coll.contains(new Person("Tom")));
// 这里返回的结果是True,说明contains方法查询的是hashCode值。
System.out.println(coll.contains(new String("Jack")));
结论:String类已经重写了equals方法,因此最后的hashCode是相同的。(重写的比较的是值来定义hashcode)
但是普通的类用的Object默认的equals,直接比较的内存地址,因此对于用户自定义的类,使用contains方法的结果是false。
本质是调用传入对象的equals方法来比较查询。
如果需要调用contains方法时,传入的对象需要重写equals方法。
containsAll(Collection coll)
传入的集合中的数据是否都存在需要查询的集合中。
注意:只有全部都在才会返回true。
remove(Object o)
通过equals来进行判断是否存在元素,如果存在就进行删除,删除成功返回true。
删除失败返回false。
removeAll(Collection coll)
从当前集合中移除传入的集合中的元素。
移除的是两个集合的交集。
retainAll(Collection coll)
获取两个集合的交集,并修改当前的集合。
使当前集合中只存在两个集合共同存在的。
equals(Object o)
比较两个集合是否相同。
注意:ArrayList是有序的,因此比较时顺序会影响结果,如果顺序不同也会返回false。
hashCode()
返回hashCode值,如果没有重写就是返回内存地址。
如果重写了hashCode方法,则返回的结果自定义。
toArray()
返回相应类型的数组。
如果没有使用泛型,那么默认是Object[]型数组。
补充:如果将数组转换为集合?
通过Arrays.asList([对象数组]),来进行数组到集合的转换。
注意:
如果传入的是基本数据类型的数组(int、boolean等),那么会将传入的整个数组当成一个对象进行处理,只有转换为相应的包装类对象才能达到想要的结果。
List<int[]> ints = Arrays.asList(new int[]{1, 2, 3});
// 虽然我们上面定义的数组含有多个元素,但是因为是基本数据类型,list中需要存放的是
// 对象数据类型,那么就会将整个数组当成一个对象进行处理,最后输出的结果:长度为1。
System.out.println(ints.size());
// 解决办法:将基本数据类型的数组转换为包装类对象进行传入 ==》 new Integer[]
// 通过包装类对象以后就能正常将传入的数组转换为list
List<Integer> integers = Arrays.asList(new Integer[]{1, 2, 3});
System.out.println(integers.size());
iterator()
返回一个迭代器实例。
每次调用方法都会返回新的一个迭代器实例对象。
迭代器实例就是用于帮助我们遍历集合。
在Java设计模式中有详细的介绍。
注意:
Map中不是通过迭代器进行遍历,迭代器遍历主要引用与Collection中的集合。
-
hasNext()
判断是否含有写一个
-
next()
先指针下移,然后再输出元素
-
remove()
移除当前遍历到的元素,先next以后才能remove,如果没有next直接remove那么会爆出:IllegalStateException
原因:next开始是-1,那么无法删除,还有一种情况也会爆出这个异常,next以后进行了两次remove。
同理,remove是删除当前元素,删除以后指针并没有发生改变,如果在remove就会爆出异常。
集合的遍历除了迭代器以外,还有一种是forEach,这也是比较常用的一种方式。
语法:for([Object类型] [集合中具体的元素]:[集合]){}
本身也是通过迭代器实现。
不能通过修改遍历出的值,来修改集合对象中的值。
原因:
本身foreach是通过将集合中的值取出然后赋值到一个对象上,那么如果我们通过foreach来修改遍历出的值,是对本身集合对象是没有任何影响的。
forEach(Consumer action)
Collection类中默认是可以通过这个方法进行forEach遍历循环。
coll.forEach(System.out ::println);
2.2 List
Collection中的一个子类接口。
存储有序的、可重复的数据。
2.2.1 概述
- ArrayList
- LinkedList
- Vector
2.2.2 ArrayList、LinkedList、Vector的异同
相同点
- 三个类都实现了List接口
- 存储的数据都是有序、可重复的
不同点
-
ArrayList是作为List主要实现类(首选)
线程不安全,效率高
底层是通过Objec[]数组存储,动态扩展
-
Vector是List接口的古老实现类
推出Java就已经存在的一个类(V 1.0)出现,List(V 1.2)才出现。
线程安全,效率低
底层是通过Objec[]数组存储,动态扩展
-
LinkedList通过链表实现和另外两种不同实现方式不同
双向链表实现
线程不安全
对于增加/删除效率比数组实现的List类效率更高,同理查找没有数组实现的List类高
2.3 List中的实现类源码分析
2.3.1 ArrayList
构造方法,无参构造默认大小为10,但是不会再实例化对象时创建数组,而是创建一个空数组,当进行第一次执行add()方法时才会进行创建。
扩容为1.5倍加1。
-
Object[] elementData实现,动态扩容
transient Object[] elementData;
-
new实例对象时,可以指定大小,如果没有指定,那么默认创建大小为10
/** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
常用方法
下面演示add()方法。
其他方法类似在进行操作之前都会进行一些数组上面的判断。
add()
先判断,再添加。数组长度不够,扩容,每次扩容原先数组的1.5倍。
不是无止境的扩容,最大值为int类型最大值。
结论:建议在创建ArrayList时指定大小。
源码
public boolean add(E e) {
// 添加元素前会进行容量判断如果容量够进行添加
// 如果不够会进行扩容
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
// 扩容判断
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 判断当加入这个元素以后是否会超过数组的长度
if (minCapacity - elementData.length > 0)
// 如果加入元素超过了数组的长度,进行扩容
grow(minCapacity);
}
// 进行扩容
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 位运算,右移一位,缩小2倍,加上本身 ==》 扩容1.5倍 + 1
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 老数组到新数组的复制
elementData = Arrays.copyOf(elementData, newCapacity);
}
2.3.2 LinkedList
双向链表实现。
默认add()方法添加到链表尾部。
类中维护一个头结点和尾结点。
Node源码
Node中添加的Object通过泛型指定。
private static class Node<E> {
E item;
// 双向指针
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
2.3.3 Vector
Java8中默认实例时,就已经创建。
扩容时,扩大的倍数为原先的2倍。
一般不用,如果线程不安全也是通过Collection中的方法实现线程安全。
2.4 List中的常用方法
add(int index, Oject o)
将对象插入到下标为index的上面。
addAll(Collection coll)
将集合中的所有数据依次加入到目标集合中。
get(int index)
获取指定下标元素。
indexOf(Object o)
对象在数组中第一次出现的下标。
如果没有在集合中查找到,那么返回-1。
lastIndexOf(Object o)
对象最后出现的位置。
不存在,返回-1。
remove(int index/Object o)
根据下标或对象来删除。
返回删除的对象。
set(int index,Object o)
将指定下标上的值修改传入的对象。
subList(int fromIndex,int toIndex)
返回一个List。
区间为左闭右开。
对本身List不会造成影响。
2.5 Set
Set:无序、不可重复。
HashSet、LinkedHashSet、TreeSet
Set接口本身没有额外定义新的方法,使用的都是Collection中声明的方法。
HashSet、LinkedHashSet底层是数组,默认大小为16,加载因子为0.75,每次扩容2倍
加载因子:当数组现有元素的大小超过了容量的加载因子大小(size > 容量 * 加载因子)以后会进行扩容。
-
HashSet:作为set接口的主要实现类,线程不安全,可以存储null值(首选),底层实现HashTable(数组+链表)
-
LinkedHashSet:是HashSet的子类,添加了指向使在遍历时按照添加的顺序输出。(伪有序,本身不是有序的)
-
TreeSet:使用红黑树(二叉树的一种)存储结点。可以按照结点中的某些属性进行排序。
2.5.1 对于无序、不可重复的理解
以HashSet说明。
无序性
- 不等于随机性,每次遍历出的结果都相同
- 数据在底层的存储顺序不是按照数组索引添加,而是无序添加,而是根据数据的hashCode进行排序添加
不可重复性
-
调用的是集合中对象的equals方法进行判断是否在集合中重复
需要重写equals和hashCode方法,不然添加对象直接判断内存地址,无法进行重复性的判断。(有些类本身就已经重写了这两个方法,比如:String等)
2.5.2 添加元素到Set的过程
以HashSet说明。
- 计算到需要添加到集合对象的hashCode
- 根据计算的hashCode确定这个元素在数组的位置(某种算法),位置为空,直接添加
- 如果存放的位置发现这个数组位置已经有元素了(只能说明存放的位置相同,不能说明hashCode值相同)
- 先比较hashCode,如果相同,那么再进行equals比较
- 调用新添加数据的equals方法,如果返回是true,那么添加失败;如果返回false,添加成功,形成链表
- 比较的hashCode不同,那么就会和当前数组位置上已有的数据形成一个链表
- 先比较hashCode,如果相同,那么再进行equals比较
通过上面的源码分析,也可以知道Set的无序性,本身添加不是按照顺序添加下去,而是通过某种算法进行添加元素到集合中。
只有hashCode和equals方法都相同才会添加失败。
补充:hashCode和equals方法的重写
为什么重写hashCode方法会把31当做系数?
-
选择系数要尽可能的大,系数越大计算得到hashCode越大,那么可能发生冲突的可能性越小(减少冲突)
-
31只占用4bits,相乘造成数据溢出的概念较小
-
31可以用2的5次方-1,现在很多虚拟机都有相关的优化(提高算法效率)
-
31是一个素数(减少冲突)
现在的编译器一般都有相关的重写,直接调用即可。
结论:
推荐重写hashCode和equals方法,对象中的所有属性都应该出现在hashCode的计算中。
2.5.3 LinkedHashSet
遍历是顺序的,但是本身存储还是无序的,通过链表实现有序遍历。
在HashSet的基础上添加了链表结构,通过LinkedHashMap实现。
优点:如果需要频繁的遍历,效率更高
2.5.4 TreeSet
TreeMap实现。添加的元素必须是相同类的对象。
可以根据对象的指定属性进行排序。
默认为从小到打进行排序。
如果添加到TreeSet中的是自定义对象,一定要有排序方法。
结论:TreeSet中的保存的对象一定是相同类,并且这个类必须要有排序方法(实现相关接口 – Comparable,重写相关方法)
注意:TreeSet中比较添加元素是否重复不是通过hashCode和equals方法,而是通过compareTo方法。如果compareTo返回的结果为0,那么添加失败。
优点:查询速度比List更快。(红黑数)
定制排序
可以在创建TreeSet时,可以添加排序规则。
Comparator com = new Comparator(){
[重写排序规则]
}
TreeSet treeSet = new TreeSet([排序规则])
2.6 总结
- 使用Collection类保存的对象需要重写equals方法,推荐hashCode方法一同重写(直接使用编译器的相关方法即可)
- Collection的很多方法都是调用集合中对象的equals和hashCode方法进行判断
- List类添加元素会进行数组大小判断(动态扩容,LinkedList通过链表实现)
- Set元素的添加需要判断是否重复,判断重复不需要遍历整个集合,通过相关算法实现元素的添加(hashCode和equals方法共同实现)
- HashSet、LinkedHashSet底层通过hashTable实现(HashMap),LinkedHashSet在HashSet的基础上添加链表实现有序的遍历,但本身都是无序的存储
- TreeSet底层通过数组+红黑树实现(TreeMap),保存的元素要求必须是相同类,添加元素的不可重复性通过添加对象的compareTo方法比较是否重复,如果添加的元素没有实现相关方法,也可以在创建TreeSet时传入一个比较规则
- TreeSet如果添加元素比较的结果为0则添加失败,其他Set的实现类,只有hashCode和equals完全相同才会添加失败
- 遍历的方法,获取到相关迭代器或者使用增强FOR循环(foreach)
- TreeSet的遍历按照排序规则输出
- HashSet底层是HashMap,但是不能设定Key值,只有value,是因为,所有HashSet的Key都是固定相同的。是有的,只是被固定了。通过后面的分析,也可以知道为什么Set不能重复,因为在Map中Key本身就是不可重复的。
3.Map
双列数据,存储K-V键值对数据。
3.1 概述
-
HashMap:主要的实现类,线程不安全,效率高;可以存储null的K/V值
HashMap的线程安全使用CurrentHashMap
- LinkedHashMap:保证在遍历时,是有序的,在原有的HashMap基础上添加了一对指针(前、后),频繁的遍历操作,使用它
-
TreeMap(V 1.0):古老的实现类,比Map(V 1.2)还早,可以保证按照添加的K-V对进行排序,实现排序遍历(按照Key的规则排序);使用红黑树实现
-
Hashtable(注意:API中的t是小写):线程安全,效率低;不能存储null的K/V值
- Properties:常用来保存配置文件;K和V必须为String类型
HashMap、Hashtable在JDK1.7之前为数组加链表,在JDK1.8以后的实现为数组加红黑树。
3.2 对K-V键值对结构的理解
-
Key不能重复且无序(理解为通过Set存储)
- 对于HashMap中Key所在的类需要重写hashCode和equals方法,同理TreeMap根据排序规则存储
-
Value可重复且无序(因为Key是无序的)
-
Value需要重写equals方法
Value更多是用在查询中,所以只用重写equals方法,但是Key多用于在存储中,为了提高效率需要重写hashCode和equals方法。
-
-
K-V通过Entry包装存储(Map是包装为Node结点)
-
Entry不可重复且无序(Key的特点)
3.3 HashMap
JDK1.8版本。
####3.3.1 概述
- 实例化以后,底层不会创建数组,会在第一次添加以后才会创建数组 – Node[] node
- 存入数据 – map.put(key1,value1);
- 首先调用value所在类的hashCode方法,得到hashCode值,通过某种算法(与或运算)得到在数组中的位置
- 如果数组上没有数据,直接添加成功
- 如果数组上有数据(一个或多个),多个数据以链表的形式存在
- 和已经存在的数据进行hashCode比较
- 如果和数据都不相同(hashCode值),那么直接添加成功
- 如果hashCode值和已经存在的数据的某一个hashCode相同,继续比较equals方法(调用传入对象的equals)
- 如果equals方法返回false,则对象添加到链表上
- 如果是true,则用传入的数据替换已经存在的(修改)
- 在添加数据时的扩容,当触发到加载(负载)因子且要添加的位置非空,进行扩容,默认扩容为原来的2倍
- 加载因子为0,75
- 最大支持容量:2的30次方
JDK1.8中结构为:数组 + 链表 + 红黑树。
当数组上的某一个索引上的链表存在的数据个数,大于8且当前数组的长度大于64,此时此索引位置上的所有数据改为红黑树存储。
红黑树优点:遍历块,方便查找。
3.3.2 源码分析
1.封装结点 – Node<K,V>
所有需要存储在HashMap中的数据都是将传入的Key-Value封装到结点中,然后HashMap保存这些结点。
- 通过静态内部类在HashMap中实现
- 重写了hashCode和equals方法
- 结点本身具有下一个指向,链表结构
- 可以修改Key上的Value值,但是不能修改Key的值 – setValue(V newValue)
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
2.构造器
- 无参构造器,创建时不会创建数组,使用HashMap中定义的默认负载因子(0.75f)
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
- 有参构造器有两种,一种直接传递初始HashMap大小,另外一种除了可以设置大小以外还可以设置负载因子
- 我们可以指定初始大小,但是HashMap中的大小一定是2的次方,所以可能我们设置的大小不是最终的大小(比如:初始值为15,就不会创建大小为15的HashMap,而是16)
- 有参构造1,实际也是调用的有参构造2
// 有参构造1
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
// 有参构造2
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
// 真实大小设置
this.threshold = tableSizeFor(initialCapacity);
}
// 有参构造3 -- 传入一个Map
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
// 3.1 调用的putMapEntries(m, false)方法
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) { // pre-size
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
threshold = tableSizeFor(t);
}
else if (s > threshold)
resize();
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
3.添加元素 – put(K key, V value)
在调用put方法时,就会计算出key的hash值。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
实际实现方式 – putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
- Node[],HashMap的底层实现之一,管理着一个Node数组
- 当容量为null或者长度为0时,就会对HashMap进行创建一个容量为16的数组,这就是第一次添加元素时,HashMap才会创建的数组的原因 – resize()
- i = (n - 1) & hash,计算添加元素在HashMap中的位置
- 链表满足一定条件以后,变为红黑树 – treeifyBin(tab, hash)
- 添加完以后,还会进行一次HashMap大小判断
- 添加结点的过程,看前面概述
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 首次添加才会进行HashMap的创建
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 如果当前数组上的位置为空,直接添加
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
// 不为空进行判断
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 红黑树的判断,链表变为红黑树,添加结点
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 和链表上的所有数据进行比较hashCode
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 链表变为红黑树的判断,
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 修改老的值 -- hashCode值相同,并且equals方法返回为真,满足这两个条件,认为就是完全相同的,修改老数据的值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
链表变为红黑树 – treeifyBin(Node<K,V>[] tab, int hash)
- 一条链表上的结点数超过8,在添加新的元素时,进入这个方法,但不会直接变为红黑树,进行下一步的判断
- 如果当前HashMap的容量小于64,不会变红黑树,而是使HashMap进行扩容
- 如果当前链表上的结点超过8且HashMap的容量大于64,变为红黑树
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
补充
关于负载因子的大小问题?
为什么负载因子为0.75?通过上面的分析,我们可以看到HashMap有扩容的问题,而扩容的临界值就与负载因子有关。
如果负载因子过小,就会频繁的进行扩容,就是会导致数组中的利用率不高。
如果负载因子过大,扩容会减少,但数组上的每一条链表的长度就会过长,不利于查找,所以HashMap默认为0.75,是一个比较好的结果。
3.4 LInkedHashMap
HashMap的一个子类,可以实现遍历结果为添加顺序。
大部分方法(如:put())都是使用的父类 – HashMap中的方法,但是重写了newNode方法,给添加的Node中添加了两个指向,因此可以在遍历的时候按照添加顺序输出。
优点:频繁遍历,效率更高。
static class Entry<K,V> extends HashMap.Node<K,V> {
// 添加两个指向,达到顺序输出的结果
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
3.5 TreeMap
和TreeSet相同原理。但是通过K-V键值对添加,通过Key指定规则进行排序。
3.6 Properties
Hashtable的一个子类。Key和Value被固定为String类型,通常用于配置文件。
Properties pro = new Properties();
// 创建IO流读取指定文件
FileInputStream is = new FileInputStream("xxx.properties");
// 加载IO流
pro.load(is);
// 获取配置文件中的value
String key = pro.getProperty("key");
3.7 Map中的常用方法
put(Object key,Object value)
将指定Key-Value添加到当前map中
相同的Key添加时,变为修改
putAll(Map map)
将指定Map添加到Map中
remove(Object key)
删除指定Key-Value,返回值为value,不存在返回null
clear()
清空map中的数据,并不是将 map = null。大小还存在,只是没有数据了。
get(Object key)
返回指定Key的Value,不存在返回null
containsKey(Object key) / containsValue(Object value)
查询是否包含指定的Key/Value,不存在返回null
因为Value是可以重复的,找到第一个指定的返回true不能继续往下寻找
isEmpty()
空,返回true;不为空,返回false。
equals(Object o)
Object一定也需要是一个Map,判断是否相同。
keySet()
返回一个所有Key构成的Set集合
values()
返回一个所有Value构成的Collection
遍历的顺序是按照Key的遍历顺序
entrySet()
返回一个所有entry构成的Set
通过得到的Entry,可以调用每一个Entry中的getKey和getValue方法,得到需要的数值
遍历
1.通过for循环遍历,但是一定要通过泛型指明Key-Value的数据类型,不然无法遍历。
2.通过entrySet获得到的Set集合,然后获得集合的迭代器进行遍历。
HashMap<Integer,Integer> map = new HashMap();
map.put(1,1);
map.put(2,2);
map.put(3,3);
// 方式一
for (Map.Entry<Integer,Integer> entry : map.entrySet()){
System.out.println(entry.getKey() + entry.getValue());
}
// 方式二
Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
Iterator<Map.Entry<Integer, Integer>> iterator = entries.iterator();
while (iterator.hasNext()){
Map.Entry<Integer, Integer> next = iterator.next();
System.out.println(next.getKey() + next.getValue());
}
推荐使用第一种
4.Collections
Collections是一个操作Set、List和Map等集合的工具类。
4.1 常用的方法
reverse(List list)
反转有序的List(list本身就是有序的),将本身的List给修改。
shuffle(List list)
对List集合中的数据进行随机排序。
sort(List list,[定制排序])
如果没有传入排序规则就根据存储数据类型的compare进行排序(存储数据需要实现相关接口),如果传入排序规则,则按照排序规则进行排序。
swap(List list,int i,int j)
将指定下标的元素进行交换。
max(Collection coll)/min(Collection coll,[指定排序])
返回最大传入集合中的最大/最小元素。
copy(List dest,List src)
将src中的内容复制到dest中。
src数组大于dest的长度,复制失败,报出异常。
解决方式:List dest = Arrays.asList(new Object[src.size()]);
replaceAll(List list,Object oldVal,Object newVal)
所有的旧值改为新值。
转换为线程安全的方法
Collections.synchronizedxxx([传入需要转换的集合])。
调用相关方法转换为线程安全的。
List<Object> list = Collections.synchronizedList(list);
5.其他
1.Arrays.asList()
问题:通过数组工具类中的asList方法将数组转换为List以后,使用相关如:add、remove等方法,报错
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4};
List<int[]> asList = Arrays.asList(arr);
asList.add(new int[]{5, 6});
}
问题分析:这是因为通过asList方法得到的List是Arrays的内部类。
而这个内部类中并没有相关方法,因此会报错。
解决方法:将得到的集合成为新实例集合的参数即可。
public static void main(String[] args) {
Integer[] arr = {1, 2, 3};
List<Integer> integerList = Arrays.asList(arr);
// 解决
List<Integer> list = new ArrayList<>(integerList);
list.add(4);
System.out.println(list);
}