Java集合容器
1.为什么使用集合:
1)动态扩容机制;
2) 减少资源浪费;
3) 如果我们不知道程序运行时会需要多少对象,或者需要跟复杂的方式存储对象--可以使用Java集合框架;
2. 集合整体结构:
3.List与Set:
1) Collection接口存储一组不唯一、无序的对象;
2) List接口存储一组不唯一、有序的对象;
3) Set接口存储一组唯一、无序的对象;
4) Map接口存储一组键值对象,提供key映射到value的映射;
一:Collection接口:
1.本质:存放的是单一值,本质还是使用的是一个Object类型的数组;
2.特点:1)可以存放不同类型的数据,而数组只能存放固定类型的数据;
2)当使用arrayList子类实现的时候,初始化长度是10,当长度不够时会进行自动扩容操作();
3)扩容机制:原来旧的长度+旧长度的/2 新长度为旧长度的1.5倍;
4)常用API方法:
a)添加数据的方法:
add():要求必须传入的参数是Object对象类型,因此当写入基本类型时,包含自动拆箱和自动装箱的过程;
addAll():添加另一个集合的元素到此集合中;
b) 删除数据的方法:
clear(): 只是清空集合中的元素,但是此集合对象并没有被回收;
remove(): 删除集合中特定的元素;
removeAll(): 删除集合中集合元素;
c)查询数据的方法:
contains(): 判断集合中是否包含指定的元素;
containsAll():判断此集合中是否包含另一个集合;
retainAll(): 若集合中拥有另一个集合的所有元素,返回true,否则返回false
size(): 返回当前集合的大小
isEmpty(): 判断此集合是否为空;
d)转数组操作:
toArray(): 将集合装换为数组
代码实现:
package com.cy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
/**
* java集合框架:
* Collection:存放的是单一值,本质还是使用的是一个Object类型的数组;
* 特点:
* 1.可以存放不同类型的数据,而数组只能存放固定类型的数据;
* 2.当使用arrayList子类实现的时候,初始化长度是10,当长度不够时会进行自动扩容操作();
* 3.扩容机制:原来旧的长度+旧长度的/2 新长度为旧长度的1.5倍;
*
*
* API方法;
* 添加数据的方法:
* add():要求必须传入的参数是Object对象类型,因此当写入基本类型时,包含自动拆箱和自动装箱的过程;
* addAll():添加另一个集合的元素到此集合中;
* 删除数据的方法:
* clear(): 只是清空集合中的元素,但是此集合对象并没有被回收;
* remove(): 删除集合中特定的元素;
* removeAll(): 删除集合中集合元素;
* 查询数据的方法:
* contains(): 判断集合中是否包含指定的元素;
* containsAll():判断此集合中是否包含另一个集合;
* retainAll(): 若集合中拥有另一个集合的所有元素,返回true,否则返回false
* size(): 返回当前集合的大小
* isEmpty(): 判断此集合是否为空;
*
* 转数组操作:
* toArray(): 将集合装换为数组
*
*/
public class CollectionDemo {
public static void main(String[] args) {
Collection collection=new ArrayList();
//在集合中添加元素
collection.add(1);
collection.add(true);
collection.add(1.21);
collection.add("abc");
System.out.println(collection);
//集合中在特定下标添加元素
((ArrayList) collection).add(0,"老王");
System.out.println(collection);
Collection collection1=new ArrayList();
collection1.add("a");
collection1.add("b");
collection1.add("c");
collection1.add("d");
//添加另一个集合元素
collection.addAll(collection1);
//判断集合中是否包含指定的元素
System.out.println(collection.contains("a"));
//判断该集合中是否包含另一个集合
System.out.println( collection.containsAll(collection1));
//判断该集合是否为空
System.out.println(collection.isEmpty());
//判断删除某一个特点的元素
collection.remove("a");
//删除集合元素
collection.removeAll(collection1);
//判断一个集合是否包含另一个集合的所有元素
collection.retainAll(collection1);
//集合转数组
Object[] objects = collection.toArray();
//清空集合元素
collection.clear();
System.out.println(collection);
}
}
二:List接口:
特点:有序,不唯一(可以重复)
实现类一:ArrayList
1)ArrayList: 实现了长度可变的数组,在内存中分配连续的空间;
优点:遍历元素和随机访问元素的效率比较高;
缺点:添加、删除需要大量移动元素效率低,按照内容查询效率低;
实现类二:LinkedList
2)LinkdeList: 采用链表存储方式;
优点: 插入、删除元素时效率比较高;
缺点:遍历和随机访问元素效率低下;
常用API:
a) 添加数据的方法:
add():要求必须传入的参数是Object对象类型,因此当写入基本类型时,包含自动拆箱和自动装箱的过程;
addAll():添加另一个集合的元素到此集合中;
set():在集合中指定的位置添加指定的值;
b) 删除数据的方法:
clear(): 只是清空集合中的元素,但是此集合对象并没有被回收;
remove(): 删除集合中特定的元素;
removeAll(): 删除集合中集合元素;
c)查询数据的方法:
getIndex(): 获取指定的下标元素;
indexOf(): 返回指定元素的下标;(向后找)
lastIndexOf(): 返回指定元素的下表 (向前找)
contains(): 判断集合中是否包含指定的元素;
containsAll():判断此集合中是否包含另一个集合;
retainAll(): 若集合中拥有另一个集合的所有元素,返回true,否则返回false
size(): 返回当前集合的大小
isEmpty(): 判断此集合是否为空;
subList(): 截取集合中指定内容的集合;
b) 转数组操作:
toArray(): 将集合装换为数组
package com.cy;
import java.util.ArrayList;
import java.util.List;
/**
* java集合框架:
* List:存放的是单一值,本质还是使用的是一个Object类型的数组;
* 特点:
* 1.可以存放不同类型的数据,而数组只能存放固定类型的数据;
* 2.当使用arrayList子类实现的时候,初始化长度是10,当长度不够时会进行自动扩容操作();
* 3.扩容机制:原来旧的长度+旧长度的/2 新长度为旧长度的1.5倍;
*
*
* API方法;
* 添加数据的方法:
* add():要求必须传入的参数是Object对象类型,因此当写入基本类型时,包含自动拆箱和自动装箱的过程;
* addAll():添加另一个集合的元素到此集合中;
* set():在集合中指定的位置添加指定的值;
* 删除数据的方法:
* clear(): 只是清空集合中的元素,但是此集合对象并没有被回收;
* remove(): 删除集合中特定的元素;
* removeAll(): 删除集合中集合元素;
* 查询数据的方法:
* getIndex(): 获取指定的下标元素;
* contains(): 判断集合中是否包含指定的元素;
* containsAll():判断此集合中是否包含另一个集合;
* retainAll(): 若集合中拥有另一个集合的所有元素,返回true,否则返回false
* size(): 返回当前集合的大小
* isEmpty(): 判断此集合是否为空;
* indexOf(): 返回指定元素的下标;(向后找)
* lastIndexOf(): 返回指定元素的下表 (向前找)
* subList(): 截取集合中指定内容的集合;
* 转数组操作:
* toArray(): 将集合装换为数组
*
*/
public class ListDemo {
public static void main(String[] args) {
List list = new ArrayList();
list.add("a");
list.add(1);
list.add("a");
list.add(true);
System.out.println(list);
//获取指定下标元素
System.out.println(list.get(0));
//返回当前指定元素的下标-->向前找
System.out.println(list.indexOf("a"));
//返回当前指定元素的下标-->向后找
System.out.println(list.lastIndexOf("a"));
//在当前集合中插入指定元素
list.set(0,"老王");
System.out.println(list);
//list中的截取方法
List list1 = list.subList(0, 3);
System.out.println(list1);
}
}
package com.cy;
import java.util.LinkedList;
/**
* linkList拥有更加丰富的方法实现,需要用时查询API即可,不需要记忆;
*/
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.add(123);
linkedList.add(false);
linkedList.add("abc");
System.out.println(linkedList);
//在指定位置添加节点元素
linkedList.add(0,"老王");
//在该集合中首个位置添加节点元素
linkedList.addFirst("1111");
//在该集合中最后一个位置添加节点元素
linkedList.addLast("2222");
System.out.println(linkedList);
//获取集合中第一个元素、
System.out.println(linkedList.element());
//最后一个节点添加元素
linkedList.offer("3333");
}
}
Vector:
1) Vector也是List接口的一个子类实现
2) Vector跟ArrayList一样,底层都是使用数据进行实现的
3) Vector跟ArrayList区别:
a)ArrayList是线程不安全的,效率高;Vector是线程安全的,效率低;
b)ArrayList在进行扩容时,是扩容1.5,Vector扩容时扩容原来的2倍;
package com.cy;
import java.util.Vector;
/**
* 1.Vector也是List接口的一个子类实现
* 2.Vector跟ArrayList一样,底层都是使用数据进行实现的
* 3.Vector跟ArrayList区别:
* 1)ArrayList是线程不安全的,效率高;Vector是线程安全的,效率低;
* 2)ArrayList在进行扩容时,是扩容1.5,Vector扩容时扩容原来的2倍;
*/
public class VectorDemo {
public static void main(String[] args) {
Vector vector = new Vector();
vector.add("abc");
vector.add(1);
vector.add(false);
System.out.println(vector);
}
}
Iteractor(迭代器):
Java代码中包含三种循环的方式 : 1)do-while; 2) while; 3)for;
还有一种增强for循环的方式,可以简化循环的编写;
所有集合都默认实现了Iterable接口,实现此接口意味着具备了增强for循环的能力,也就是for-each;
增强for循环本质上也是使用的也是iterator的功能;
方法: iterator(); foreach();
在iterator的方法中,要求返回一个Iterator的接口子类实例对象
此接口也包含了
hasNext(); 判断下一个是否有值;
next(); 返回下一个值;
实现原理:
注意:在使用iterator进行迭代的过程中如果删除其中某个元素会报错,并发操作异常,因此如果遍历的同时需要修改元素,建议使用listIterator();
ListIterator迭代器提供了向前向后两种遍历的方式: 始终是通过cursor和lastret的指针来获取元素值及向下的遍历索引;
当使用向前遍历的时候必须要保证指针在迭代器的结果,否则无法获取结果;
三:Set接口:
1.特点: 1)set中存放的是无序、唯一的数据;
2)由于set无序,所以set不可以通过对应位置的元素值;
3) 使用treeset底层实现是treemap,利用红黑树进行实现;
2.实现类:HashSet、TreeSet;
HashSet:
1)采用Hashtable哈希表存储结构;本质:底层实现是HashMap;
优点:添加、查询、删除速度快;
缺点:无序;
原理:
2) LinkedHashSet:
采用哈希表存储结构,同时使用链表维护次序;
有序;
TreeSet:
采用二叉树(红黑树)的存储结构;
优点:有序(排序后升序)查询速度比list快;
缺点:查询速度没有HashSet快;
扩展红黑树:
代码实现:
package com.cy;
import com.entity.Person;
import java.util.*;
/**
* 1.set中存放的是无序、唯一的数据;
* 2.由于set无序,所以set不可以通过对应位置的元素值;
* 3.使用treeset底层实现是treemap,利用红黑树进行实现;
* 4.设置元素的时候,如果是自定义对象,会查找对象中equal和hashcode的方法,如果没有,比较的就是地址值;
* 5.树中的元素是要默认进行排序操作的,如果是基本数据类型,自动比较,如果是引用类型的话,需要自定义比较器;
* 比较器:
* 内部比较器:
* 定义在元素的类中,通过实现comparable接口来进行实现;
* 外部比较器:
* 定义在当前类中,通过实现comparator接口来实现,但是要将该比较器传递到集合中;
* 注意:外部比较器可以定义成一个工具类,此时所有需要比较规则如果一致的话,可以直接复用;
* 内部比较器只有在存储当前对象的时候才可以使用;
* 如果两者同时存在,使用外部比较器(当使用比较器的时候不会调用equals)
*/
public class SetDemo implements Comparator<Person> {
public static void main(String[] args) {
Set set=new HashSet();
set.add("abc");
set.add(123);
set.add("true");
set.add("123");
System.out.println(set);
//迭代器
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("_______________");
//将while循环改写成for循环,推荐使用
for (Iterator iter=set.iterator(); iter.hasNext();){
System.out.println(iter.next());
}
TreeSet treeSet = new TreeSet();
treeSet.add(34);
treeSet.add(1);
treeSet.add(65);
System.out.println( treeSet.ceiling(0));
System.out.println(treeSet);
TreeSet treeSet1 = new TreeSet(new SetDemo());
treeSet1.add(new Person("zhangsan",14));
treeSet1.add(new Person("lisi",13));
treeSet1.add(new Person("wangwu",15));
treeSet1.add(new Person("zhouliu",16));
System.out.println(treeSet1);
}
/**
* 根据年龄进行排序
* @param o1
* @param o2
* @return
*/
@Override
public int compare(Person o1, Person o2) {
if (o1.getAge()> o2.getAge()){
return 1;
}else if(o1.getAge() < o2.getAge()){
return -1;
}else{
return 0;
}
}
}
package com.entity;
import java.util.Objects;
public class Person implements Comparable{
private String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name) && Objects.equals(age, person.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
/**
* 次比较器按照name长度比较
* @param o
* @return
*/
@Override
public int compareTo(Object o) {
Person person=(Person) o;
if (person.name.length()> this.name.length()){
return 1;
}else if (person.name.length()< this.name.length()){
return -1;
}else {
return 0;
}
}
}
问题: HashSet是如何保证元素的唯一性?
通过元素的两个方法,hashCode和equals方法来完成,如果元素的HashCode值相同,才会判断equals方法是否true;如果元素的HashCode值不同,不会调用equals方法;
四:Map接口:
1. 特点:key-value映射;
2.实现子类:
1)HashMap: 数组+链表(1.7) 数组+链表+红黑叔(1.8)
2) LinkedHashMap: 链表
3) TreeMap: 红黑树
3. 常用API:
添加:
put(K,v) 添加元素
查找:
isEmpty() 判断是否为空
size() 返回map的大小
containKay() 判断是否包含指定的key
cintainValue() 判断是否包含指定的Value值
get() 通过Key获取指定元素
keySet() 获取所有的key值
删除:
clear() 清空结合中的所有元素;
remove() 删除指定元素;
Map.entry:表示K-V组合的一组映射关系,key和value成组出现;
代码实现:
public class MapDemo {
public static void main(String[] args) {
Map<String,Integer> map=new HashMap<>();
//向map集合中添加数据
map.put("a",1);
map.put("b",2);
map.put("c",3);
map.put("d",4);
System.out.println(map);
//判断当前map是否为空
System.out.println(map.isEmpty());
//返回当前集合的大小
System.out.println(map.size());
//判断是否包含指定的key
System.out.println(map.containsKey("a"));
//判断是否包含指定的Value值
System.out.println(map.containsValue(2));
//通过Key获取指定元素
System.out.println(map.get("a"));
//根据key删除指定元素
System.out.println(map.get("a"));
//清空所有元素
//map.clear();
//遍历操作
//获取所有的Key
Set<String> keys = map.keySet();
for (String key:keys) {
System.out.print("Key:"+key+",Value:"+map.get(key)+" ");
}
System.out.println();
//获取所有的Value值(但只能获取Value,不能获取key)
Collection<Integer> values = map.values();
for (Integer value: values) {
System.out.print(value);
}
System.out.println();
//迭代器
//通过迭代key,来获取值;
Set<String> keySet = map.keySet();
Iterator<String> iterator = keySet.iterator();
while (iterator.hasNext()){
String key = iterator.next();
System.out.print(key+"="+map.get(key));
}
System.out.println();
//map.entry()
Set<Map.Entry<String, Integer>> entries = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator1 = entries.iterator();
while (iterator1.hasNext()){
Map.Entry<String, Integer> next = iterator1.next();
System.out.print(next.getKey()+"=="+next.getValue());
}
}
}
常用类一:HashMap:
1)特点:key无序,唯一(set)
value 无序 不唯一(Collection)
2)hashMap和hashTable的区别:
a)hashMap线程不安全,效率比较高,hashTable线程线程安全,效率低;
b)hashMap中key和Value都可以为空,hashTable不允许为空;
c) hashMap的初始长度为16,hashTable的初始长度为11;(加载因子均为0.75)
3) hashMap初始值为2的N次幂:
方便进行 &运算操作,提高效率,且&运算要比取模运算效率要高;
位置计算:hash(hash值) & (initCapacity-1) 哈希值 & (数组初始长度-1);
4) hashMap扩容机制:
1)数组初始长度默认值为16,当填入的数长度大于数组长度*加载因子0.75的长度时,那么新数组长度=原数组长度*2;
2)在扩容之后涉及到元素迁移过程,迁移的时候只需要判断二进制的前一位是0或者是1,如果是0表示新数组和旧数组的下标位置不变,如果是1,只需将索引位置加上旧数组的长度值即为新数组的下标
5)JDK1.7--HashMap.put()执行流程:
6) JDK1.8HashMap.put()执行流程:
链表使用头插法插入值:
7) JDK1.8以后引入红黑树,当阈值(链表长度)大于8,采用红黑树算法
LinkedHashMap: 链表
--- 有序的HashMap 速度快;
TreeMap:红黑树
----有序 速度没有Hash快;
问题:Set与Map有关系吗?
采用了相同的数据结构,只用于map的key存储数据结构是map;
五:Collections工具类
1.Collections和Collection区别:前者是集合操作类,后者是集合接口;
2.常用API:
1)添加:
addAll():批量加入元素的方法;
2)排序:
sort(List<E>,Comparator<E>): 按自定义条件比较;
sort(List<E>): 默认按字母顺序排序;
3)查询:
binarySearch(List<E>): 二分查找
注意:二分查找时需要先进行排序操作,如果没有排序时无发找到指定元素;
4)批量替换:
fil(): 把原来所有元素填充为指定元素;
5)数组和集合之间的转化:
数组转集合:asList(Array arr);
集合转数组:toArray();
3.代码实现:
public static void main(String[] args) {
List<String> list=new ArrayList<>();
list.add("af");
list.add("bg");
list.add("cssf");
list.add("dfsghkj");
//批量加入元素
Collections.addAll(list,"edkl","fdihcco","grho");
System.out.println(list);
//自定义比较器
Collections.sort(list,new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
if (o1.length()>o2.length()){
return 1;
}else if (o1.length()<o2.length()){
return -1;
}else {
return 0;
}
}
});
System.out.println(list);
//默认按字母顺序排序
Collections.sort(list);
System.out.println(list);
//二分查找
System.out.println(Collections.binarySearch(list,"af"));
//批量替换
Collections.fill(list,"老王");
System.out.println(list);
//数组转集合
int[] arr=new int[]{1,2,3,4,5};
List<int[]> ints = Arrays.asList(arr);
//集合转数组
Object[] objects = ints.toArray();
}
}
六:总结
1.集合数据结构总结:
2.集合特性总结:
3.面试题总结:
1)集合与数组的区别:
a) 存放对象不同:数组可以存放基本数据类型和对象,集合只可以存放对象;
b)是否支持动态扩容:数组一但创建不能改变,无法动态扩容,集合可以动态扩容;
c) 数据结构不同:数组仅采用了顺序表达方式,而集合采用了顺序表、链表、哈希表、二叉树等数据据结构;
d) 是否知道元素的个数; 数组采用length()来判断,只能告诉数组的容量,而集合size()可以确切知道元素的个数;
2) ArrayList和LinkedList的联系和区别:
a) ArrayList实现了长度可变的数组,在内存中分配连续空间。遍历元素和随机访问元素效率比较高。
b) LinkedList采用链表存储方式。插入、删除元素效率比较高
3) Vector和ArrayList的联系和区别:
联系:实现原理相同,功能相同,都是长度可变的数组结构,很多时候可以互用
区别:
▪ Vector是早期的JDK接口,ArrayList是替代Vector的新接口
▪ Vector线程安全,ArrayList重速度轻安全,线程非安全
▪ 长度需要增长时,Vector默认增长一倍,ArrayList增长50% (1.5+1)
4)HashMap和Hashtable的联系和区别
联系:实现原理相同,功能相同,底层都是哈希表结构,查询速度快,在很多情况下可以互用
区别:
▪ Hashtable是早期的JDK提供的接口,HashMap是新版的JDK提供的接口
▪ Hashtable继承Dictionary类,HashMap实现Map接口
▪ Hashtable是线程安全,HashMap线程非安全
▪ Hashtable不允许null值,HashMap允许null值