单列集合
双列集合(key-value)
Collection接口的常用方法
Collection接口是Java集合框架的根接口,它定义了集合的一些基本功能。Collection接口的常用方法包括:
- add(E e) - 添加一个元素到集合中
- remove(Object o) - 从集合中删除一个元素
- contains(Object o) - 判断集合是否包含指定的元素
- containsAll 查找多个元素是否存在
- isEmpty() - 判断集合是否为空
- size() - 返回集合中的元素个数
- iterator() - 返回在集合上的迭代器
- toArray() - 将集合转换为数组
- clear() - 清空集合中的所有元素
- equals(Object o) - 判断两个集合是否相等
- hashCode() - 返回集合的哈希值
例如:
List<String> list = new ArrayList<>();
list.add("a"); // 添加元素
list.remove("a"); // 删除元素
System.out.println(list.size()); // 获取元素个数
for(String s : list) { // 通过迭代器遍历
System.out.println(s);
}
Collection接口规定了集合的基本操作和属性,它的实现类提供了各种不同性能tradeoff的集合实现,如List,Set等。
Collection接口的主要特点:
- Collection接口继承了Iterable接口,意味着所有Collection集合都可以通过for-each循环来遍历。
- Collection不是一个真正的接口,它也提供了一些默认方法的实现,如addAll(), removeAll()等。
- Collection表示一组object,可以通过iterator()获得迭代器对元素进行遍历。
- Collection接口没有直接提供任何方法修改自身的长度,是通过子类自己实现如add()和remove()。
- Collection实现了equals()和hashCode()方法来判断相等性。
- 主要子接口有List,Set和Queue。
- 主要实现类有ArrayList,LinkedList,HashSet,LinkedHashSet和TreeSet等。
使用Collection主要涉及:元素添加,删除,迭代遍历,检查包含关系等操作。选择合适的Collection实现类可以有效地存储和操作对象集合。
- collection实现子类可以存放多个元素,每个元素可以是Object
- 有些Collection的实现类,可以存放重复的元素,有些不可以
- 有些Collection的实现类,有些是有序的(List),有些不是有序(Set)
- Collection接口没有直接的实现子类, 是通过它的子接口Set和List 来 实现的
List接口和常用方法
List接口是Collection接口的子接口
- List集合类中元素有序(即添加顺序和取出顺序一致)、 且可重复
- List集合中的每个元素都有其对应的顺序索引,即支持索引
- List容器中的元素都对应一 个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
List接口常用的方法包括:
- add(E e) - 添加元素到列表末尾
- add(int index, E element) - 在指定位置插入元素
- addAll(Collection c) - 将一个集合的所有元素添加到列表末尾
- get(int index) - 返回指定位置的元素
- set(int index, E element) - 修改指定位置的元素
- remove(int index) - 删除并返回指定位置的元素
- remove(Object o) - 删除第一个满足o==element的元素
- clear() - 删除列表中的所有元素
- contains(Object o) - 判断列表是否包含某元素
- indexOf(Object o) - 返回元素在列表中首次出现的索引
- isEmpty() - 判断列表是否为空
- iterator() - 返回列表的迭代器
- size() - 返回列表中的元素个数
- subList(int from, int to) - 返回从from到to索引范围的子列表
- sort(Comparator c) - 对列表元素进行排序
List接口继承自Collection,也可以使用所有Collection通用方法来操作列表。
List接口有三种常见的遍历方式:
- 通过索引遍历
List<String> list = new ArrayList<>();
for(int i=0; i<list.size(); i++) {
String str = list.get(i);
//...
}
- 通过迭代器遍历
List<String> list = new ArrayList<>();
Iterator<String> it = list.iterator();
while(it.hasNext()) {
String str = it.next();
//...
}
- 通过增强for循环遍历
List<String> list = new ArrayList<>();
for(String str : list) {
//...
}
增强for循环和迭代器遍历是Java 5后引入的新特性,可以简化代码。
索引遍历可以灵活访问任意索引位置,但代码繁琐。
迭代器遍历更加简洁,可以同时进行删除操作,但不能存取随机索引。
增强for循环最简洁,但只能遍历不能删除。
ArrayList底层结构和源码分析
permits all elements, including null , ArrayList可以加入null,并且多个
ArrayList是由数组来实现数据存储的
ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高)在多线程情况下,不建议使用ArrayList
ArrayList中维护了一个Object类型的数组elementData.
transient表示瞬间,短暂的,表示该属性不会被序列化
当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。
如果使用的是指定大小的构造器,则初始elementData容量为指定大小, 如果需要扩容,则直接扩究elementData为1.5倍
Vector 底层结构和源码剖析
Vector底层也是一个对象数组,protected Object[] elementData;
Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized
在开发中,需要线程同步安全时,考虑使用Vector
LinkedList
LinkedList底层实现了双向链表和双端队列特点
可以添加任意元素(元素可以重复),包括null
线程不安全,没有实现同步
LinkedList 的常用方法和遍历方式包括:
- add(E e) - 尾部加入元素
- add(int index, E element) - 在指定位置加入元素
- addFirst(E e)/addLast(E e) - 头部/尾部加入元素
- get(int index) - 获取指定位置的元素
- set(int index, E element) - 设置指定位置的元素
- remove(int index)/removeFirst()/removeLast() - 删除指定位置/头部/尾部的元素
- contains(Object o) - 判断是否包含某个元素
- size() - 获取元素个数
遍历方式:
- 通过索引遍历:
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
- 通过迭代器遍历:
Iterator iter = list.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}
- 通过 foreach 循环遍历:
for (Object obj : list) {
System.out.println(obj);
}
- 通过descendingIterator反向遍历:
Iterator descIter = list.descendingIterator();
while (descIter.hasNext()) {
System.out.println(descIter.next());
}
ArrayList和LinkedList比较
如何选择ArrayList和LinkedList:
1)如果我们改查的操作多,选择ArrayList
2)如果我们增删的操作多,选择LinkedList
3)一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList
4)在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另外一个模块是LinkedList.
Set接口和常用方法
- 无序(添加和取出的顺序不一致),没有索引
- 不允许重复元素,所以最多包含一个null
和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样.
Set接口的遍历方式同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。
- 可以使用迭代器
- 增强for
- 不能使用索引的方式来获取
package org.example.set_;
import java.util.HashSet;
import java.util.Iterator;
/**
* @author adekang
* @version 1.0
* @date 2023/8/29
*/
public class Set01_ {
public static void main(String[] args) {
HashSet set = new HashSet();
set.add("abc");
set.add("123");
set.add("def");
set.add(null);
// 遍历 方式一
for (Object o : set) {
System.out.println(o);
}
// 迭代器
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
package org.example.set_;
import java.util.HashSet;
/**
* @author adekang
* @version 1.0
* @date 2023/8/29
*/
public class Set02_ {
public static void main(String[] args) {
HashSet set = new HashSet();
// 会返回一个boolean值,如果添加成功,返回true,如果添加失败,返回false
System.out.println(set.add("abc"));
System.out.println(set.add("abc"));
System.out.println(set.add("1"));
System.out.println(set.add("2"));
set.remove("abc");
}
}
package org.example.set_;
import java.util.HashSet;
/**
* @author adekang
* @version 1.0
* @date 2023/8/29
*/
public class Set02_ {
public static void main(String[] args) {
HashSet set = new HashSet();
// 会返回一个boolean值,如果添加成功,返回true,如果添加失败,返回false
System.out.println(set.add("abc"));
System.out.println(set.add("abc"));
System.out.println(set.add("1"));
System.out.println(set.add("2"));
set.remove("abc");
set.add(new Dog("小黑"));
set.add(new Dog("小黑"));
System.out.println(set);
set.add(new String("ade"));
set.add(new String("ade")); // 加入不了,因为String类重写了equals方法,比较的是内容
System.out.println(set);
}
}
class Dog {
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + ''' +
'}';
}
}
HastSet源码
- HashSet底层是HashMap
- 添加一个元素时,先得到hash值会转成->索引值
- 找到存储数据表table ,看这个索引位置是否已经存放的有元素
- 如果没有,直接加入
- 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
- 在Java8中,如果一条链表的元素个数超过TREEIFY THRESHOL
- D(默认是8),并且table的大小>=MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树)
第一步
第二步
第三步
第四步
求出 hash值 不完全等价于hashCode值
第五步
/**
* Implements Map.put and related methods.
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; // 辅助变量
// table 是一个haspMap的一个属性, 类型是 Node[]
// 第一次扩容,到16个空间
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 根据key,得到hash值 去计算key因该存放到table表的那个索引位置
// 并把这个位置的对象,赋给 p
// 判断p是否为空 key "abc" value = PERSENT
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 &&
// 如果当前索引位置对应的链表的第-一个元素和准备添加的key的hash值一样
// 并且满足 下面两个条件之一 :
//(1) 准备加入的key和p指向的Node 结点的key是同一个对象
//(2) p指向的Node结点的key 的equals() 和准备加入的key比较后相同
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 再判断p是不是一颗红黑树,
// 如果是 颗红黑树,就调用 putTreeVal ,来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 如果tabLe对应索引位置,已经是一个链表, 就使用for循环比较
// (1) 依次和该链表的每一个元素比较后,都不相同,则加入到该链表的最后
// 注意在把元素添加到链表后,立即判断该链表是否已经达到8个结点
// 就调用treeifyBin() 对当前这个链表进行树化(转成红黑树)
// 注意,在转成红黑树时,要进行判断,判断条件
// if (tab == nu1l || (n = tab.Length) < MIN_ TREEIFY_ CAPACITY(64))
// resize() ;
// 如果上面条件成立,先tabLe扩 容.
// 只有上面条件不成立时,才进行转成红黑树
// (2)依次和该链表的每个元素比较过程中,如果有相同情况,就直接break
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;
}
}
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;
}
扩容机制
- HashSet底层是HashMap,第一次添加时,table 数组扩容到16,临界值(threshold)是16加载因子(loadFactor)是0.75 = 12
- 如果table数组使用到了临界值12,就会扩容到16* 2 = 32,新的临界值就是32*0.75 = 24,依次类推
- 在Java8中,如果一条链表的元素个数到达TREEIFY THRESHOLD(默认是8 ),并且table的大小>=MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制
LinkedHashSet
- LinkedHashSet是HashSet的子类
- LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表
- LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
- LinkedHashSet不允许添重复元素
源码剖析
- LinkedHashSet加入顺序和取出元素,数据的顺序致
- LinkedHashSet 底层维护的是一个L inkedHashMap (是HashMap的子类)
- LinkedHashSet 底层结构(数组table+双向链表)
- 添加第一次时,直接将数组tabLe扩容到16 ,存放的结点类型是LinkedHashMap$Entry
- 数组是HashMapEntry类型
Map
- Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
- Map中的key和value 可以是任何引用类型的数据,会封装到HashMap$Node对象中
- Map中的key不允许重复,原因和HashSet 一样,前面分析过源码.
- Map中的value可以重复
- Map的key可以为null, value也可以为null ,注意key为null,只能有一个,value为null ,可以多个.常用String类作为Map的key
- key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
package org.example.map;
import java.util.HashMap;
/**
* @author adekang
* @version 1.0
* @date 2023/9/4
*/
public class Map01 {
public static void main(String[] args) {
HashMap hashMap = new HashMap();
hashMap.put("no1", 10);
hashMap.put("no2", 20);
hashMap.put("no3", 10);
hashMap.put("no4", 20);
System.out.println(hashMap.get("no1"));
// System.out.println(hashMap);
}
}
- Map存放数据的key-value示意图,一对k-v是放在一个HashMap$Node中的, 有 因为Node实现了Entry 接口,有些书上也说一对k-v就是一个Entry
Map接口常用方法
- put:添加
- remove:根据键删除映射关系
- get:根据键获取值
- size:获取元素个数
- isEmpty:判断个数是否为0
- clear:清除
- containsKey:查找键是否存在
package org.example.map;
import java.util.HashMap;
/**
* @author adekang
* @version 1.0
* @date 2023/9/5
*/
public class MapMethod {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("邓超", "孙俪");
map.put("李晨", "范冰冰");
map.put("刘德华", "柳岩");
System.out.println(map);
map.remove("刘德华");
Object val = map.get("邓超");
System.out.println(val);
System.out.println(map.size());
System.out.println(map.containsKey("李晨"));
System.out.println(map.containsValue("范冰冰"));
System.out.println(map.isEmpty());
map.clear();
}
}
Map遍历方式
- containsKey:查找键是否存在
- keySet:获取所有的键
- entrySet:获取所有关系
- values:获取所有的值
package org.example.map;
import java.util.*;
/**
* @author adekang
* @version 1.0
* @date 2023/9/5
*/
public class MapMethod {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("邓超", "孙俪");
map.put("李晨", "范冰冰");
map.put("刘德华", "柳岩");
// 第一组 keySet()方法
for (Object o : map.keySet()) {
System.out.println(o + " " + map.get(o));
}
// 迭代器 快捷键 itit
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + " " + map.get(key));
}
// 第二组 values()方法
Collection values = map.values();
for (Object value : values) {
System.out.println(value);
}
// 迭代器
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
Object value = iterator1.next();
System.out.println(value);
}
// 第三组 EntrySet 获取
Set entrySet = map.entrySet();
for (Object entry : entrySet) {
Map.Entry entry1 = (Map.Entry) entry;
System.out.println(entry1.getKey() + " " + entry1.getValue());
}
Iterator iterator2 = entrySet.iterator();
while (iterator2.hasNext()) {
Object next = iterator2.next();
Map.Entry entry = (Map.Entry) next;
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
}
Map底层
- HashMap底层维护了Node类型的数组table,默认为null
- 当创建对象时,将加载因子(loadfactor)初始化为0.75.
- 当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key是否和准备加入的key相等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。
- 第1次添加,则需要扩容table容量为16,临界值(threshold)为12.
- 以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍,即24,依次类推.
- 在Java8中,如果一条链表的元素个数超过TREEIFY THRESHOLD(默认是8 ),并且table的大小>= MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树)
HashTable
- 存放的元素是键值对:即K-V
- hashtable的键和值都不能为null, 否则会抛出NullPointerException\
- hashTable使用方法基本上和HashMap-样
- hashTable是线程安全的,hashMap是线程不安全的
Properties
- Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形 式来保存数据。
- 他的使用特点和Hashtable类似
- Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象, 并进行读取和修改
- 说明:工作后xxx.properties 文件通常作为配置文件,这个知识点在IO流举例
-
先判断存储的类型(一 组对象或一组键值对)
-
一组对象: Collection接口
允许重复: List 增删多: LinkedList [底层维护了-个双向链表] 改查多: ArrayList [底层维护Object类型的可变数组] 不允许重复: Set 无序: HashSet [底层是HashMap ,维护了一个哈希表即(数组+链表+红黑树)] 排序: TreeSet 插入和取出顺序一致: LinkedHashSet , 维护数组+双向链表
-
一组键值对: Map 键无序: HashMap [底层是:哈希表jdk7:数组+链表,jdk8: 数组+链表+红黑树] 键排序: TreeMap 键插入和取出顺序一致: LinkedHashMap 读取文件Properties
Collections工具类
- Collections是一个操作Set、List 和Map等集合的工具类
- Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
排序操作
- reverse(List):反转List中元素的顺序
- shuffle(List):对List集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定List集合元素按升序排序
- sort(List, Comparator): 根据指定的Comparator产生的顺序对List 集合元素进行排序
- swap(List, int, int): 将指定list集合中的i处元素和j处元素进行交换
- 应用案例演示Collections java
package org.example.collections;
import java.util.ArrayList;
import java.util.Collections;
/**
* @author adekang
* @version 1.0
* @date 2023/9/7
*/
public class Collections_ {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
Collections.reverse(arrayList);
System.out.println(arrayList);
}
}
package org.example.collections;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
/**
* @author adekang
* @version 1.0
* @date 2023/9/7
*/
public class Collections_ {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("张三");
arrayList.add("李四二");
arrayList.add("王二麻子");
Collections.reverse(arrayList);
System.out.println(arrayList);
Collections.sort(arrayList, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String) o1).length() - ((String) o2).length();
}
});
}
}
查找、替换
- Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
- Object max(Collection, Comparator): 根据Comparator指定的顺序,返回给定集合中的最大元素
- Object min(Collection)
- Object min(Collection, Comparator)
- int frequency(Collection, Object): 返回指定集合中指定元素的出现次数
- void copy(List dest,List src):将src中的内容复制到dest中
- boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List对象的所有旧值