集合
1、集合的理解和好处
(1)数组
- 长度开始时必须指定,而且一旦指定,不能更改
- 保存的必须为同一类型的元素
- 使用数组进行增加/删除元素比较麻烦
(2)集合
- 可以动态的保存多个对象,使用比较方便
- 提供了一系列方便的操作对象的方法,
add,remove,set,get
等 - 使用集合添加,删除新元素比较简洁,方便
2、集合的框架体系
- 第一类:单列集合(List,Set)
- 第二类:双列集合(Map),存放的是键值对形式
3、COLLECTION接口和常用方法
(1)COLLECTION接口特点
- collection实现子类可以存放多个元素,每个元素可以是Object
- 有些Collection实现类,可以存放重复的元素,有些不可以
- 有些Collection实现类,有些是有序的(List),有些不是有序(Set)
- Collection接口没有直接的实现子类,是需要通过它的子接口Set和List实现的
(2)COLLECTION接口方法-以ArrayList为例
public static void main(String[] args) {
List list = new ArrayList();
list.add("滴滴");
list.add(true);
list.add(10); //存放的是对象,Integer(10)
list.remove(1); //删除对应下标元素
list.remove(true); //删除指定元素
list.contains("jack");
list.size();
list.isEmpty();
list.addAll(new ArrayList<>(Arrays.asList("shu","sho","sho")));
}
(3)COLLECTION 接口遍历元素方式
第一种方式:Iterator(迭代器)
Java迭代器(Iterator)是 Java 集合框架中的一种机制,是一种用于遍历集合(如列表、集合和映射等)的接口。
它提供了一种统一的方式来访问集合中的元素,而不需要了解底层集合的具体实现细节。Iterator本身并不存放对象。
迭代器接口定义了几个方法,最常用的是以下三个:
- next() - 返回迭代器的下一个元素,并将迭代器的指针移到下一个位置。
- hasNext() - 用于判断集合中是否还有下一个元素可以访问。
- remove() - 从集合中删除迭代器最后访问的元素(可选操作)。
第二种方式:增强for循环
两种方式举例:
public static void main(String[] args) {
// 创建集合
ArrayList<String> sites = new ArrayList<String>();
sites.add("Google");
sites.add("Runoob");
sites.add("Taobao");
sites.add("Zhihu");
//创建迭代器,进行遍历,相当于一个指针
Iterator iterator = sites.iterator();
while(iterator.hasNext()){
String str = (String)iterator.next();
System.out.println(str);
if(str.equals("Google")){ //如果是google,删除当前元素
iterator.remove();
}
}
//进行下次遍历的时候要对迭代器进行重置
iterator = sites.iterator();
//另外一种遍历方式,增强for循环
for(String str : sites){
System.out.println(str);
}
}
4、LIST接口和常用方法
(1)List接口介绍
- List集合中元素有序,可以重复
- List集合中每个元素都有对应的顺序索引
- List容器中元素都对应一个整数型的序号记录其位置,可以根据序号取元素
- 常用的接口实现类有ArrayList,LinkedList和Vector
(2)List常用方法
可以参考COLLECTION的方法介绍,基本一致,具体的建议看官方文档。
public static void main(String[] args) {
List list = new ArrayList();
list.add("张三丰");
list.add("潘小青");
list.add(1,"邹昊飞"); //在index = 1的地方插入
list.set(1,"明星"); //在指定index插入设置元素
list.get(1); //获取下标为1的元素
list.remove(1); //删除下标为1的元素
}
5、ArrayList底层结构与源码分析
(1)ArrayList的注意事项
- 允许所有类型的元素,包括空值null,并且可以多个
- ArrayList是由数组实现数据存储的
- ArrayList基本等同于Vector,除了ArrayList是线程不安全的(执行效率高),在多线程的情况下,不建议使用ArrayList
(2)ArrayList的底层结构
- ArrayList中维护了一个Object类型的数组elementData
transient Object[] elementData;
- 当创建ArrayList对象时,如果使用的是无参构造器,则初始化elementData容量为0,第一次添加,则扩容elementData为10,如果需要再次扩容,则扩容elementData为1.5倍
- 如果使用的是指定大小的构造器,则初始elementData为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。
核心源代码:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); //1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
6、Vector底层结构与源码分析
(1)定义说明
- Vector底层也是一个对象数组,protected Object[] elementData;
- Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized
- 在开发中,需要线程同步安全时,考虑使用Vector
(2) 底层结构
- 当创建Vector对象时,如果使用的是无参构造器,则初始化elementData容量为0,第一次添加,则扩容elementData为10,如果需要再次扩容,则扩容elementData为2倍
- 如果使用的是指定大小的构造器,则初始elementData为指定大小,如果需要扩容,则直接扩容elementData为2倍。
源代码:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//扩充为两倍
int newCapacity = oldCapacity + ((capacityIncrement > 0) ? //三元运算符
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
(3) Vector与ArrayList的对比
底层结构 | 版本 | 线程安全(同步)效率 | 扩容倍数 | |
---|---|---|---|---|
ArrayList | 可变数组 | jdk1.2 | 不安全,效率高 | 有参1.5倍 无参 1.第一次10 2.第二次1.5倍 |
Vector | 可变数组 | jdk1.0 | 安全,效率不高 | 有参2倍 无参 1.第一次10 2.第二次2倍 |
7、LinkedList底层结构
(1) 定义说明
- LinkedList底层维护了一个双向链表
- LinkedList中维护了两个属性first和last,分别指向首节点和尾结点
- LinkedList删除和添加元素的效率 较高
(2) 源码分析
-
新增节点,直接在链表尾部新增节点。更改指针指向。
-
void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) //为空和不为空的指向不一样;为空的话,first节点先指向第一个节点 first = newNode; else //不为空,新节点就需要接上前面那个链表 l.next = newNode; size++; modCount++; }
-
-
删除节点,在移除prev链和next链的时候分别判断是否是第一个节点和最后一个节点,分开判断和执行,涉及链表的断链和接链可以学习一下。
-
unlink(Node<E> x) { // assert x != null; //先去这个节点的三个值 final E element = x.item; final Node<E> next = x.next; final Node<E> prev = x.prev; //先处理prev,判断是否x前面是否为空,x是不是第一个节点 if (prev == null) { first = next; } else { prev.next = next; x.prev = null; } //然后处理next,判断x后面是否为空,x是不是最后一个节点 if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; }
-
8、ArrayList与LinkedList对比
(1)ArrayList与LinkedList对比
底层结构 | 增删的效率 | 改查的效率 | |
---|---|---|---|
ArrayList | 可变数组 | 较低,涉及扩容问题 | 较高,直接索引 |
LinkedList | 双向链表 | 较高,直接断链接链 | 较低,遍历链表 |
(2)如何选择ArrayList和LinkedList
-
涉及改查的操作多,选择ArrayList
-
涉及增删的操作比较多,选择LinkedList
-
一般来说,在程序中,80%-90%都是查询,大部分情况选择ArrayList
-
根据具体业务灵活选择
9、Set接口和常用方法
(1)接口基本介绍
- 无序(添加和取出的顺序不一致),没有索引
- 不允许重复元素,所以最多包含一个null(自动去重)
- 继承了COLLECTION接口,方法与COLLECTION接口一致
(2) Set接口的遍历方式
- 可以使用迭代器
- 增强for循环
- 不能使用索引的方式来获取
示例:
//遍历方式
//第一种方式,迭代器的方式
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
//第二种方式,采用增强for循环
for(Object o : set){
System.out.println("o:"+o);
}
10、Set接口实现类-HashSet
(1)接口基本介绍
-
继承了Set接口
-
HashSet底层实际上是HashMap,参考源码
-
public HashSet() { map = new HashMap<>(); }
-
-
可以存放null,但是只能有一个null(不能有重复元素)
-
HashSet不保证元素是有序的,取决于hash,再确定索引
(2)底层结构以及源码分析(hash()+equals())
- HashSet的底层是HashMap
- 添加一个元素时,先得到hash值,代码会转为索引
- 找到存储数据表table,看这个索引位置是否已经存放的有元素
- 如果没有,直接加入
- 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
- 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小 >=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
源码分析:
//第一步:转为HashMap,所以HashSet的本质就是HashMap
public HashSet() {
map = new HashMap<>();
}
//第二步:进入put, PRESENT可以认为是一个常量,所有的HashSet的value都是一样的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//第三步:进入putVal,也就是整个的核心代码
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//第四步:核心,将值插入到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; //辅助变量
//table为HashMap的数组,类型是Node[]
//如果当前table为空或者大小为0,第一次扩容到16个空间大小
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//如果当前key的hash值不在tab里面,直接创建新节点加入
if ((p = tab[i = (n - 1) & hash]) == null)//根据hash值与(n-1)进行与操作得到下标索引值
tab[i] = newNode(hash, key, value, null);
//如果当前key的hash值计算得到的索引不在当前表中,就继续判断
else {
Node<K,V> e; K k; //辅助变量
//判断该key是否已经在HashMap中
//满足两个条件之一:就不能加入
//1、准备加入的key值与当前Node的key值相同
//2、key不为空并且p节点的key的equals()与准备加入的key相同
//其中的equals函数是根据需要可以重写的,所以具体问题具体分析
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果第一个key不相同,就判断当前节点是否是一个红黑树,如果是,则在红黑树上面进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//如果不在红黑树上面,那就在链表中添加节点,遍历当前节点所在的链表
else {
//for循环比较该链表,并用bincount计数,判断是否需要转为红黑树
for (int binCount = 0; ; ++binCount) {
//如果后续没有找到,就直接创建一个新的节点插入到后面
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//如果长度大于等于8的时候,就需要考虑是否需要转换为红黑树了
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break; //for循环无限循环,得通过break来跳出
}
//在链表中找到了,无需插入,直接break
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break; //for循环无限循环,得通过break来跳出
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value; //这里是hashMap的对传进来相同的key中不同的value进行的更新
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//每次加入一个节点都需要++size,然后再来判断是否需要扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
(3)HashSet的扩容机制
- HashSet的底层是HashMap,第一次添加是,table数组扩容到16,临界值(threhold)是*加载因子(loadFactor)是0.75 = 12,
- 如果table数组使用到了临界值12,就会扩容到16 * 2 = 32,新的临界值就是32 * 0.75 = 24,依此类推
- 在Java8中,如果一条链表的元素个数到达TREEIfy——THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认是64),就会进行树化(红黑树),否则仍然采用数组的扩容机制
11、SET接口实现类-LinkedHashSet
(1)接口基本介绍
- LinkeHashSet 是HashSet的子类
- linkedHashSet 底层是一个LinkedHashMap,底层维护了一个数组+双向链表(有head和tail指针)
- LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时采用链表维护元素插入的时候的相对位置,这可以使得元素是以插入顺序保存的。
- LinkedHashSet不允许添加重复元素
(2)底层源码分析
本身数组是HashMap$Node[]
类型,存放的元素数据是LinkedHashMap$Entry
类型,二者有继承关系
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; //继承了HashMapNode,变成了双向链表
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
其余的插入操作、扩容操作和判断元素是否相同的操作与HashMap一致。
12、Set接口实现类—TreeSet
(1)TreeSet注意事项
- TreeSet接口继承了Set接口
- 底层是TreeMap,TreeMap的底层是红黑树
- 使用无参构造时,仍然是无序的
- 使用有参构造,需要自己传入比较器(匿名内部类来实现制定规则的排序)
举例:
//无参构造器无序,需要重新定义一个比较器(匿名内部类)
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// return ((String)o1).compareTo((String)o2);
//自定义比较函数,如果比较函数中两个值相同,就默认这两个元素相同,不会加入到Set当中
return ((String)o1).length() - ((String)o2).length();
}
});
treeSet.add("wwc"); //底层是TreeMap
treeSet.add("liujie");
treeSet.add("luoxiang");
treeSet.add("hanjie"); //如果是比较长度的话,这个与liujie长度相同,不会加入到treeSet中
System.out.println(treeSet);
13、Map接口和常用方法
(1)Map接口的特点(Jdk1.8)
- 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
- 一对k-v是放在一个HashMap $ Node中的,因为Node实现了Entry接口,有些书上也说一对k-v就是一个Entry
举例:
public static void main(String[] args) {
Map map = new HashMap();
map.put("no1","jack"); //key唯一,相同则覆盖
map.put("no1","jerry");
map.put(null,null); //都可以为null
map.put("no2",null); //value可以重复,也可以多个null
System.out.println(map.get("no1")); //通过get方法获取
System.out.println(map); //顺序不是加入的顺序
}
(2)Map中Node与Entry的关系辨析
Map底层用一个table以数组+链表+红黑书组织HashMap$Node,为了方便管理把Node封装为entry,再将entry放到EntrySet中
-
k-v 最后是HashMap$Node node = newNode(hash, key, value, null)
-
k-v为了方便程序员的遍历,还会创建EntrySet集合,集合存放的元素是Entry,一个Entry对象就有key和value
-
Set<Map.Entry<K, V>> entrySet(); //entryset结构
-
-
entrySet中,定义的类型是Map.Entry,但实际上存放的还是HashMap $ Node(Node实现了Map.Entry的接口)
-
当把HashMap $ Node对象存放到entrySet就方便我们的遍历(Map本身并没有迭代器),因为Map.Entry提供了重要的方法:K getKey()和V getValue()
举例:
Map map = new HashMap();
map.put("no1",123);
//结构:Set<Map.Entry<K, V>> entrySet();
Set set = map.entrySet();
System.out.println(set.getClass()); //HashMap$EntrySet
for(Object entry :set){
System.out.println(entry.getClass()); //HashMap$Node
//向下转型Object->Map.Entry
Map.Entry entry1 = (Map.Entry)entry;
System.out.println("entry1.getKey(): "+entry1.getKey() +" entry1.getValue(): " + entry1.getValue());
}
(3)Map的常用方法
Map map = new HashMap();
map.put("no1","jack"); //添加
map.put("no2","jerry"); //添加
String str = (String)map.get("no1"); //获取
System.out.println(str);
map.size(); //map大小
map.remove("no1"); //删除
System.out.println(map.isEmpty()); //判断是否为空
map.clear(); //清空
System.out.println(map.containsKey("no2")); //判断这个键是否存在
(4)Map接口的遍历方法
稍微比list,set麻烦,但是基本原理一致
//keyset,获取所有的键
//values,获取所有的值
//entryset,获取所有的键值对
Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");
//第一组:取出所有的key,然后通过get方法获取value,遍历方式为增强for循环和迭代器
Set keyset = map.keySet();
//1、增强for循环
for(Object key : keyset){
System.out.println(key+"-"+map.get(key));
}
//2、迭代器
Iterator iterator = keyset.iterator();
while(iterator.hasNext()){
Object key = iterator.next();
System.out.println(key+"-"+map.get(key));
}
//第二组:把所有的values取出来,遍历所有的values,但是无法获取key
Collection values = map.values();
//1、增强for循环
for(Object value : keyset){
System.out.println(value);
}
//2、迭代器
iterator = values.iterator();
while(iterator.hasNext()){
Object value = iterator.next();
System.out.println(value);
}
//第三组:使用EntrySet,然后使用entry的getKey和getValue方法
Set entrySet = map.entrySet();
//1、增强for循环
for(Object entry : entrySet){
Map.Entry mentry = (Map.Entry)entry; //向下转型
System.out.println(mentry.getKey()+"-"+mentry.getValue());
}
//2、迭代器
iterator = entrySet.iterator();
while(iterator.hasNext()){
Map.Entry mentry = (Map.Entry)iterator.next(); //向下转型
System.out.println(mentry.getKey()+"-"+mentry.getValue());
}
14、Map接口实现类-HashMap
(1)HashMap基本介绍
- HashMap是以key-val对来存储数据的
- HashMap是Map接口使用频率最高的实现类
- 如果添加相同的key,则会覆盖原来的key-val,等同于修改
- 与HashSet一样,不保证映射的顺序
- HashMap没有实现同步,线程不安全,方法没有做同步互斥的操作,没有synchronized
(2)HashMap底层源码分析
扩容机制:(与HashSet一样)
- 第一次添加是,table数组扩容到16,临界值(threhold)是*加载因子(loadFactor)是0.75 = 12,
- 如果table数组使用到了临界值12,就会扩容到16 * 2 = 32,新的临界值就是32 * 0.75 = 24,依此类推
- 在Java8中,如果一条链表的元素个数到达TREEIfy——THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认是64),就会进行树化(红黑树),否则仍然采用数组的扩容机制
源码分析参考HashSet
15、Map接口实现类-HashTable
(1)接口基本介绍
- 存放的是键值对:即 K-V
- hashtable的键和值都不能为null,否则会抛出NullPointerException
- hashTable使用方法基本与HashMap一致
- HashTable是线程安全的(synchronized),hashMap是线程不安全的
示例:
Hashtable hashtable = new Hashtable();
hashtable.put("no1",1); //添加
// hashtable.put("no1",null); //空指针异常NullPointerException
// hashtable.put(null,1); //空指针异常NullPointerException
hashtable.put("no1",2); //替换
hashtable.remove("no1"); //删除
System.out.println(hashtable);
(2)源码分析
-
底层是数组+链表,不会转化成红黑树
-
//数组 Entry<?,?> tab[] = table; //单个节点结构为链表 private static class Entry<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Entry<K,V> next;
-
-
初始化容量为11,有一个临界值threhold = 当前容量 * loadFactor(0.75),超过临界值便会扩容,扩容机制为原来的两倍+1
-
int newCapacity = (oldCapacity << 1) + 1;
-
(3) HashMap与HashTable对比
版本 | 线程安全(同步) | 效率 | 允许null | |
---|---|---|---|---|
HashMap | 1.2 | 不安全 | 高 | 可以 |
Hashtable | 1.0 | 安全 | 低 | 不可以 |
16、Map接口实现类TreeMap
(1)接口基本介绍
- TreeMap接口继承了Map接口
- TreeMap的底层是红黑树
- 使用无参构造时,仍然是无序的
- 使用有参构造,需要自己传入比较器(匿名内部类来实现制定规则的排序)
(2)源码分析
public V put(K key, V value) { //添加元素的底层源码
Entry<K,V> t = root;
if (t == null) { //当前为空的话,直接创新节点,加入到红黑树当中
compare(key, key); // type (and possibly null) check
//第一次加入数据也会进行比较,但是比较的话,是传入相同的值,排除非空的情况
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator; //如果自定义比较器不为空的话,也就是构造器传入了一个比较器的情况
if (cpr != null) {
do { //遍历红黑树结构,看是否找到节点,通过比较器进行判断
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value); //找到了的话,直接覆盖原来的值,并返回
} while (t != null);
}
else { //没有传入比较器的情况,调用之前设置的比较器
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
//如果都没有找到,就需要在红黑树中,插入一个节点
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
17、Map接口实现类-Properties
(1)基本介绍
- Properties类继承自hashtable类并且实现了Map接口也是一种使用键值对的形式来保存数据
- 使用方法与HashTable类似
- Properties可以用于从xxx.porperties文件之中,加载数据到Properties类对象,并进行读取和修改。xxx.properties文件通常作为配置文件。
(2)常用方法
Properties properties = new Properties();
//properties.put(null, "abc");//抛出 空指针异常
//properties.put("abc", null); //抛出 空指针异常
properties.put("john", 100);//k-v
properties.put("lucy", 100);
properties.put("lic", 100);
properties.put("lic", 88);//如果有相同的key , value 被替换
System.out.println("properties=" + properties);
properties.remove("lic"); //删除
System.out.println(properties.get("lucy")); //查找
18、总结,如何选择集合实现类
选择什么集合实现类,主要取决于业务操作特点。
主要参考以下几点:
- 先判断存储类型(单列/双列)(一组对象/键值对形式)
- 一组对象[单列]:Collection接口
- 允许重复:List
- 增删多:LinkedList[底层是双向链表]
- 改查多:ArrayList[底层是Object类型的可变数组]
- 不允许重复:Set
- 无序:hashSet[底层是HashMap,维护了一个哈希表[数组+链表+红黑树]]
- 排序:TreeSet
- 插入和取出顺序一致:LinkedHashSet,[数组+双向链表]
- 允许重复:List
- 一组键值对[双列]:Map
- 键无序:HashMap[底层是哈希表:jdk7:数组+链表,jdk8:数组+链表+红黑树]
- 键排序:TreeMap
- 键插入和取出顺序一致:LinkedHashMap
- 读取文件 Properties
- 一组对象[单列]:Collection接口
19、COLLECTION工具类
(1)工具类基本介绍
- Collections是一个集成了Set,List,Map的工具类
- Collections中提供了一系列静态的方法对集合进行排序、查询、修改等操作
(2)排序操作(均为static方法,针对List进行排序)
方法举例:
/创建ArrayList 集合,用于测试.
List list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
list.add("tom");
//1、sort(list)针对元素的顺序比较后进行排序
Collections.sort(list);
//2、sort(list, Comparator)添加指定的比较器进行排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//这里指定顺序进行排序
return ((String)o1).length() - ((String)o2).length();
}
});
//3、reverse(List)反转list比较
Collections.reverse(list);
//4、shuffle(List)随机排序
Collections.shuffle(list);
//5、swap(list, int, int)交换元素位置
Collections.swap(list, 1, 3);
System.out.println(list);
(3)其他方法
//6、max(Collection)返回最大元素,可以添加比较器,自行设定规则,来确定最大
System.out.println(Collections.max(list));
Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//这里指定长度进行排序
return ((String)o1).length() - ((String)o2).length();
}
});
//7、min参考max,方法一致
//8、frequency(Collection, Object)统计元素出现次数
System.out.println(Collections.frequency(list,"tom"));
//9、copy(List dest, List src)将src中的内容复制到dest中
List list2 = new ArrayList();
for (int i = 0; i < 10; i++) {
list2.add(i);
}
Collections.copy(list2, list); //list2的实际元素要比list多,不然会越界
System.out.println(list2);
//10、replaceAll(list, oldVal, newVal)将新值替换所有旧值
Collections.replaceAll(list,"tom","merry");
System.out.println(list);