🎉博客首页:一心向上的文浩
欢迎关注🥳点赞 👍 收藏 ⭐留言 📝 欢迎讨论!
❤:热爱Java学习,期待一起交流!
🙏🏻作者水平有限,如果发现错误,求告知,多谢!
🥰有问题可以私信交流!!!
1.什么是java中的集合?
在处理数据的过程中我们经常会使用一个容器来存储某一类型的数据,在java中,数组便是一种容器,但很多时候我们不确定数据的长度,或者是数组对数据的操作并不方便,因此便有了另一种较数组来说更为方便的容器–集合
下面让我们来一起学习关于集合的知识吧
2. java常见集合体系图
首先,让我们来看一下java中常见集合的体系图
由上面两幅图片可以看出,java集合有Collection(单列集合)和Map(双列集合)
- Collection下有List和Set两个接口
- List下有ArrayList,Vector和LinkenList
- Set下有TreeSet和HashSet
- Map的重要实现子类有
- Hashtable,HashMap,TreeMap
- Properties是Hashtable的子类,LinkedHashMap是HashMap的子类并且直接实现了Map
3.Collection接口
🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉
3.1 List–有序可重复
🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰
List接口常用方法
下面用代码来解释List接口的常用方法
package com.lwhxx.List;
import java.util.ArrayList;
import java.util.List;
public class List_ {
@SuppressWarnings("all")
public static void main(String[] args) {
//List集合类中元素有序(即添加顺序和取出顺序一致),且可以重复
List list = new ArrayList();
list.add("老大");
list.add("老三");
//方法1:void add(int index,Object ele) 在index位置插入ele元素
list.add(1, "老二");//在索引为1的位置插入"老二"
System.out.println("list=" + list);//输出: list=[老大, 老二, 老三]
//方法2:从index位置插入所有元素
List list2 = new ArrayList();
list2.add("老四");
list2.add("老五");
list.addAll(3, list2); //将list2全部插入到list的索引为3的位置
System.out.println("list=" + list);//输出: list=[老大, 老二, 老三, 老四, 老五]
//方法3:对象名.indexOf 返回集合中首先出现的索引位置
System.out.println(list.indexOf("老二")); //输出: 1
//4.对象名.lastIndexOf返回集合中最后的元素的索引
list.add(5, "老二"); //先在索引为5的位置再加一个"老二"
System.out.println(list.lastIndexOf("老二")); //输出:5 因为老二最后出现在索引为5
//方法5:获取索引位置的元素
System.out.println(list.get(2));//获取索引为2的元素,输出:老三
//方法6:remove删除索引所指的位置的元素
list.remove(0); //将老大删除
System.out.println("list=" + list);//输出结果:list=[老二, 老三, 老四, 老五, 老二]
//7.set 指定index位置的元素,相当于替换
list.set(1, "强子");
System.out.println("list=" + list); //输出:list=[老二, 强子, 老四, 老五, 老二]
//8.subList 返回从fromIndex到toIndex位置的子集合 前闭后开
//返回的子集合 fromIndex<=list1<toInderx
List list1 = list.subList(0, 2); //重新创建对象了
System.out.println("list1=" + list1); //输出:list1=[老二, 强子]
}
}
List接口三种遍历方法
说完了List接口的常用方法,那么我们该如何遍历List呢
这里有三种常用的遍历方法
我们先以ArrayList为例来演示这三种方法
List list = new ArrayList();
list.add("小黄");
list.add("小李");
list.add("小红");
🎉🎉🎉🎉🎉🎉🎉🎉🎉方式一:使用迭代器
//方式一:使用迭代器遍历
Iterator iterator = list.iterator();
while (iterator.hasNext()) { //这里用的快捷键: itit
Object obj = iterator.next();
System.out.println(obj);
}
🎉🎉🎉🎉🎉🎉🎉🎉🎉方式二:使用增强for循环
//方式二:使用增强for循环
for (Object o :list) { //快捷键: I
System.out.println(o);
}
🎉🎉🎉🎉🎉🎉🎉🎉🎉方式三:使用普通for循环
//方式三:普通for循环
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
ArrayList
- 本质上,ArrayList是对象引用的一个”变长”数组
- 当创建ArrayList对象时,如果使用的是无参构造器,则初始化数组容量为0,第一次添加数组容量扩容为10,如需再次扩容,则扩容数组为原来容量的1.5倍
- 如果使用的是指定大小的构造器,则初始数组容量为指定大小,如果需要扩容,则直接把数组容量扩大为原来容量的1.5倍
- 增删效率较高,因为涉及数组扩容;改查效率较高
Vector
- Vector底层也是一个对象数组
- Vector是线程同步的,即线程安全,在开发时需要线程同步安全时考虑使用Vector
- Vector扩容机制:如果是无参,默认10,满后按两倍扩容;如果指定大小,则每次扩容直接按两倍扩容
LinkedList
- LinkedList底层实现了双向链表和双端队列特点
- LinkedList可以添加任意元素(元素可以重复),包括null
- LinkedList线程不安全,没有实现同步
- LinkedList的元素的删除和添加不是通过数组完成的,因此不涉及数组扩容,所以效率比较高;修改和查询效率较低
- LinkedList中维护了两个属性first和last分别指向首节点和尾节点
- 每个节点(node对象),里面又维护了prev,next,item三个属性,通过prev指向前一个节点,通过next指向后一个节点,最终实现双向链表
🎉🎉🎉🎉那么我们如何使用LinkedList呢?,首先我们应该定义一个Node类,Node表示双向链表得一个结点,Node下面有item,pre,next三个属性
//定义一个Node类,Node对象,表示双向链表的一个结点
class Node{
public Object item; //真正存放数据
public Node next; //指向后一个结点
public Node pre; //指向前一个结点
public Node(Object item) {
this.item = item;
}
@Override
public String toString() {
return "Node name="+item;
}
}
🎉🎉🎉🎉定义完Node类后,让我们来创建一个简单的双向链表吧
//模拟一个简单的双向链表
Node jack = new Node("jack");
Node rose = new Node("罗斯");
Node li = new Node("文浩");
//连接三个结点,形成双向链表
jack.next=rose;
rose.next=li;
li.pre=rose;
rose.pre=jack;
Node first=jack;//让first引用指向jack,就是双向链表的头结点
Node last=li; //让last引用指向li,就是双向链表的尾结点
🎉🎉🎉🎉那么我们如何遍历这个双向链表呢?
很简单!!!
因为LinkedList实现了List接口,所以使用三种常规的方法遍历也可以(迭代器遍历,增强for循环遍历,普通for循环遍历) 在这里我再介绍一种特殊的LinkedLIst的遍历方法:
//演示:从头到尾进行遍历
System.out.println("===演示从头到尾遍历===");
while(true){
if(first==null){
break;
}
//输出first信息
System.out.println(first);
first=first.next;
}
//演示:从尾到头的遍历
System.out.println("===演示从尾到头遍历===");
while(true){
if(last==null){
break;
}
//输出last信息
System.out.println(last);
last=last.pre;
}
运行结果如下:
🎉🎉🎉🎉那么我们该如何在链表中添加和删除对象呢?
很简单,通过改变对象之间的指向即可实现,下面我们来以添加为例演示一下将smith添加到罗斯和文浩之间吧
注意添加对象之后如果要再次遍历需要将first指向第一个人
//下面就将smith加入到了双向链表
rose.next=smith;
smith.next=li;
li.pre=smith;
smith.pre=rose;
//让first再次指向第一个人
first=jack;
while(true){
if(first==null){
break;
}
//输出first信息
System.out.println(first);
first=first.next;
}
结果如下:(smith果真被添加到了罗斯和文浩中间)
3.2 Set–无序,唯一
Set接口常用方法
和List接口一样,Set接口也是Collection的子接口,因此常用方法和Collection接口一样
Set接口的两种遍历方式
- 可以使用迭代器
- 可以使用增强for循环
- 不能使用索引的方式来获取,也就是不能使用普通for循环,因为Set是无序的
HashSet
- HashSet底层是HashMap,HashMap底层是数组+链表+红黑树
对HashSet进行调试可以轻松看出它的源码底层就是HashMap
public HashSet() { //HashSet的底层就是HashMap
map = new HashMap<>();
}
- 可以存放null值,但是只能有一个null
- HashSet不保证元素是有序的,取决于hash后,再确定索引的结果(即不能保证元素的顺序和取出顺序一致)
- HashSet不能有重复对象/元素,在前面Set接口已经讲过
🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰
面试官问烂了的HashMap/HashSet底层
🥰1.HashSet底层是HashMap
🥰2.添加一个元素时,先得到hash值,会转换成索引值
🥰3.找到存储数据表table,看这个索引位置是否已经存放的有元素
🥰4.如果没有,直接插入
🥰5.调用equals方法比较(equals比较的是内容还是其他信息是由程序员控制的,比如字符串比较的就是内容,因为字符串已经把equals方法重写了),如果相同,就放弃添加,如果不相同,则添加到最后
🥰6.在java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
HashSet的扩容机制:
- HashSet的底层是HashMap,第一次添加时,table数组扩容到16,临界值是16加载因子 0.7516=12,意思是数组内元素到12个的时候就开始扩容了
- 如果table数组使用到了临界值12,就扩容到162=32,新的临界值就变成了320.75=24,以此类推.
- 在java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则依然采用数组扩容机制,(如果数组table为16就变成32)
LinkedHashSet
首先来看一下LinkedHashSet的类结构图
- LinkedHashSet是HashSet的子类
- LinkedHashSet底层是一个LinkedHashMap,底层维护了一个hash表+双向链表
- 每个节点都有before和after属性
- LInkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素次序,这使得元素看起来是以插入顺序保存的
- LinkedHashSet不允许添加重复元素
TreeSet(排序)
1.当我们使用TreeSet无参构造器时,使用TreeSet仍然是无序的,TreeSet底层是TreeMap
2.我们使用TreeSet进行排序时的做法是:使用TreeSet提供一个构造器,传入一个比较器(匿名内部类)并指定排序规则
如下就是一个按照字符串大小的比较方式(比字母顺序,相同时比较下一个)
TreeSet treeSet = new TreeSet(new Comparator(){
@Override
public int compare(Object o1, Object o2) {
//下面 调用String的compare方法进行字符串大小比较
return ((String) o1).compareTo((String) o2);
}
});
再来介绍一个按照字符串长度来比较的:
TreeMap treeSet1 = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//按照(String)的长度大小进行排序
return ((String)o2).length()-((String)o2).length();
}
});
注意如果这种方式在遇到第二个和已经添加过的长度相同的元素时,就不能添加进去,详情看源码
例如
treeSet1.add("a");//正常添加
treeSet1.add("ab");//正常添加
treeSet1.add("b");//无法添加,因为已经有长度为1的元素了
4.Map接口–双列元素
- Map用于保存具有映射关系的数据: key-value (HashMap$Node类型)
- 常用实现类:HashMap,Hashtable,Properties
- Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
- Map中的key不允许重复,当key再次出现时就相当于把第一次的key替换了
- Map中的value可以重复
- Map的key可以为null,value也可以为null,注意key为null只能有一个,value为null可以多个,因为key值不能重复
- 常用String类作为Map的key
- key和value之间存在单向一对一的关系,即通过key总能找到相对应的value
Map接口常用方法
- put: 添加
- remove: 根据键删除映射关系
- get: 根据键获取值
- size: 获取元素个数
- isEmpty: 判断个数是否为0
- clear: 清空所有元素,慎用
- containsKey: 查找键是否存在
Map接口的六种遍历方式(注意理解EntrySet)
public class MapFor {
public static void main(String[] args) {
Map map = new HashMap();
map.put("邓超","孙俪"); //替换
map.put("王宝强","马蓉");//ok
map.put("宋喆","马蓉");//ok
map.put("李文浩",null);//ok
map.put(null,"刘亦菲");
map.put("鹿晗","关晓彤");
//第一组:先取出所有的key,通过key去除对应的Value
Set keyset = map.keySet(); //keyset()方法: 获取所有的键
//(1)增强for
System.out.println("-----第一种方式-----");
for (Object key :keyset) {
System.out.println(key+"-"+map.get(key));
}
//(2)迭代器
System.out.println("-----第二种方式-----");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key+"-"+map.get(key));
}
//第二组:把所有的values取出
Collection values = map.values();
//这里可以使用所有Collection使用的遍历方法
//(1)增强for循环
System.out.println("-----第三种取出所有的value 增强for-----");
for (Object value :values) {
System.out.println(value);
}
//(2)使用迭代器
System.out.println("-----第四种取出所有的value 使用迭代器-----");
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
Object value = iterator1.next();
System.out.println(value);
}
//第三组:通过EntrySet 来获取key-value
Set entrySet = map.entrySet(); //EntrySet<Map.Entry<k,v>>
//(1)使用增强for
System.out.println("-----第五种使用entrySet的增强for-----");
/*
//1.k-v最后是HashMap$Node node=newNode(hash,key,value,null)
//2.k-v为了方便程序员的遍历,还会创建EntrySet集合,该集合存放的元素类型是Entry,而一个Entry
// 对象就有k,v EntrySet<EntrySet<k,v>>
//3.entrySet中,定义的类型是Map.Entry, 但实际上存放的还是HashMap$Node
//4.当把 HashMap$Node对象存放到entrySet就方便我们的遍历,因为Map.Entry提供了重要方法,getKey()和getValue()
*/
for (Object entry :entrySet) {
//将entry转成Map.Entry 为了从Node中取出key和value
//先做一个向下转型
Map.Entry m=(Map.Entry)entry;
System.out.println(m.getKey()+"-"+m.getValue());
}
//(2)迭代器
System.out.println("-----第六种使用EntrySet的 迭代器");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next();
// System.out.println(next.getClass()); //HashMap$Node -实现->Map.Entry(getKey,getValue0
//向下转型 Map.Entry
Map.Entry m=(Map.Entry) entry;
System.out.println(m.getKey()+"-"+m.getValue());
}
}
}
HashMap
在前面介绍HashSet的时候就已经说过,HashSet的底层就是HashMap,并且在介绍HashSet时就对HashMap的源码等底层东西讨论过了,这里就不再赘述
HashTable
- 存放的元素是键值对,即k-v
- HashTable的键和值都不能为空
- HashTable使用方法基本上和HashMap一样
- HashTable线程安全(synchronized),HashMap线程不安全
- 相较于HashMap,HashTable的效率比较低
Properties
- Properties类继承HashTable类并实现了Map接口,也是用键值对保存数据
- 使用特点与HashTable相似,key和value不能为空,有相同的key也会被替换
- Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改
- 工作后 xxx.properties文件通常作为配置文件,这个知识点在IO流举例
TreeMap
因为TreeSet的底层是TreeMap,在前面已经对TreeSet介绍较为详尽,这里就简单介绍一下(下面列出两种常用的比较方法)
1.按照传入的k(String)的大小进行比较 :比较字母顺序,相同比下一个,具体看源码
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//按照传入的k(String)的大小进行比较
return ((String)o1).compareTo((String)o2);
}
});
2.按照k(String)的长度大小进行排序
TreeMap treeMap1 = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//按照k(String)的长度大小进行排序
return ((String)o2).length()-((String)o2).length();
}
});
注意:元素key长度相同时第二次出现的就不能被添加进去
treeMap1.put("jack","杰克");//添加成功,长度为4
treeMap1.put("tom","汤姆");//添加成功,长度为3
treeMap1.put("kristina","克里斯提洛");//添加成功,长度为8
treeMap1.put("smith","斯密斯");//添加成功,长度为5
treeMap1.put("rose","罗斯");//添加失败,长度为4已经存在了
5.Collections工具类
介绍
- Collections是一个操作Set,List和Map等集合的工具类
- Collections中提供了一系列静态的方法对集合元素进行排序,查询和修改等操作
常用工具类一:排序操作(均为static操作)
- reverse(List):反转 List中的元素顺序
- shuffle(List):对List集合元素进行随机排序
- sort(List): 根据元素的自然顺序指定List集合元素按升序排序//字符串首字母从小到大排序
- sort(List,Comparator): 根据指定的Comparator产生的顺序对List集合元素进行排序
- swap(List,int,int):指定将list集合中i处和j处元素进行交换
代码演示:
public class Collections_ {
public static void main(String[] args) {
List list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
//1.reverse(List):反转 List中的元素顺序
System.out.println("list="+list);
Collections.reverse(list);
System.out.println("list="+list);
//2.shuffle(List):对List集合元素进行随机排序
Collections.shuffle(list);
System.out.println("list="+list);
//3.sort(List): 根据元素的自然顺序指定List集合元素按升序排序//字符串首字母从小到大排序
Collections.sort(list);
System.out.println("自然排序后list="+list);
//4.sort(List,Comparator): 根据指定的Comparator产生的顺序对List集合元素进行排序
//例如按照字符串长度排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length()-((String)o2).length();
}
});
System.out.println("字符串长度大小排序="+list);
//5.swap(List,int,int):指定将list集合中i处和j处元素进行交换
Collections.swap(list,0,1);
System.out.println("交换后的顺序list="+list);
}
}
运行结果:
常用工具类二:查找,替换
- Collections.max(list):根据元素的自然顺序,返回集合中的最大元素
- Collections.max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大
- Collections.min(Collection)
- Collections.min(Collection,Comparator) //这两个是取最小的就不演示了参考max
- int frequency(Collection,Object):返回指定集合中指定元素的出现次数
- void copy(List dest,List src) :将src中的内容复制到dest中
- Collections.replaceAll(list,“tom”,“汤姆”); 使用新值替换List对象所有旧值
代码如下
//Collections.max(list):根据元素的自然顺序,返回集合中的最大元素
System.out.println("自然顺序的最大元素="+Collections.max(list));
//元素Collections.max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大
//比如我们要返回长度最大的元素
Object maxObject=Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length()-((String)o2).length();
}
});
System.out.println("长度最大的元素="+maxObject);
//Collections.min(Collection)
//Collections.min(Collection,Comparator) //这两个是取最小的就不演示了参考max
//int frequency(Collection,Object):返回指定集合中指定元素的出现次数
System.out.println("tom出现次数="+Collections.frequency(list,"tom"));
//void copy(List dest,List src) :将src中的内容复制到dest中
ArrayList dest = new ArrayList();
//为了完成完整的拷贝,我们需要先给dest赋一些值,大小和lise.size()一样
for (int i = 0; i < list.size(); i++) {
dest.add("");
}
//拷贝
Collections.copy(dest,list);
System.out.println("dest="+dest);
//Collections.replaceAll(list,"tom","汤姆"); 使用新值替换List对象所有旧值
//如果list中有tom就换成汤姆
Collections.replaceAll(list,"tom","汤姆");
System.out.println("list替换后="+list);
}
结尾
好了关于集合的知识点本人所学大概就这些了,文章较长将近1.3w字,码字不易还希望大家能多多支持我这个菜鸡,谢谢大家,我们下次再见