第7节:Java基础 - 三大集合(下)
(1) TreeMap有哪些特征
答:TreeMap底层实现使用红黑树实现,TreeMap中存储的键值对按照键来排序。
-
如果Key存入的是字符串等类型,那么会按照字典序默认排序
-
如果传入的是自定义引用类型,比如说User,那么该对象必须实现Comparable接口,并且覆盖其compareTo,或者在创建TreeMap的时候,我们必须指定使用的比较器,
123456789101112131415161718// 方式一:定义该类的时候,就指定比较规则
class
User
implements
Comparable{
@Override
public
int
compareTo(Object o) {
// 在这里边定义其比较规则
return
0
;
}
}
public
static
void
main(String[] args) {
// 方式二:创建TreeMap的时候,可以指定比较规则
new
TreeMap<User, Integer>(
new
Comparator<User>() {
@Override
public
int
compare(User o1, User o2) {
// 在这里边定义其比较规则
return
0
;
}
});
}
关于TreeMap的考察,会涉及到两个接口Comparable和Comparator的比较。Comparable接口的后缀able大概表示可以的意思,也就是说一个类如果实现了这个接口,那么这个类就是可以比较的。类似还有cloneable接口表示可以克隆的。而Comparator则是一个比较器,是创建TreeMap的时候传入,用来指定比较规则。
Comparaable接口和Comparator接口的区别
-
Comparable实现比较简单,但是当需要重新定义比较规则的时候,必须修改代码,即修改User类里边的compareTo方法
-
Comparator接口不需要修改源代码,只需要在创建TreeMap的时候重新传入一个具有指定规则的比较器即可。
(2)ArrayList和LinkedList的区别
常用的ArrayList和LinkedList的区别如下:
-
ArrayList底层实现使用了动态数组实现,实质上是一个动态数组
-
LinkedList底层实现使用了双向链表实现,可当作堆栈,队列,双端队列使用
-
ArrayList在随机存取方面效率高于LinkedList
-
LinkedList在节点的增删方面效率高于ArrayList
-
ArrayList必须预留一定的空间,当空间不足的时候,会进行扩容操作
-
LinkedList的开销是必须存储节点的信息以及节点的指针信息
解析:
List集合也是我们平时使用很多的集合。List接口的长剑实现就算ArrayList和LinkedList,我们必须熟练掌握其底层实现以及一些特征。其实好友一个集合Vector,它是线程安全的ArrayList,但是已经被废弃,不推荐使用了。多线程环境下,我们可以使用CopyOnWriteArrayList替代ArrayList来保证线程安全。
(3)HashSet和TreeSet的区别
HashSet和TreeSet的区别:
-
HashSet底层实现使用了Hash表实现。(保证元素唯一性原理:判断元素的hashCode值是否相同。如果相同,还会继续判断元素的equals方法,是否为true)
-
TreeSet底层使用了红黑树来实现。(保证元素唯一性是通过Comparable和Comparator接口实现)
解析:
其实,HashSet的底层实现还是HashMap,只不过其使用了其中的Key,具体如下所示:
-
HashSet的add方法底层使用HashMap的put方法将key = e,value=PRESENT构建成key-value键值对,当此e存在于HashMap的key中,则value将会覆盖原有value,但是key保持不变,所以如果将一个已经存在的e元素添加到HashSet中,新添加的元素是不会保存到HashMap中,所以就满足了HashSet中元素不会重复的特性。
-
HashSet的contains方法使用HashMap的containsKey方法实现
(4)LinkedHashMap和LinkedHashSet有了解吗?
LinkedHashMap可以记录下元素的插入顺序和访问顺序,具体实现如下:
-
LinkedHashMap内部的Entry继承于HashMap.Node,这两个类都实现了Map.Entry<K,V>
-
LinkedHashMap的Entry不光有value,next,还有before和after属性,这样通过一个双向链表,保证了各个元素的插入顺序
-
通过构造方法public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder), accessOrder传入true可以实现LRU缓存算法(访问顺序)
-
LinkedHashSet 底层使用LinkedHashMap实现,两者的关系类似与HashMap和HashSet的关系,大家可以自行类比。
扩展: 什么是LRU算法?LinkedHashMap如何实现LRU算法?
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
由于LinkedHashMap可以记录下Map中元素的访问顺序,所以可以轻易的实现LRU算法。只需要将构造方法的accessOrder传入true,并且重写removeEldestEntry方法即可。具体实现参考
123456789101112131415161718192021222324252627282930313233package
pak2;
import
java.util.LinkedHashMap;
import
java.util.Map;
public
class
LRUTest {
private
static
int
size =
5
;
public
static
void
main(String[] args) {
Map<String, String> map =
new
LinkedHashMap<String, String>(size,
0
.75f,
true
) {
@Override
protected
boolean
removeEldestEntry(Map.Entry<String, String> eldest) {
return
size() > size;
}
};
map.put(
"1"
,
"1"
);
map.put(
"2"
,
"2"
);
map.put(
"3"
,
"3"
);
map.put(
"4"
,
"4"
);
map.put(
"5"
,
"5"
);
System.out.println(map.toString());
map.put(
"6"
,
"6"
);
System.out.println(map.toString());
map.get(
"3"
);
System.out.println(map.toString());
map.put(
"7"
,
"7"
);
System.out.println(map.toString());
map.get(
"5"
);
System.out.println(map.toString());
}
}
-
List是有序的并且元素是可以重复的
-
Set是无序(LinkedHashSet除外)的,并且元素是不可以重复的(此处的有序和无序是指放入顺序和取出顺序是否保持一致)
(6)Iterator和ListIterator的区别是什么?
-
Iterator可以遍历list和set集合;ListIterator只能用来遍历list集合
-
Iterator前者只能前向遍历集合;ListIterator可以前向和后向遍历集合
-
ListIterator其实就是实现了前者,并且增加了一些新的功能。
解析:
Iterator其实就是一个迭代器,在遍历集合的时候需要使用。Demo实现如下:
123456789ArrayList<String> list =
new
ArrayList<>();
list.add(
"zhangsan"
);
list.add(
"lisi"
);
list.add(
"yangwenqiang"
);
// 创建迭代器实现遍历集合
Iterator<String> iterator = list.iterator();
while
(iterator.hasNext()){
System.out.println(iterator.next());
}
1234567891011121314151617181920212223242526package
niuke;
import
java.util.ArrayList;
import
java.util.Arrays;
import
java.util.List;
public
class
ConverTest {
public
static
void
main(String[] args) {
// list集合转换成数组
ArrayList<String> list =
new
ArrayList<>();
list.add(
"zhangsan"
);
list.add(
"lisi"
);
list.add(
"yangwenqiang"
);
Object[] arr = list.toArray();
for
(
int
i =
0
; i < arr.length; i++) {
System.out.println(arr[i]);
}
System.out.println(
"---------------"
);
// 数组转换为list集合
String[] arr2 = {
"niuke"
,
"alibaba"
};
List<String> asList = Arrays.asList(arr2);
for
(
int
i =
0
; i < asList.size(); i++) {
System.out.println(asList.get(i));
}
}
关于数组和集合之间的转换是一个常用操作,这里主要讲解几个需要注意的地方吧。
数组转为集合List:
通过Arrays.asList方法搞定,转换之后不可以使用add/remove等修改集合的相关方法,因为该方法返回的其实是一个Arrays的内部私有的一个类ArrayList,该类继承于Abstractlist,并没有实现这些操作方法,调用将会直接抛出UnsupportOperationException异常。这种转换体现的是一种适配器模式,只是转换接口,本质上还是一个数组。
集合转换数组:
List.toArray方法搞定了集合转换成数组,这里最好传入一个类型一样的数组,大小就是list.size()。因为如果入参分配的数组空间不够大时,toArray方法内部将重新分配内存空间,并返回新数组地址;如果数组元素个数大于实际所需,下标为list.size()及其之后的数组元素将被置为null,其它数组元素保持原值。所以,建议该方法入参数组的大小与集合元素个数保持一致。
若是直接使用toArray无参方法,此方法返回值只能是Object[ ]类,若强转其它类型数组将出现ClassCastException错误。
(8)Collection和Collections有什么关系?
这是Java中的一类问题,类似的还有Array和Arrays,Executor和Executors有什么区别与联系? (待补)
附图:
集合的类图:
我们接着给出本节所涉及到的集合的类图结构:(C表示这是一个类,I表示这是一个接口)
-
-
-
Vector的类图结构:
-
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· ThreeJs-16智慧城市项目(重磅以及未来发展ai)
· .NET 原生驾驭 AI 新基建实战系列(一):向量数据库的应用与畅想
· Ai满嘴顺口溜,想考研?浪费我几个小时
· Browser-use 详细介绍&使用文档
· 软件产品开发中常见的10个问题及处理方法