文章目录
集合
也可以叫做容器,可以容纳其他类型的数据。
所有的集合类和接口都在java.util包中
集合中存储的是对象的内存地址。本篇中基本上只归纳了一些比较常用的集合类,其他的可以通过这些扩展,为了记录下学习过程而归纳至此。
集合结构图(常用)
Collection接口中的方法
boolean add(E e)
向集合中添加一个元素,如果因为调用这个方法集合中元素发生了改变,则返回true。在一些集合中如果要添加的元素不允许重复并且已经存在,则返回false。
int size()
返回集合中元素的个数,如果这个集合包含的元素个数超出了Integer.MAX_VALUE,则返回Integer.MAX_VALUE。
void clear()
此方法会将集合中的所有元素删除(可选择的操作),调用这个方法后集合将会变成空的。
如果这个集合不支持清空的操作,会抛出UnsupportedOperationException异常。
boolean contains(Object o)
如果集合中包含指定的元素,则返回true。更正规地说,当且仅当集合中存在一个元素e使得满足Object.equals(o, e)这个条件时返回true。
boolean remove(Object o)
==在指定元素存在的前提下,删除集合中的这个元素。(可选择的操作)==remove的底层也调用了equals方法。
如果指定元素的类型与集合不匹配,则会抛出ClassCastException类型转换异常。
如果指定元素是null并且集合不允许元素为空,则会抛出NullPointerException空指针异常。
如果删除操作在当前集合不被允许,则会抛出UnsupportedOperationException异常。
boolean isEmpty()
如果当前集合不存在任何元素,则返回true。
Iterator
boolean hasNext()
如果在迭代时有更多的元素,则返回true。换句话说,当next()返回的是一个元素而不是抛出一个异常的时候,返回true。
E next()
返回迭代中的下一个元素。
集合的结构如果发生改变,迭代器必须重新获取。如果迭代过程中出现更改集合结构的操作,则会产生ConcurrentModificationException异常。但是可以通过使用迭代器的remove方法删除集合的元素。
原理:
获取迭代器对象后,迭代器用来遍历集合,此时迭代器对集合的状态拍了一个“快照”。迭代器迭代时会通过对比“快照”和原始集合中的状态是否一致,如果原始集合与快照中的集合状态不匹配,则会抛出异常 ;但是使用迭代器的remove方法则会同时删除“快照”中的元素和原始集合中的元素。
用“for each”循环可以很好的代替带有迭代器的循环,它可以处理任何实现了Iterable接口的对象。
List接口
List的特点:
有序,可重复,即存储元素的顺序与取出元素的顺序一致,并且元素可以重复。
List中的方法(特有的、常用的):
void add(int index, E element)
在指定的位置上添加一个指定的元素。将当前处于该位置的元素(如果有的话)和随后的元素向右移动(它们的下表都加一)。效率较低
E get(int index)
返回在List中指定索引上的元素。
int indexOf(Object o)
返回指定元素在List中第一出现的索引值,否则当List不包含指定元素时返回-1。更正规地说,返回满足Object.equals(o, get(i))条件的最小的索引i,否则没有这样的索引时返回-1。
int lastIndexOf(Object o)
返回指定元素在List中最后一次出现的索引值。
E remove(int index)
删除List中指定位置上的元素(可选择的操作)。向左移动其后面的所有元素(将它们的索引值减一)。返回值为在指定位置上被删除的元素。
E set(int index, E element)
将指定元素把List中指定位置上的元素替换掉(可选择的操作)。返回值为被替换的元素。
使用Collections集合工具类的sort()对List中的元素进行排序时,该元素的必须实现Comparable接口。
ArrayList
默认初始容量为10
扩容就是将原始容量变为其1.5倍。(oldCapacity >> 1)
优点:
检索数据时效率较高,由于底层采用的数据结构为数组,数组是一串连续的内存地址。
缺点:
增加或删除元素的时候需要移动其他元素,效率较低。线程不安全。
注意: 因为扩容涉及到内存申请和数据迁移,是比较耗时的,所以如果实先能确定需要存储的数据大小,最好在创建ArrayList的时候事先指定数据大小。
LinkedList
无初始容量。底层采用的数据结构为双向链表。
优点:
增删操作效率比较高。
缺点:
查询操作效率较低,因为链表中的元素在空间存储上的内存地址并不连续。
Vector
初始容量为10,扩容机制为扩大为原来的两倍。线程安全,但很少使用了。
线程不安全变成线程安全
使用java.util.Collections类中的方法
例:
public class Example{
public static void main(String[] args){
List list = new ArrayList();
Collections.synchronizedList(list);
list.add("111");
list.add("222");
list.add("333");
}
}
Set
无序,不可重复。不允许增加重复的元素,存储和取出元素的顺序不一致。
TreeSet
特点:TreeSet是一个有序集合。
放在TreeSet中元素如果是自定义的类,那么必须要实现Comparable接口,或者在构造集时必须提供一个Comparator。
比较器的选择:
当比较规则固定,实现Comparable接口。
当比较规则多变,需要频繁切换,则选择使用Comparator接口。(其满足OCP原则)
Map接口
结构:
存放在Map集合中的元素是以key,value的键值对形式存在的。键是唯一的,并且不能对同一个键存放两个值。如果同一个键增加两个值,那么第二个值会取代上一个。
Map接口中的常用方法:
boolean containsKey(Object key)
如果map中包含指定key,则返回true。更正规地说,当且仅当这个map中包含一个键k它满足Object.equals(key, k)的条件,则返回true。
boolean containsValue(Object value)
如果map中有一个或多个键映射指定值,则返回true。更正规地说,当且仅当这个map中至少包含一个映射到的值v满足Object.equals(value, v)的条件则返回true。
Map集合的遍历:
1、通过keySet()方法将Map转换为只有key的Set集合
(1)再通过迭代器的方法遍历所有的key获取对应的value。
(2)增强for
Map<Integer, String> map = new HashMap<>();
map.put(1,"zhangsan");
map.put(2,"lisi");
map.put(3,"wangwu");
map.put(4,"zhaoliu");
Set<Integer> keys = map.keySet();
for(Integer key: keys){
System.out.println(key + "=" + map.get(key));
}
2、通过entrySet()方法将map转换为set集合。
(1)迭代器
Set<Map.Entry<Integer, String>> set = map.entrySet();
Interator<Map.Entry<Integer,String>> it = set.iterator();
while(it.hasNext()){
Map.Entry<Integer,String> node = it.next();
Integer key = node.getKey();
String value = node.getValue();
}
(2)增强for
for(var entry : set){ //这里可以使用var来代替Map.Entry
System.out.println("key=" + entry.getKey() + ", value=" + entry.getValue);
}
3、forEach()+Lambda表达式
map.forEach(k,v) -> {
System.out.println("key=" + k + ", value=" + v);
}
HashMap
无序,存储与取出的顺序不相同;不可重复,存储的数据不能重复。
底层数据结构为哈希表,哈希表又称为散列表,是由数组和单向链表的组合形成的数据结构。
Node<K,V> [] table;
数组中每个元素都是一个Node,其中包括:
final int hash; hash值,通过哈希函数计算得出,可以将其转化为下标。
final K key; 存储在map集合中的键
V value; 存储在map集合中的值
Node<K,V> next; 下一个节点的内存地址
存取原理:
1、put(k, v)
第一步:先将key,value封装到一个node节点中。
第二步:底层调用hashCode()计算hash的值,并通过哈希函数的算法将hash值转化为数组下标,如果下标位置上没有任何元素,就把Node添加到这里;如果下标位置上有链表,会用指定key与链表上的节点中的key进行比对,如果相同,则将value覆盖,如果不同,就将这个新节点添加在这个链表的末尾。
2、get(k)
通过hashCode()方法计算出指定key对应的hash值,再通过哈希算法将其转化为数组下标,然后通过数组下标找到哈希表中数组对应的位置,如果这个位置上什么都没有,则返回null;如果有单向链表,再通过equals方法对比这个下标所在的位置上的链表中的所有节点的key值,如果所有都返回false,则返回null,如果指定key值与某个节点的key通过equals方法返回true,则将这个节点的value值返回。
综上,注意放在HashMap中key部分的元素和HashSet中的元素,要同时重写hashCode()和equals()。
初始化容量
HashMap的初始化容量为16,指定容量时必须是2的倍数,这是因为达到散列均匀同时为了提高HashMap的存取效率。默认加载因子是0.75。
默认加载因子是指 当HashMap集合底层哈希表中的数组容量达到75%时,数组开始扩容。
JDK8之后,HashMap中单链表的长度大于8时,会将其改为红黑树的数据结构,小于6的时候会变回单向链表。为了提高检索效率。
集合之间的转换
Set<String> set = new HashSet<>();
List<String> list = new ArrayList<>(set);
本篇中基本上只归纳了一些比较常用的集合类,其他的可以通过这些扩展,为了记录下学习过程而归纳至此,继续努力!