Java集合笔记
Java集合类
概述:
在编程时,常常需要集中存放多个数据,在使用数组存放数据的话,因数组长度定义后就无法改变,也无法保存具有映射关系的的数据,所以数组就显得无能为力了,为了保存数量不确定的数据,以及保存具有映射关系的数据,Java提供了集合类,所有集合类都位于java.util包下,在java5之后还在java.util.concurrent包下提供了支持多线程的集合。
集合和数组
-
数组(存储一组元素最快的数据结构):
- 长度确定,一旦被创建,大小不可改变,下标区间[0,length)
- 同一数组元素必须相同,且有序,可以为任意类型,包括基本数据类型和引用数据类型(存储引用变量)
- 数组属于引用类型,数组也是对象
-
集合:
-
集合的顶级接口分为Collection接口和Map接口
-
Collection接口下面派生出Set接口、List接口、Queue接口
- Set下有HashSet、LinkedHashSet、TreeSet
- List下有ArrayList、Vector、LinkedList
- Queue下有ArrayDeque
-
Map下有HashTable、LinkedHashMap、HashMap、TreeMap
-
Collection接口
List接口:
特点:元素有序、可重复。集合中每个元素都有其对应的索引,默认按元素添加顺序设置索引。
-
ArrayList
优点:查询速度块。
缺点:增删改效率低。
底层数据结构:数组。
扩容机制:1.5倍扩容。
-
LinkedList
优点:增删改速度快。
缺点:查询效率低。
底层数据结构:双向链表。
扩容机制:链表不需要扩容。
Set接口:
特点:程序以可依次把多个对象丢进Set集合中,而Set通常无法记住添加顺序,若向Set中添加相同的元素,则add()会返回false。
-
HashSet
优点:HashSet按照Hash算法存储元素,因此具有很好的存取和查找性能。
底层数据结构:哈希表。
其它:
- 不能保证元素的排列顺序。
- 集合元素可以为null。
- 向HashSet添加元素时,HashSet会调用对象的HashCode,然后根据其HashCode来决定对象的位置。
- 如果两个对象通过equals()方法返回true,但HashCode返回值不想等,元素依然可以添加成功。
- HashSet判断两个对象相等的依据是equals()比较相等,hashCode()返回值相同。
- 如果equals()返回true,hashCode()返回false,元素会添加成功,但与set规则相冲突。
- 如果equals()返回false,hashCode()返回true,元素会添加成功,但在同一位置用链式结构存储,性能会下降。
-
LinkedHashSet
优点:可以保证数据的录入顺序。
底层数据结构:哈希表和链表。
其它:
- 当遍历LinkedHashSet集合里的元素时,会按元素的添加顺序访问集合元素。
- LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet。
-
TreeSet
优点:可保证元素是排序状态。
底层数据结构:红黑树。
其它:
- TreeSet通过自然排序和定制排序来保证元素的排序状态。
- 自然排序:当对象添加至TreeSet之后,TreeSet会调用对象的comparaTo方法来判断对象间的大小关系,并按升序排序。
- 问题:
- 当添加至TreeSet集合中的对象没有实现Comparable接口的情况下,第一个添加的元素不会报异常,第二个元素就会引发ClassCastException异常。(调用comparaTo方法对比时,无法对比,其它类似情况也会出现此异常)
- TreeSet判断对象是否相等的唯一条件是comparaTo方法是否返回为0,如果添加多次同一个重写了comparaTo方法,并一直返回非0的对象的话,添加也是可以成功的,但本质是同意个对象,修改任何一个数据,也会全部修改。
- 当修改了TreeSet集合中的元素后,TreeSet也不会重新进行排序。
- 总之:想要TreeSet正常运作的话,TreeSet只能添加同一中类型的对象。
- 问题:
- 定制排序:需要Comparator接口的帮助,由Comparator接口负责元素集合的排序逻辑。
因为每次添加数据都会进行排序处理,所以TreeSet相对于HashSet和LinkedHashSet要慢得多!
Map接口
- Map接口有三个比较重要的实现类,分别为HashMap、TreeMap、HashTable。
- Map用于保存具有映射关系的数据,因此Map集合中保存着两组值,一组key一组value,key和value都可以是任何引用类型的数据,key不允许重复。
- TreeMap是有序的,HashMap和HashTable是无序的。
- Hashtable是线程安全的,HashMap是线程不安全的。
- HashMap效率高,Hashtable效率低。(如果没有兼容性需求,建议使用HashMap)
- Hashtable不允许null值,HashMap允许null值(key、value都可以)
- 在java8中对于HashMap进行了优化,之前HashMap底层结构为数组加链表,当同一位置有两个对象时就会产生Hash碰撞,当Hash碰撞多了之后,会严重影响效率。在Java8之后,当链表长度大于8且Map元素大于64后,底层数据结构会变为数组加红黑树。根据对比来确定位置的情况少了很多,除了添加操作其余效率要高很多。
集合工具类
Iterator遍历
Iterator接口提供了以下方法操作集合
- hasNext():是否存在下一个
- next():返回集合下一个元素
- remove():删除上一个next返回的元素
- forEachRemaining():使用这和方法可用Lambda表达式遍历元素。
List<String> list = Arrays.asList(strings); //数组转换集合
Iterator<String> iterator = list.iterator(); //调用iterator
while (iterator.hasNext()){ //是否存在下一个元素(用于终止循环)
String next = iterator.next(); //拿出元素
System.out.println(next);
}
//--------------------------------------------------------------------
List<String> ls = Arrays.asList(strings); //数组转换集合
Iterator<String> iterator = ls.iterator(); //调用Iterator方法
iterator.forEachRemaining(s -> { //调用forEachRemaining使用Lambda表达式
System.out.println(s);
});
Lambda遍历
Iterable接口是Collection的父接口,为函数式接口,默认方法为forEach(),所以Collection子接口的List和Set都可以使用ForEach()方法+Lambda表达式。
List<String> ls = Arrays.asList(strings); //数组转换集合
ls.forEach(l->{ //Lambda表达式遍历l
System.out.println(l);
});
Stream流
Java8中的Stream流与inputStream、outputStream是完全不同的概念,stream适用于对集合迭代器的增强,使之能够更高效率的完成聚合操作(过滤、排序、统计分组)或大批量数据操作,stream流与lambda表达式结合后编码效率大大提高,可读性会更强。火箭代码----->火车代码。
集合讲的是数据,流讲的是计算
- 首先把数据源(数组、集合...)转换为流。
- 对流进行流水线式的中间操作。
- 操作后将产生全新的流,和原来的数据源没有任何关系。
创建Stream流
//创建Stream
public void test1(){
//1.通过Collection系列集合提供的stream()(串行流)或parallelStream()(并行流)
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
//2.通过Arrays中的静态方法stream获取数组流
String[] strings = new String[10];
Stream<String> stream = Arrays.stream(strings);
//3.通过Stream类中的静态方法of()方法
Stream<String> aa = Stream.of("aa", "bb", "cc");
//4.创建无限流
//迭代 //这是无限流
Stream<Integer> iterate = Stream.iterate(0, (x) -> x + 2);
//这是中间操作 //这是截止操作
iterate.limit(10).forEach(System.out::println);
//生成
Stream.generate(()->(Math.random()))
.limit(20)
.forEach(System.out::println);
}
中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何处理!而在终止操作时一次性全部处理,称为“惰性求值”。
Stream会对数据进行遍历操作,这是由Stream API完成的,称为内部迭代
筛选
-
filter:接收lambda,从流中排除某些元素。
public void test(){ //根据lambda表达式过滤 //中间操作 emps.stream().filter((e) -> e.getAge() > 30) //终止操作,没有终止操作,中间操作不会执行! .forEach(emp -> System.out.println(emp)); }
-
limit:截断流,使其中元素不超过给定数量。当找到给定次数的数据后,就不再执行。
public void test2(){ //使用limit(3)后会取出三个元素 emps.stream().limit(3).forEach((e)-> System.out.println(e)); }
-
skip:跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流。
public void test3(){ emps.stream().skip(2).forEach((e)-> System.out.println(e)); }
-
distinct:筛选去重,通过流所生成的hashCode()和equals()去除重复元素。
public void test4(){ emps.stream().distinct().forEach(e-> System.out.println(e)); }
映射
-
map:接收Lambda,将元素转换成其它形式或提取信息。接收一个函数作为参数,改函数会被应用到每个元素上,并映射成为一个新元素。
public void test5(){ emps.stream().map((emp) -> emp.getName()).forEach((emp)-> System.out.println(emp)); }
-
flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
排序
-
sorted():自然排序 按对象指定的Comparable方式排序
public void test6(){ List<String> list = Arrays.asList("ccc","bbb","sss","aaa"); list.stream().sorted().forEach(l-> System.out.println(l)); }
-
sorted(C c):定制排序 Comparator 按自己传递的新的方式去排
public void test8(){ emps.stream().sorted((e1,e2)->{ if (e1.getAge().equals(e2.getAge())){ return e1.getName().compareTo(e2.getName()); }else { return e1.getAge().compareTo(e2.getAge()); } }).forEach(e-> System.out.println(e)); }
终止
-
allMatch:检查是否匹配所有元素
public void test11(){ //allMatch是不是匹配所有元素(对象中Status()枚举中是否全为BUSY) boolean b = emps.stream().allMatch(emp -> emp.getStatus().equals(Emp.Status.BUSY)); System.out.println(b); }
-
anyMatch:检查是否有一个匹配元素
public void test12(){ boolean b = emps.stream().anyMatch(emp -> emp.getStatus().equals(Emp.Status.BUSY)); System.out.println(b); }
-
noneMatch:检查是否没有匹配所有元素
public void test13(){ boolean b = emps.stream().noneMatch(emp -> emp.getStatus().equals(Emp.Status.BUSY)); System.out.println(b); }
-
findFirst:返回第一个元素Optional<>(为了防止空指针异常,Stream用Optional封装了对象,get方法可取出对象)
public void test14(){ //findFirst返回了个Optional<Emp> Optional<Emp> b = emps.stream().sorted((e1,e2)->{ if (e1.getAge().equals(e2.getAge())){ return e1.getName().compareTo(e2.getName()); }else { return e1.getAge().compareTo(e2.getAge()); } }).findFirst(); Emp emp = b.get(); System.out.println(emp); }
-
findAny:返回当前流中任意元素
public void test15(){ //allMatch是不是匹配所有元素 //parallelStream多线程流stream单线程流 Optional<Emp> b = emps.parallelStream().findAny(); Emp emp = b.get(); System.out.println(emp); }
-
count:返回流中元素总数
public void test16(){ long b = emps.parallelStream().count(); System.out.println(b); }
-
max/min:返回流中最大/小值
public void test17(){ //自己决定根据什么选的最大/小值 Optional<Emp> max = emps.parallelStream().max((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())); System.out.println(max); }
Collections操作集合
排序
- reverse(List list):反转指定List集合中元素顺序。
- shuffle(List list):对指定List集合进行随机排序。
- sort():
- 形参传入List:根据元素的自然顺序对List集合进行升序排序。
- 形参传入List,Comparator:根据指定Comparator产生的顺序对List集合进行排序。
- Comparator:自定义顺序(lambda表达式)
- swap(List list,int i,int j):将指定List集合中的i处元素和j处元素交换。
- rotate(List,int distance):
- 当distance为正数时,将集合的后distance个元素整体移到前面。
- 当distance为负数时,将集合的前distance个元素整体移到后面。
List<Emp> emps = new ArrayList<>(); //对象集合
emps.add(new Emp(1,"小王",23));
emps.add(new Emp(2,"小张",24));
emps.add(new Emp(3,"小李",18));
emps.add(new Emp(4,"小孙",21));
List<Integer> num = new ArrayList<>();//数字集合
num.add(1);
num.add(0);
num.add(-1);
num.add(5);
//操作--------------------------------------------------------------------------------
//反转
Collections.reverse(emps);
//随机
Collections.shuffle(emps);
//自然排序(Integer)
Collections.sort(num);
//定制排序:年龄从大到小
Collections.sort(emps,(e1,e2)->{
//Integer提供了compare方法,可比按数字大小进行排序,默认升序,Integer前加-(负号)即降序
int compare = Integer.compare(e1.getAge(), e2.getAge());
return compare;
});
//交换集合中低三个和第四个元素的位置
Collections.swap(emps,2,3);
//将集合最后两个元素剪切至集合最前面
Collections.rotate(emps,2);
附:Integer提供了compare方法,可比按数字大小进行排序,默认升序,Integer前加-(负号)即降序