数组与集合的区别
- 数组的长度是固定的,集合的长度是可变的;
- 数组中可以存储基本数据类型,也可以存储对象,而集合中只能存储对象;
集合的分类
- Collection:表示一组对象;
- Map :表示一组映射关系或键值对;
集合体系
Iterable 接口
iterator() 方法返回 Iterator 接口的实例,用于遍历Collection元素;
只要是实现了 Iterable 接口的集合类都可以直接使用加强for循环来遍历;
加强 for 循环内部帮我们使用 Iterator 遍历;
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {....};
}
Iterator 接口
Java 访问 Collection 总是通过统一 的方式——迭代器(Iterator)来实现;
优点在于:
- 无需知道集合内部元素是按什么方式存储的;
- 对于不同的集合类型,返回的 Iterator 对象实现也是不同的,但总是具有最高的访问效率;
- 迭代器使用的注意点:
集合对象每次调用 iterator() 方法都会得到一个全新的迭代器对象;游标默认在第一个元素之前;
remove 方法的注意点:- 未调用 next() 就 remove;
- 调用一个next,连续调用2次 remove;
报异常:IllegalStateException;
// Iterator 接口,取代了原始的迭代器:Enumeration接口
public interface Iterator<E> {
boolean hasNext();
E next();
//如果在迭代过程中以除调用此方法之外的任何方式修改了基础集合,则迭代器的行为是未指定的。
//这个方法在每次调用 next 时只能被调用一次;
default void remove(){};
default void forEachRemaining(Consumer<? super E> action){
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
};
}
Collection接口
Collection : 表示一组对象,此接口继承了 Iterable 接口;
- List :有序列表,按照元素放入的先后顺序存放,每个元素都可以通过索引确定自己的位置;
-ArrayList :内部使用数组来存储所有的元素;
-Vector: 一种线程安全的 List 实现;
- -Stack:栈
-LinkedList:通过双向链表的方式实现;(实现了List和Deque接口) - Set :没有重复元素的集合;
-HashSet
- -LinkedHashSet : 可以按照添加顺序遍历
-TreeSet : 自然顺序(从小到大),必须是实现 Comparable接口的同一类型数据; - Queue :队列,先进先出的有序表;
-Deque :双端队列接口;
- -LinkedList
- -ArrayDeque
-PriorityQueue:优先级队列,类;
-BlockingQueue:阻塞对列接口,获取元素时可能会让线程变成等待状态 ;java.util.concurrent
- -ArrayBlockingQueue: 由数组结构组成的有界阻塞队列。
- -SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列;
- -LinkedBlockingQueue:由链表结构组成的有界阻塞队列。
- -PriorityBlockingQueue:支持优先级排序的无界阻塞队列;
注:
1、list可以在任意位置添加和删除元素,而 Queue 只有两个操作:- 把元素添加到队列末尾;add() / offer
- 从队列头部取出元素;remove() / poll()
2、LinkedList 既实现了 List 接口,又实现了 Queue 和 Deque 接口,使用的时候根据需要来指定引用:
//List
List<String> list = new LinkedList<>();
//Queue
Queue<String> queue = new LinkedList<>();
//Deque
Deque<String> deque = new LinkedList<>();
3、PriorityQueue : 优先队列
PriorityQueue 和 Queue 的区别在于,它的出队顺序与元素的优先级有关,调用 remove 或 poll,返回的总是优先级最高的元素。
放入 PriorityQueue 的元素,必须实现 Comparable 接口,PriorityQueue 会根据元素的排序顺序决定出队的优先级;
如果我们要放入的元素并没有实现Comparable接口,PriorityQueue允许我们提供一个Comparator对象来判断两个元素的顺序。
队列中的元素按照自然顺序(从小到大)弹出,“小元素”的优先级最高;
public class Main {
public static void main(String[] args) {
Queue<String> q = new PriorityQueue<>();
// 添加3个元素到队列:
q.offer("apple");
q.offer("pear");
q.offer("banana");
System.out.println(q.poll()); // apple
System.out.println(q.poll()); // banana
System.out.println(q.poll()); // pear
System.out.println(q.poll()); // null,因为队列为空
}
}
4、Stack
一种后进先出(LIFO)的数据结构;
stack 的相关操作:
压栈:push(E);
弹出栈顶的元素: pop();
取栈顶元素但不弹出:peek();
Collection 接口
常用方法:
- boolean add(Object obj) # 添加元素
- boolean c1.addAll(Collection c2) # 提取 c2 中的元素,分别添加到 c1;
- int c.size() # 获取集合中元素的数量;
- void c.clear() # 清空集合;
- boolean c.isEmpty() # 判断集合是否为空;
- boolean c.remove(Object obj) # 删除指定的元素,如果列表中有重复的元素,则只能删除第一个;
- boolean c1.removeAll(c2) # 求差集;从 c1 中移除c2中的所有元素;
- boolean c1.retainAll(c2) # c1 改变为与 c2 的交集;
- boolean c.contains(Object) # 判断集合内是否包含指定元素,调用 equals;
- boolean c1.containsAll(Collection c2) # 判断 c2 中的元素是否都存在于 c1 中;
- Object[] c.toArray() # 转为数组;
- boolean c1.equals(c2) # 判断 c1 与 c2 是否想相等; List : 元素 +顺序; set 只看元素;
List 接口
接口中的方法
List接口中的方法增加了索引操作:
1、void add(int index, Object ele) # 在 index 位置插入 ele 元素;
2、boolean addAll([int index],Collection eles) # 从 Index 位置开始,将 eles 中的所有元素添加进来;
3、Object get(int index) # 获取指定 index 位置的元素;
4、int indexOf(Object obj ) # 返回 obj 在集合中首次出现的位置,找不到返回 -1;
5、int lastIndexOf( Object obj) # 返回 obj 在当前集合中末次出现的位置;
6、Object remove(int index)
7、Object set(int index, Object ele) # 返回原值;
8、List subList(int fromIndex, int toIndex) : 左闭右开
注意:
要正确使用 List 的 contains() 、 indexOf() 这些方法,放入的实例必须正确覆写 equals() 方法,否则查找不到;
怎样正确编写 equals方法?
1、先确定实例相等的逻辑,即哪些字段相等,就认为实例相等;
2、用 instanceof 判断传入的待比较的 Object 是不是当前类型,如果是,继续比较,否则,返回 false;
3、对引用类型用 Objects.equals() 比较,对基本类型直接用 == 比较;
List 与数组之间的转换
List 转数组:
1、Object[] array = list.toArray(); # 会丢失类型信息;
2、给 toArray(T[]) 传入一个类型相同的 Array,List内部自动把元素复制到传入的 Array 中:
Integer[] array = list.toArray(new Integer[3]);
或者
Integer[] array = list.toArray(Integer[] : :new );
注意:
1、如果数组类型与集合存储的类型不一致,会抛异常: ArrayStoreException;
2、如果传入的数组不够大,List内部会创建刚好够大的数组,填充后返回;
3、如果传入的数组比 List 元素还要多,剩下的数组元素一律填充 null;
4、List 可以直接打印出其中的元素,而对于数组则需要调用Arrays.toString(strArray);
// 只有List指定了泛型,且toArray传入了对应类型数组作为参数,才能返回指定类型的数组
List<String> list =new ArrayList<>();
String[] strArry = list.toArray(new String[]{});
//未泛型但指定参数;返回的仍然是Object数组
List list2 =new ArrayList();
Object[] objects = list2.toArray(new String[]{});
数组转List
Arrays.asList(T…t) : 快速创建一个list 并赋值;
接收可变长的参数,转换成一个不可变的集合;返回一个受指定数组支持的固定大小的列表;
不可变:
不能进行 add 、remove,否则报UnsupportedOperationException
可以进行 set(index,obf) 修改;但是index如果超出索引下标范围,会 ArrayIndexOutOfBoundsException;
注意:
Arrays.asList(T…t) # 里面是一个个对象,如果传入基本数据类型的一个数组,会被看做一个引用数据类型整体添加;
List list1 = Arrays.asList(new int[]{1,2,3});
System.out.println("list1.size() = " + list1.size()); //1
对于非基本数据类型的数组
List list2 = Arrays.asList(new String[]{“a”,“b”,“c”});
System.out.println("list2.size() = " + list2.size());//3
Set接口中的方法
Set 接口在 Collection 接口的基础上没有额外提供方法;
只实现了 Collection 接口中的默认方法:
default boolean removeIf(Predicate<? super E> filter);
set集合不允许包含相同的元素,如果添加相同的元素到一个set集合中,则添加失败(不会报错)!
set 根据 equals 方法的返回值来判断两个对象是否相等;
放入 Set 的元素,必须正确实现 equals 和 hashCode 方法;
Set相当于只存储 key 不存储 value 的Map,我们通常用它来去除重复元素;
public HashSet() {
map = new HashMap<>();
}
// 存放元素时,只使用了HashMap的key,
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// value是一个静态常量,所有key共用同一个 value,节省了内存
private static final Object PRESENT = new Object();
利用 Set 快速对 list 进行去重:
Set set = new HashSet();
set.addAll(list);
List newList = new ArrayList(set);
Queue 接口中的方法
Throw Exception | 返回false或null | |
---|---|---|
添加元素到队尾 | boolean add(E e) | boolean offer(E e) |
取队首元素并删除 | E remove() | E poll() |
取队首元素但不删除 | E element() | E peek() |
ArrayList
底层采用Object数组;大小可变数组的一种实现;
底层使用的是 Arrays.copyOf():
public static <T> T[] copyOf(T[] original, int newLength)
# original: 将要被复制的数组
# newLength: 返回的复制后的数组的长度
# 返回原始数组的副本
ArrayList的构造器
1、传入一个集合,按照从此集合迭代器中,迭代出来的元素顺序,构造一个ArrayLsit;
public ArrayList(Collection<? extends E> c) {...}
2、开发过程中如果知道数组的长度,可以使用带参的构造器,传入一个合适的初始容量,避免使用过程中发生扩容;
public ArrayList(int initialCapacity) {...}
3、空参构造器
JDK1.7:new对象时,空参构造器默认创建一个长度为 10 的Object 数组;容量不够时扩大为原来的1.5倍;
JDK1.8:new对象时,生成的是长度为 0 的Object数组,第一次添加元素时才会生成一个长度为10 的数组;
扩容
存储的数据个数 > 底层数组长度, 执行扩容;
Map 接口
继承关系
Map接口:
– HashMap (主要实现类,线程不安全,可以存储 null 的 key 和 value)
----LinkedHashMap(在HashMap的基础上加了一对指针,HashMap 的子类,保证可以按照添加顺序遍历 map,对于频繁的遍历操作,此类的执行效率比较高)
– HashTable (古老实现类,线程安全,key和value不能为 null);
---- Properties (常用来处理配置文件,key 和 vlaue 都是 String);
– SortedMap(接口)
---- TreeMap (底层是红黑树,可以实现排序遍历,key必须是同一个类的对象);
CurrentHashMap : 高并发条件下使用的一种线程安全的 Map;
接口中常用方法
Object put(Object key , Object value ) : 添加或修改,key已存在时,修改操作,返回的是旧值, key 不存在时,添加操作,返回的是 null;
void putAll(Map m) : 将 m 中的所有 k-v 放入到当前 map 中;
Object remove(Object key) : 移除指定 key 的 k-v 对,并返回 value;如果 key不存在,则返回 null;
boolean remove(Object key , Object value) : key不存在或者 key 对应的 value 与形参不一致,返回 false;
void clear() : 清空当前 map 中的所有数据;
V replace(K key , V value) # key 不存在返回 null; key 存在,替换旧值,并返回旧值;
boolean replace( K key , V oldValue , V newValue) # key & value 都存在,替换成功,返回 true,否则返回 false;
Object get(Object key) :
boolean containsKey(Object key)
boolean containsValue(Object value)
int size():
boolean isEmpty()
boolean equals(Object obj)
Set keySet( ) : 返回所有 key 构成的 Set 集合;
Collection values() : 所有 value 构成的 Collection 集合;
Set entrySet() : 返回所有 key-value 对 构成的 Set 集合,Entry对象中可以 entry.getKey() , entry.getValue();
equals() 与 hashCode()的编写规则
equals() 返回 true , hashCode 计算的值(一个 int 整数)必须相同;
equals 返回 false, 两个对象的 hashCode() 尽量不要相等;
HashMap的存储过程
首先调用key所在类的 hashCode 方法,计算 hash值,与数组索引做映射,找到存储位置
- - 如果索引位置为空,直接添加;
- - 如果索引位置不为空,则判断 key 与已存在key的hash值是否相等;
- - - - 如果不相等,则添加成功;
- - - - 如果相等,则调用 key 的 equals 方法,
- - - - - - 如果返回 true 则修改value;
- - - - - - 如果返回 false 则添加;
HashMap 的扩容与树化
JDK7
扩容:添加元素时,Map 中键值对的数量大于等于阈值,且添加位置不为空,扩容为原来的2倍,旧数据重新存放;
树化:JDK7无树化行为;
JDK8
扩容:
- size 大于等于阈值,扩容为原来的2倍,扩容后旧数据要重新存放;
- 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 < 64 时;
树化:
当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 >= 64 时,此时此索引位置上的所有数据改为使用红黑树存储;
1、HashMap的设计者不希望一个桶里面有太多元素,同时,也不想让桶内的元素树化,如果能够通过扩容数组达到分散元素的目的,尽量扩容数组,
2、如果数组已经足够长 > 64 , 设计者才考虑将桶内元素树化,增强元素查找性能;
3、加载因子的合理值是0.75; 如果太小数组的利用率低,如果太大树化长度程度又太高;
equals 与 == 的区别
“==” :用于比较基本数据类型和引用类型数据:
1、当比较基本数据类型的时候比较的是数值;
2、判断两个引用是否指向同一个对象。
如果编程人员没有显示地重写 equals() 方法,则默认比较两个引用是否指向同一个对象。
//Object类中的equals()方法源码
public boolean equals(Object obj) {
return (this == obj);
}
TreeMap
遍历 TreeMap 得到的顺序一定是排好序的;
TreeMap 不使用 equals 和 hashCode;
放入的 key 必须实现 Comparable 接口(实现 int compareTo(T o));
如果 key 没有实现 Comparable 接口,那么在创建 TreeMap 时需要传入 Comparator 接口的实现类;
Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>(){
public int compare(Person p1,Person p2){
return p1.name.compareTo(p2.name);
}
});
TreeMap 在比较两个 Key是否相等时,依赖 Key 的 compareTo() 方法 或者 Comparator.compare()方法,当两个key相等时,必须返回 0 。 否则 map.get(xxx); 会返回 null;
Properties
读取 .properties 文件:
String f = "setting.properties";
Properties props = new Properties();
props.load(new FileInputStream(f));
// 从 Classpath 读取配置文件
props.load(getClass().getResourceAsStream("/commom/setting.properties"));
// key 不存在将返回 null。我们可以设置一个默认值;
String filepath = props.getProperty("auto_save_interval","120");
props.setProperty("language", "Java");
props.store(new FileOutputStream("C:\\conf\\setting.properties"), "这是写入的properties注释");
如果有多个.properties
文件,可以反复调用load()
读取,后读取的key-value会覆盖已读取的key-value;
Collections 工具类
Collections 是一个操作 Set、List 和 Map 等集合的工具类;
排序:
1、reverse(List) : 反转 List 中元素的顺序;
2、shuffle(List) : 对 List 集合元素进行随机排序;
3、sort(List)
4、sort(List, Comparator)
5、swap(List,int i int j) : 交换 i 处 和 j 处元素;
Object max(Collection) : 返回集合中自然顺序,最大的元素;
Object min(Collection) : 返回最小元素;