Java学习记录9——集合

10 篇文章 0 订阅

一、集合类

基本介绍

集合是一种容器,与数组类似,不同的是集合的大小是不固定的,可以动态保存任意多个对象

通过创建集合的对象表示得到一个集合容器,同时集合提供了比数组更好用、更丰富的API功能来操作对象,如add、remove、set、get等,添加删除元素简洁

集合的框架体系

集合主要分为两组:

  • 单列集合:单个存储元素——Collection接口
  • 双列集合:存储键值对——Map接口

Collection接口有两个重要的子接口List和Set,它们的实现都是单列集合
在这里插入图片描述

Map接口的实现子类是双列集合,存放对应的Key-Value
在这里插入图片描述
总框架:
在这里插入图片描述

二、Collection接口

1.Collection接口实现类的特点

  1. Collection的实现子类可以存放多个元素,每个元素可以是Object
  2. 有些Collection的实现类可以存放重复元素,有些不可以
  3. Collection的实现类有的有序(List接口),有的无序(Set接口)
  4. Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的

2.Collection接口的常用方法

方法名描述
add添加单个元素
remove删除指定元素
contains查找元素是否存在
size获取元素个数
isEmpty判断是否为空
clear清空
addAll添加多个元素
removeAll删除多个元素
containsAll查找多个元素是否存在

PS:clear()方法比removeAll()方法更高效
如果元素满足特定条件,我们也可以使用removeIf()方法将满足条件的删除

以ArrayList为例

List list = new ArrayList();
list.add("jack");
list.add(10);//自动装箱,相当于new Integer(10)
list.add(true);//自动装箱Boolean
list.remove(0);//按索引删除
list.remove(true);//指定删除元素
list.contains("jack"); //返回false
list.size();//1
List list1 = new ArrayList();
list1.addAll(list);//传入集合,把list中的元素添加到list1
//removeAll、containsAll也需要传入集合

3.Collection接口遍历元素的方式

1)使用Iterator(迭代器)

迭代器接口介绍:

  1. Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
  2. 所有实现了Collection接口的集合类都有一个iterator()方法,用于返回一个实现了Iterator接口的对象(迭代器)
  3. Iterator仅用于遍历集合,本身不存放对象

迭代器的执行原理:
得到一个集合的迭代器,hasNext()方法:判断是否还有下一个元素,next()方法:游标后移,并将后移之后对应的集合位置上的元素返回

快捷键:itit可快速生成如下while循环

Iterator iterator = coll.iterator();//得到一个集合的迭代器
while (iterator.hasNext()) {//有元素则继续遍历
	Object obj = iterator.next();
	System.out.println(obj);
}//循环结束时指向最后的元素
//再次遍历需要重置迭代器
iterator = coll.iterator();

PS:调用iterator.next()方法之前必须要调用iterator.hasNext()进行检测。
若不检测而下一条记录无效,直接调用iterator.next()会抛出NoSuchElementException异常

2)for循环增强

增强for循环就是简化版的iterator,本质一样,底层还是迭代器,只能用于遍历集合或数组

for(Object obj : col) { //此处要求为Object类型
}

三、List接口

1.基本介绍

List接口是Collection接口的子接口

  1. List集合中元素有序(添加和取出顺序一致)且可重复
  2. List集合中的每个元素都有其对应的顺序索引
  3. List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可根据序号存取容器中的元素
  4. List常见实现类:ArrayList、LinkedList、Vector

2.List接口的常用方法

在这里插入图片描述

ArrayList.remove(Integer.valueOf(1));	//删除值为1的元素

3.List的三种遍历方式

  1. 使用Iterator(迭代器)
  2. 使用增强for循环
  3. 使用普通for循环

1.ArrayList

从jdk1.2开始使用

允许任何元素(包括空元素),ArrayList由数组实现数据存储。
ArrayList基本等同于Vector,除了ArrayList线程不安全,执行效率高。多线程情况下不建议用ArrayList

底层操作机制源码分析

  1. ArrayList中维护了一个Object类型的数组elementData.transient Object[] elementData//transient意为瞬间,表示该序列不会被序列化
  2. 当创建ArrayList对象时,如果使用无参构造器,则初始elementDate容量为0,第一次添加扩容为10,如需再次扩容,则扩容为elementData的1.5倍
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  3. 如果使用指定大小的构造器,则初始elementData容量为指定大小,如需扩容,则直接扩容为elementData的1.5倍在这里插入图片描述

2.Vector

从jdk1.0开始使用

Vector底层也是一个对象数组protected Object[] elementData
Vector是线程同步的,线程安全,效率比ArrayList低

底层操作机制

  • 当创建Vector对象时,如果使用无参构造器,则默认elementDate容量为10,如需再次扩容,则扩容为elementData的2倍
//new Vector()底层
public Vector() {
	this(10);//本质上还是调用有参构造
}

如果使用指定大小的构造器,则初始elementData容量为指定大小,如需扩容,则直接扩容为elementData的2倍

//new Vector(int)底层
public Vector(int initialCapacity) {
	this(initialCapacity,0);
}

3.LinkedList

LinkList底层实现了双向链表(jdk1.6之前为循环链表,jdk1.7取消了循环)和双端队列
可以添加任意元素(可重复),包括null,线程不安全,未实现线程同步

底层操作机制

在这里插入图片描述

  1. LinkList底层维护了一个双向链表
  2. 维护了两个属性firstlast分别指向首尾节点
  3. 每个节点(Node对象)中维护了prev、next、item三个属性
  4. LinkList元素的添加和删除通过双向链表实现,效率相对较高
LinkedList list = new LinkedList();
//底层:public LinkedList() {},此时first、last=null

add添加的底层源码:
l,e,null对应节点的prev,item,next
在这里插入图片描述

4.List集合的使用选择

多线程时使用Vector,ArrayList和LinkedList线程不安全,效率相对高
在这里插入图片描述
在这里插入图片描述

四、Set接口

1.基本介绍

无序添加和取出顺序并不一样,但每次取出的顺序是固定的),无索引,不允许重复元素,最多包含一个null
Set接口常用实现类:HashSet、TreeSet、LinkedHashSet

常用方法和遍历方式同Collection接口,但没有索引,不能用索引方式通过普通for循环获取

1.HashSet

  1. HashSet底层实际上是HashMap(HashMap底层是数组+链表+红黑树)
  2. HashSet不保证元素有序,取决于hash后,再确定索引的结果(添加和取出顺序并不一样,但每次取出的顺序是固定的
  3. 不允许重复元素(本质是地址相同)
HashSet hashSet = new HashSet();
String a = "java";
String b = "java";
hashSet.add(a);//添加成功
hashSet.add(b);//添加失败,因为已有"java"
hashSet.add(new String("java"));//添加失败
hashSet.add(new Cat("Tom"));
hashSet.add(new Cat("Tom"));//不同对象,可以添加
System.out.println(hashSet);//1个java,两个Tom

原理:

  1. 添加一个元素时,先得到hash值,转换成索引值
    找到存储数据表table,看这个索引位置是否已经有存放元素
  2. 如果没有,直接加入;如果有,调用equals比较(equals可以自己重写比较标准),相同就放弃添加,不同则添加到该索引的最后(以链表形式添加,如果该索引位置已经是链表,就使用for循环和链表所有元素比较)
  3. Java8中,如果一条链表的元素个数>=TREEIFY_THRESHOLD(默认为8),且table大小>=MIN_TREEIFY_CAPACITY(默认为64),就会进行树化(红黑树)
  4. 在把元素添加到链表后,立即判断该链表是否已达到8个节点,达到就调用treeifyBin(),判断若table大小<64,则先对table扩容,若>=64,则将当前链表转成红黑树

扩容机制:
table就是HashMap的一个数组,类型是Node[ ],若当前table为null或大小=0,就是第一次扩容,得到16个空间,临界值为160.75=12,0.75为加载因子(loadfactor)。若table使用到了临界值12(指其中元素数量达到临界值threshold,占用数组或链表的都算),就会扩容到162=32,新临界值为24

易错问题:
在这里插入图片描述

2.LinkedHashSet

LinkedHashSet 是 HashSet 的子类,底层是一个LinkedHashMap(HashMap的子类),维护了一个数组+双向链表
底层存储的数组是HashMap$Node [],存放的数据元素是LinkedHashMap$Entry类型(多态数组),因为LinkedHashMap的Entry中有可实现双向链表的before和after属性
在这里插入图片描述

LinkedHashSet根据元素的hashCode值来确定元素的存储位置,同时使用双向链表来维护元素的次序,这使得元素看起来是以插入顺序保存的

3.TreeSet

TreeSet存储数据有有序,不重复,底层为红黑树(自平衡的排序二叉树)
当使用无参构造器创建TreeSet时,元素无序
使用TreeSet提供的有参构造器,可传入一个比较器(匿名内部类),并指定排序规则。

构造器把传入的比较器对象(实现了Comparator接口的匿名内部类对象)赋给了TreeSet底层的TreeMap的属性comparator

TreeSet treeSet = new TreeSet(new Comparator() {
	@Override
	public int compare(Object o1, Object o2) {
		//调用String的compareTo方法比较字符串大小
		return ((String) o1).compareTo((String) o2);//从小到大
		//return ((String) o2).compareTo((String) o1);//从大到小
		//按长度大小排序
		return ((String) o1).length()-((String) o2).length();
	}
});
TreeSet treeSet = new TreeSet(new Comparator() {
	@Override
	public int compare(Object o1, Object o2) {
		//按长度大小排序(会认为长度相等就是相同)
		return ((String) o1).length()-((String) o2).length();
	}
});
treeSet.add("tom");
treeSet.add("curry");
treeSet.add("abc");//添加失败,因为长度与"tom"相同

PS:
传入了Comparator匿名对象,使用实现的方法compare去重,如果方法返回0,就认为是相同的元素,不添加
若未传入Comparator匿名对象,则以所添加的对象实现的Comparable接口的compareTo去重

TreeSet若没有传入Comparator匿名对象,就需要把传入的元素转为Comparable类型,这就要求传入数据的类型必须实现Comparable接口

TreeSet treeSet = new TreeSet();
treeSet.add(new Person());//ClassCastException异常

class Person {
}
//实现Comparable接口即可
class Person implements Comparable {
	@Override
	public int compareTo(Object o) {
		return 0;//始终返回0,则只能添加一个
	}
}

常用方法:

TreeSet<Integer> s = new TreeSet<Integer>();
//使用s.add(1);等把1-5都加进去,代码省略
System.out.println(s.ceiling(3));	//>=3的最小的数,输出3
System.out.println(s.floor(3));		//<=3的最大的数,输出3
System.out.println(s.higher(3));	//>3的最小的数,输出4
System.out.println(s.lower(3));		//<3的最大的数,输出2
System.out.println(s.headSet(3));	//<3的数组成的TreeSet,输出[1, 2]
System.out.println(s.tailSet(3));	//>=3的数组成的TreeSet,输出[3, 4, 5]
System.out.println(s.subSet(2,4));	//>=2且<4的数组成的TreeSet,输出[2, 3]
System.out.println(s.subSet(2,false,4,true));	//>2且<=4的数组成的TreeSet,输出[3, 4]

五、Map接口

1.Map基本介绍

  1. Map与Collection并列存在,用于保存有映射关系的数据Key—Value
  2. Map中key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
  3. Map中key 是无序的、不可重复的(同HashSet),value 是无序的、可重复的
  4. 如果用已用过的key存放value,那么这个新value会替换原有的value
  5. Map中key可为null,只能有一个;value也可为null,可以有多个
  6. 常用String类作为Map的key
  7. key和value之间存在单向的一对一关系,通过key找对应的value
  8. 一对key—value放在一个HashMap N o d e 中, ‘ H a s h M a p Node中, `HashMap Node中,HashMapNode node = newNode(hash,key,value,null);`
    Node实现了Entry接口。为方便遍历,还会创建EntrySet集合,定义存放Entry类型,Entry对象(实际上就是Node对象)中有k,v指向newNode中的key和value。
    将Node放到EntrySet方便遍历是因为Map.Entry提供了方法getKey()和getvalue()
    在这里插入图片描述
    遍历Node:
HashMap map = new HashMap();
map.add("k1","1");
map.add("k2","2");
Set set = map.entrySet();
for (Object obj : set) {//遍历
	Map.Entry entry = (Map.Entry) obj;//向下转型
	System.out.println(entry.getKey()+"-"+entry.getValue());
}

2.Map常用方法

方法名描述
put添加,若已有对应值则替换
get根据键获取值
remove根据键删除映射关系
containsKey查找键是否存在
size获取元素个数
isEmpty判断是否为空
clear清空
keySet获取所有的键
entrySet获取所有的关系
values获取所有的值

3.Map的遍历方式

  1. 先取出map所有的key,再通过key取出对应的value
    Set keyset = map.keySet();
    以Set类型获取所有键,可使用Set的两种遍历方式:
    增强for循环、迭代器

  2. 把所有的value取出
    Collection values = map.values();
    以Collection类型获取所有键,可使用Collection的两种遍历方式:增强for循环、迭代器

3.通过EntrySet来获取k-v
增强for循环、迭代器

Set entrySet = map.entrySet();
//增强for循环
for (Object entry : entrySet) {
	//将entry转成Map.Entry
	Map.Entry m = (Map.Entry) entry;
	System.out.println(m.getKey()+"-"+m.getValue());
}
//迭代器
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
	Object entry =  iterator.next();//entry为HashMap$Node类型,实现了Map.Entry
    Map.Entry m = (Map.Entry) entry;
    System.out.println(m.getKey()+"-"+m.getValue());        
}

六、Map实现类

1.HashMap

1)基本介绍
HashMap是Map接口使用频率最高的实现类,底层为数组+链表+红黑树。
HashMap没有实现同步,线程不安全

2)底层原理
(k,v)是一个Node,实现了Map.Entry<k,v>
jdk7.0的HashMap底层是数组+链表,jdk8.0底层是数组+链表+红黑树
在这里插入图片描述
3)扩容机制
在这里插入图片描述

2.LinkedHashMap

继承自 HashMap,所以它的底层仍然是数组和链表或红黑树组成。
在HashMap结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑

3.HashTable

1)基本介绍

  1. 存放元素是键值对k-v
  2. HashTable的键和值都不能为null,否则抛出NullPointerException
  3. HashTable使用方法基本与HashMap一样
  4. HashTable线程安全,效率相对较低

2)底层原理(数组+链表)

  1. HashTable底层有数组 Hashtable$Entry[]初始化大小为11
  2. 临界值threshold=11*0.75=8(向上取整)

3)扩容
执行方法 addEntry(hash,key,value,index); 添加k-v,封装到Entry。

元素数大于临界值时扩容,每次扩容为原容量的2倍+1,如11->23

4)与HashMap对比
在这里插入图片描述

4.Properties

Properties类继承自Hashtable类并实现了Map接口,使用键值对形式保存数据,key和value都不能为null。使用特点与Hashtable类似

Properties还可用于从.properties文件中,加载数据到Properties类对象,并进行读取和修改(.properties文件通常作为配置文件)

5.TreeMap

与TreeSet类似
使用默认构造器创建TreeMap对象,存储的键值对无序
使用有参构造器,可传入一个比较器(匿名内部类),并指定排序规则。

构造器把传入的比较器对象(实现了Comparator接口的匿名内部类对象)赋给了TreeMap的属性comparator

TreeMap treeMap = new TreeMap(new Comparator() {
	@Override
	public int compare(Object o1, Object o2) {
		//按长度大小排序(会认为长度相等就是相同)
		return ((String) o2).length()-((String) o1).length();
	}
});
treeSet.add("tom","TOM");
treeSet.add("curry","CURRY");
treeSet.add("abc","ABC");//添加失败,因为长度与"tom"相同,不会替换

七、集合类的选择

主要取决于操作特点,根据集合实现类的特性选择
1、先判断存储的类型:一组对象(单列)或 一组键值对(双列)
2、一组对象(单列):Collection接口

  1. 允许重复:List
  • 增删多:LinkedList(底层双向链表)
  • 改查多:ArrayList(底层Object类型的可变数组)
  1. 不允许重复:Set
  • 无序:HashSet(底层HashMap,维护了一个哈希表)
  • 排序:TreeSet
  • 插入和取出顺序一致:LinkedHashSet(底层LinkedHashMap,维护了数组+双向链表)

3、一组键值对(双列):Map

  • 键无序:HashMap(底层哈希表(数组+链表+红黑树))
  • 键排序:TreeMap
  • 键插入和取出顺序一致:LinkedHashMap(底层HashMap)
  • 读取文件:Properties

八、Collections工具类

1.基本介绍

Collections 是一个操作Set、List和Map等集合的工具类
Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,用Collections.方法名调用

2.排序操作

  1. reverse(List):反转List中元素的顺序
  2. shuffle(List):对List集合元素进行随机排序
  3. sort(List):根据元素的自然顺序对指定的List集合元素按升序排序
  4. sort(List,comparator):根据指定的Comparator产生的顺序对List集合元素进行排序
  5. swap(List, int i, int j):将指定List集合中的 i 处元素和 j 处元素进行交换

3.查找、替换

  1. Object max(Collection):根据元素的自然顺序,返回给定集合中的最大值
  2. Object max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大值
  3. Object min(Collection):最小值
  4. Object min(Collection,Comparator)
  5. int frequency(Collection,Object):返回指定集合中指定元素出现的次数
  6. void copy(List dest,List src):将src中的内容复制到dest中(dest空间大小需>=src)
  7. boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List对象的所有旧值

九、集合转换为数组

1.集合转为对象数组

使用toArray()方法即可,不传入参数只能转换为Object类型,不能强转;传入参数转换为对应类型:

ArrayList<Integer> list = new ArrayList<>();
Object[] objects = list.toArray();
Integer[] integers = list.toArray(new Integer[0]);

2.集合转为基本类型数组

toArray()方法不能将集合直接转成基本数据类型的数组,需要使用流式编程和lambda表达式:

ArrayList<Integer> list = new ArrayList<>();
int[] ints = list.stream().mapToInt(x -> x).toArray();

stream()方法用于获取流对象,用mapToInt拿到int流对象,再使用toArray返回int数组即可。
x->x是lambda表达式,对应一个函数式接口,因为集合中泛型为Interger类型,可自动拆箱,所以直接返回x就行
如果这里式String泛型要转为int数组的话,就要调用Integer.parseInt()方法,lambda写法:mapToInt(Integer::parseInt)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
文件上传是Web开发中常见的功能之一,Java中也提供了多种方式来实现文件上传。其中,一种常用的方式是通过Apache的commons-fileupload组件来实现文件上传。 以下是实现文件上传的步骤: 1.在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency> ``` 2.在前端页面中添加文件上传表单: ```html <form method="post" enctype="multipart/form-data" action="upload"> <input type="file" name="file"> <input type="submit" value="Upload"> </form> ``` 3.在后台Java代码中处理上传文件: ```java // 创建一个DiskFileItemFactory对象,用于解析上传的文件 DiskFileItemFactory factory = new DiskFileItemFactory(); // 设置缓冲区大小,如果上传的文件大于缓冲区大小,则先将文件保存到临时文件中,再进行处理 factory.setSizeThreshold(1024 * 1024); // 创建一个ServletFileUpload对象,用于解析上传的文件 ServletFileUpload upload = new ServletFileUpload(factory); // 设置上传文件的大小限制,这里设置为10MB upload.setFileSizeMax(10 * 1024 * 1024); // 解析上传的文件,得到一个FileItem的List集合 List<FileItem> items = upload.parseRequest(request); // 遍历FileItem的List集合,处理上传的文件 for (FileItem item : items) { // 判断当前FileItem是否为上传的文件 if (!item.isFormField()) { // 获取上传文件的文件名 String fileName = item.getName(); // 创建一个File对象,用于保存上传的文件 File file = new File("D:/uploads/" + fileName); // 将上传的文件保存到指定的目录中 item.write(file); } } ``` 以上代码中,首先创建了一个DiskFileItemFactory对象,用于解析上传的文件。然后设置了缓冲区大小和上传文件的大小限制。接着创建一个ServletFileUpload对象,用于解析上传的文件。最后遍历FileItem的List集合,判断当前FileItem是否为上传的文件,如果是,则获取文件名,创建一个File对象,将上传的文件保存到指定的目录中。 4.文件上传完成后,可以给用户一个提示信息,例如: ```java response.getWriter().write("File uploaded successfully!"); ``` 以上就是使用Apache的commons-fileupload组件实现文件上传的步骤。需要注意的是,文件上传可能会带来安全隐患,因此在处理上传的文件时,需要进行严格的校验和过滤。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值