java-learn(9):常见算法,集合框架
常见算法
collection
常用方法:
Collection<String> cl = new ArrayList<>();
//boolean add()
cl.add("abc");
cl.add("bbb");
cl.add("ccc");
System.out.println(cl);
cl.remove("abc");
System.out.println(cl);
//size()
System.out.println(cl.size());
boolean result = cl.contains("abc");
System.out.println(result);
boolean result1 = cl.isEmpty();
System.out.println(result1);
cl.clear();
System.out.println(cl);
三种遍历的方式
迭代器
迭代器的细节:
1.如果当前位置没有元素,还要进行获取,会报NoSuchElementException
2.迭代器遍历完毕,指针不会复位
3.循环中只能用一次next方法
4.迭代器遍历时,不能使用集合的方法进行添加或者删除元素(如果实在要删除,只能使用迭代器的删除方法,添加还没有办法)
//获取迭代器对象
Iterator<String> it = cl.iterator();
//it.hasNext() 判断当前位置是否有元素
while(it.hasNext()){
//得到当前位置的元素
String str = it.next();
System.out.println(str);
}
增强for循环
//语法
for(元素的数据类型 变量名:数组或者集合){
}
// 修改增强for中的变量,不会改变集合中原本的数据
//但是如果是可变对象 修改对象的属性间接修改对象的内容
lambda表达式遍历
coll.add("abc");
coll.add("bcd");
coll.add("fedg");
//匿名内部类
coll.forEach(new Consumer<String>(){
@Override
public void accept(String s){
System.out.println(s);
}
});
//lambda表达式
coll.forEach(s->System.out.println(s));
三种表达式的使用场景:
当需要遍历删除元素时,可以使用迭代器的方式,当只需要遍历时,可以使用增加for循环,lambda表达式
List集合

list.add(1);
list.add(2);
list.add(3);
System.out.println(list);
//当方法重载时,优先调用实参更形参类型完全一致的那个方法
list.remove(1);
Integer i = Integer.valueOf(1);
list.remove(i);
System.out.println(list);
五中遍历方法对比:
迭代器遍历:删除元素
列表迭代器:需要添加元素,修改元素,删除元素
普通for遍历:需要使用到索引
增强for循环,lambda表达式遍历:只需要遍历,书写方式简单
//列表迭代器
ListIterator<Integer> it1 = list.listIterator();
while(it1.hasNext()){
Integer num = it1.next();
if(num == 1){
//迭代器添加元素是添加到当前位置的后一个位置,并指向添加后的那个元素
it1.add(100);
}else if(num == 10){
//修改元素
it1.set(1000);
}
if(num == 100){
//删除元素
it1.remove();
System.out.println("遍历到了");
}
}
数据结构
ArrayList集合
在创建一个ArrayList的对象时,底层初始化的容量为0,当添加第一个元素的时候会进行扩容为10
在后面 如果容量不够会自动扩容为当前容量的1.5倍
但是还有就是可能会用到addAll去添加一个集合中的所有元素,这个时候就可能扩容1.5倍之后还不够,这个时候就会扩容为当前需要的容量
LinkedList集合
相当于双向连链表
泛型深入
在jdk5之前是没有泛型的,所有任意数据类型都可以存入到集合中,当遍历集合时,只能用object接受,但是这个不能使用子类特有的方法。在推出泛型之后,在指定的存入的数据类型之后就只能存入那种数据类型,避免了强制类型转化可能出现的异常
在指定泛型的具体类型之后,可以传入他对应的子类或者实现类的类型
没有指定泛型的时候,默认是object类型
泛型中不能写基本数据类型
泛型类:
语法:
访问权限 class 类名<泛型>{}
如果不知道类型 可以默认设置一个为E
泛型方法:
修饰符<类型>返回值类型 方法名(类型 变量名){}
例子:
public static<T> boolean addAll(ArrayList<T> list){};
泛型接口:
写法和泛型类一样
泛型接口的使用:
1.实现类给出具体的类型
2.实现类延续泛型,创建对象时再确定泛型


package Mycollection;
import java.util.ArrayList;
public class demo7 {
public static void main(String[] args) {
//泛型不能继承,但是数据可以继承
ArrayList<Ye> list = new ArrayList<>();
Fu f = new Fu();
list.add(f);
method(list);
}
//?也表示不确定的类型
// 在不确定需要传入的数据类型时,可以使用泛型类,泛型接口,泛型方法
// 如果类型不确定,但是知道具体的继承体系,就可与使用泛型的通配符
// 通配符: 可以限定类型的范围 如果是? super E的话 就能传入当前类型以及他的父类
// 如果是? extends E的话,只能传入当前类或者他的子类
public static void method(ArrayList<? super Ye> t){
}
}
class Ye{
}
class Fu extends Ye{
}
class Zi extends Fu{
}
set系列集合

Set<String> myset = new HashSet<>();
myset.add("abcd");
myset.add("bbb");
myset.add("ccc");
myset.add("ooo");
System.out.println(myset.add("acbd"));
System.out.println(myset.add("acbd"));
// 增强for循环遍历
for(String s : myset){
System.out.println(s);
}
// contains
System.out.println(myset.contains("acbd"));
// remove
System.out.println(myset.remove("acbd"));
System.out.println(myset.size());
System.out.println(myset);
System.out.println(myset);
// 迭代器的遍历
Iterator<String> it = myset.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
//lambda表达式
myset.forEach(new Consumer<String>(){
@Override
public void accept(String s){
System.out.println(s);
}
});
//简略版
myset.forEach(s-> System.out.println(s));
myset.forEach(System.out::println);
hashset
数组+链表+红黑树
hashSet底层原理:
在jdk8之后,当链表长度大于八,而且数组长度等于64时,自动转化为红黑树
如果集合中储存的是自定义对象,必须重写hashCode和equals方法,重写hashCode是为了用对象的属性值去进行计算哈希值,重写equals是为了用对象的属性值进行比较。
linkedhashset


TreeSet
TreeSet的特点:可以排序,无索引,不重复
如果是String,Integer默认是从小到大排序
如果是自定义类型 需要重写compareTo方法
第一种比较方式
@Override
public int compareTo(Student o) {
//o代表的是已经存入到红黑树中的元素
//结果返回负数代表需要存储在o的左边
//为正数代表需要存入到o的右边
//为0的话就代表有相同的对象,就不会存入到红黑树中
return this.getAge() - o.getAge();
}
第二种比较方式
//比较器排序
TreeSet<String> t = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int temp = o1.length() - o2.length();
return temp == 0 ? o1.compareTo(o2) : temp;
}
});
t.add("a");
t.add("bc");
t.add("ff");
t.add("jfjf");
System.out.println(t);

综合案例,使用场景

双列集合的特点
常用API:
Map<Integer,String> m = new HashMap<>();
//添加元素
m.put(1,"张三");
m.put(2,"李四");
//获取元素
System.out.println(m.get(1));
//删除元素
m.remove(2);
//查看是否包含这个键
System.out.println(m.containsKey(1));
//查看是否包含这个值
System.out.println(m.containsValue("张三"));
//获取元素个数
System.out.println(m.size());
//判断是否为空
System.out.println(m.isEmpty());
//获取所有的键
System.out.println(m.keySet());
//获取所有的值
System.out.println(m.values());
//获取所有的键值对
System.out.println(m.entrySet());
//清空集合
m.clear();
遍历方式:
// 通过键找值
Set<Integer> keys = map.keySet();
for(Integer key : keys){
String value = map.get(key);
System.out.println(key + ":" + value);
}
System.out.println("------------------");
//通过键值对
for(Map.Entry<Integer,String> entry : map.entrySet()){
System.out.println(entry.getKey()+":"+entry.getValue());
}
System.out.println("------------------");
//通过Lambda表达式
map.forEach(new BiConsumer<Integer,String>() {
@Override
public void accept(Integer integer, String s) {
System.out.println(integer + ":" + s);
}
});
System.out.println("------------------");
map.forEach((k,v)-> System.out.println(k + ":" + v));
System.out.println("------------------");
Set<Map.Entry<Integer,String>> entryset = map.entrySet();
// 增加for循环
for(Map.Entry<Integer,String> entry:entryset){
System.out.println(entry.getKey()+":"+entry.getValue());
}
//迭代器
Iterator<Map.Entry<Integer, String>> it = entryset.iterator();
while(it.hasNext()){
Map.Entry<Integer,String> entry = it.next();
System.out.println(entry.getKey()+":"+entry.getValue());
}
//lambda表达式
Iterator<Map.Entry<Integer,String>> it2 = entryset.iterator();
while(it2.hasNext()){
Map.Entry<Integer,String> entry = it2.next();
System.out.println(entry.getKey()+":"+entry.getValue());
}
HashMap
hasmap底层是哈希表结构的
依赖hashcode方法和equals方法保证键的唯一
如果键储存的是自定义对象,需要重写hashcode和equals方法
如果值储存自定义对象,不需要重写
为什么需要重写hashcode和equals方法呢?
如果没有重写,就会自动调用object的hashcode和equals方法,但是在object中,是通过地址值计算哈希值的和比较两个对象是否相同的,所以如果想要通过属性值来计算哈希值或者通过比较属性值是否相同来确定两个对象是否相同,就需要重写。
如果用object的hashcode方法就会导致属性值相同的对象哈希值不同,就不会发生哈希冲突,如果要put元素的话,就不会发生覆盖现象,这样的话就会导致很多元素的重复,就违背了map系列的特点。
用object的equals方法也是同理。
LinkedHashMap
每个节点会存他上一个节点的地址值
特点:有序,无索引,无重复值
TreeMap
如果是Integer,String,Character这中类系统默认是升序排序,如果是自定义对象,就需要定义比较方法
有两种自定义比较方式:
1.在类中实现Comparable接口,并实现comparaTo方法
2.在创建对象时,传入Comparator比较器,使用匿名内部类的形式重写里面的compare方法
当第一种方法不能满足我们的需求时,我们可以使用第二种方法,就比如Integer原码默认是按照数字从小到大排序,如果需要改变排序规则就需要用第二种,因为我们不能改变源码。
当两种排序规则够实现的时候,最终只会采取比较器的排序规则。
源码解析
TreeMap源码分析
1.treeMap:红黑树
2.底层是利用红黑树来实现的,如果创建对象的时候,如果没有传入比较器,就会把key强转为comparable,然后调用重写的compareTo方法,如果传入了比较器,就用比较器的比较规则去比较。
HashMap源码分析
1.hashmap:数组+链表+红黑树
2.hashmap在创建对象时,数组初始化为0,在添加第一个元素时,默认初始化数组为16,扩容因子为0.75,当容量不够时,会自动扩容为当前的两倍。
3.当元素的通过哈希值计算出下标一样时,会发生哈希碰撞,如果元素key值相同就会覆盖掉当前数组中的value,并放回被替换的value,当key不相同时,就会通过遍历链表和下面的元素相比较,如果都不相同,就挂在最下面,如果这个链表的元素超过了8个,就会转化为红黑树。
可变参数
作用:可以传入任意多个相同类型的参数
格式:数据类型…变量名
细节:一个方法中只能有一个可变参数。
如果一个方法中定义了可变参数,那么这个可变参数必须放在最后面。
collections
集合的工具类
综合练习(集合的嵌套,斗地主)
不可变集合
不可变集合的特点:
定义之后不能修改或者添加,删除
创建List不可变集合
List<String> list = List.of("zhangsan","lisi","wangwu","zhaoliu");
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
创建Set不可变集合
Set<String> set = Set.of("zhangsan","lisi","wangwu","zhaoliu");
for(String s : set){
System.out.println(s);
}
细节:如果元素重复了会报错
创建Map不可变集合
Map <String,Integer> map = Map.of("zhangsan", 18, "lisi", 19, "wangwu", 20);
for(Map.Entry<String,Integer> entry : map.entrySet()){
System.out.println(entry.getKey() + ":" + entry.getValue());
}
细节:
传入参数时,必须传入偶数个参数,第奇数个为键第偶数个为值
如果用Map.of()最多创建10对键值对,超过10对需要使用ofEntries方法
Map<String,Integer> map1 = new HashMap<>();
map1.put("zhangsan", 18);
map1.put("lisi", 19);
map1.put("wangwu", 20);
Map<String,Integer> map2 = Map.ofEntries(map1.entrySet().toArray(new Map.Entry[0]));
for(Map.Entry<String,Integer> entry : map1.entrySet()){
System.out.println(entry.getKey() + ":" + entry.getValue());
}
还有一种在jdk10之后出现的方法:
在底层会自动把map转化为不可变集合。
Map.copyOf(map);
Stream流
Stream流思想和获取stream流方法
stream的使用步骤:
获取stream流对象
使用中间方法处理数据
使用终结方法处理数据
stream流的作用:
简化数组和集合的操作过程
四种获取stream流的方式:
//单列集合获取stream流
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("a");
list.add("a");
list.add("a");
list.stream().forEach(s-> System.out.println(s));
//在双列集合中获取stream流
HashMap<String,Integer> map = new HashMap<>();
map.put("zhangsan", 18);
map.put("lisi", 19);
map.put("wangwu", 20);
Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
entrySet.stream().forEach(entry -> System.out.println(entry.getKey() + ":" + entry.getValue()));
//数组获取stream流
int[] arr = {1,2,3,4,5,6,7,8,9,10};
String[] arr1 = {"zhangsan","lisi","wangwu","zhaoliu"};
Arrays.stream(arr).forEach(s-> System.out.println(s));
// 零散的数据获取stream流
int[] arr = {1,2,3,4,5,6,7,8,9,10};
String[] arr1 = {"zhangsan","lisi","wangwu","zhaoliu"};
//如果是基本数据类型的话,stream会把这个数组看做一个整体 只会打印这个数组的地址值
Stream.of(arr).forEach(s-> System.out.println(s));
Stream.of(1,2,3,4,5).forEach(s-> System.out.println(s));
Stream流的中间方法

注意1:使用中间方法放回的stream流,原来的stream流只能使用一次,建议使用链式编程
注意2:修改stream流中的数据,不会影响到原来集合或者数组中的数据。
ArrayList<String> list = new ArrayList<>();
list.add("zhangsan");
list.add("lisi");
list.add("wangwu");
list.add("zhaoliu");
ArrayList<String> list2 = new ArrayList<>();
Collections.addAll(list2,"张三","李四","王五","赵六");
// filter方法 :过滤
// list.stream().filter(new Predicate<String>(){
// @Override
// public boolean test(String s) {
// return s.startsWith("z");
// }
// }).forEach(System.out::println);
// limit : 获取前几个元素
// list.stream().limit(3).forEach(System.out::println);
//skip: 跳过前几个元素
// list.stream().skip(2).forEach(System.out::println);
// distinct:去重
// list.stream().distinct().forEach(System.out::println);
// concat :合并两个stream流
// Stream.concat(list.stream(),list2.stream()).forEach(System.out::println);
// map :映射 转换流中的数据
// list.stream().map(new Function<String,Integer>(){
//
// @Override
// public Integer apply(String s){
// return s.length();
// }
// }).forEach(System.out::println);
list.stream().map(s-> s.length()).forEach(System.out::println);
Stream流中的终结方法

ArrayList<String> list = new ArrayList<>();
list.add("zhangsan");
list.add("lisi");
list.add("wangwu");
list.add("zhaoliu");
ArrayList<String> list2 = new ArrayList<>();
Collections.addAll(list2,"张三","李四","王五","赵六");
// forEach终结方法
// list.stream().forEach(System.out::println);
// 统计
long cnt = list.stream().count();
System.out.println(cnt);
//收集流中的数据,放在数组中
Object[] arr = list.stream().toArray();
// String[] arr1 = list.stream().toArray(new IntFunction<String[]>() {
// @Override
// public String[] apply(int value) {
// return new String[value];
// }
// });
String[] arr1 = list.stream().toArray(value->new String[value]);
System.out.println(Arrays.toString(arr1));
System.out.println(Arrays.toString(arr));
// 收集流中的数据,放在集合中
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张三-男-14","李四-女-15","王五-男-16","赵六-女-17");
// list.stream().filter(new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.split("-")[1].equals("男");
// }
// });
// Map<String,Integer> m = list.stream()
// .filter(s -> s.split("-")[1].equals("男"))
// .collect(Collectors.toMap((new Function<String,String>(){
// @Override
// public String apply(String s){
// return s.split("-")[0];
// }
// }),(new Function<String,Integer>(){
// @Override
// public Integer apply(String s){
// return Integer.parseInt(s.split("-")[2]);
// }
// })));
Map<String,Integer> m = list.stream()
.filter(s -> s.split("-")[1].equals("男"))
.collect(Collectors.toMap((s->s.split("-")[0]),(s->Integer.parseInt(s.split("-")[2]))));
System.out.println(m);
Set<String> list2 = list.stream().filter(s -> s.split("-")[1].equals("男"))
.collect(Collectors.toSet());
方法引用
方法引用概述
1.什么是方法引用:把已近存在的接口拿过来用,当做函数式接口的方法体。
2.::是方法引用符
3.引用方法的注意事项
需要时函数式接口
被引用的方法是已经存在的
被引用的方法需要和函数式接口的抽象方法的返回值和形参保持一致
被引用的方法需要满足当前的需求
引用静态方法
String[] s = {"1","2","3","4","5"}; Arrays.stream(s).map(Integer::parseInt).forEach(System.out::print);
引入成员方法
格式:对象::成员方法
1.其它类:其它类对象::方法名
2.本类:this::方法名
3.父类:super::方法名
注意:静态方法中没有this,super
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","周芷若","赵敏","张强","张三丰","张四");
// list.stream().filter(s->s.startsWith("张") && s.length() == 3).forEach(s-> System.out.println(s));
list.stream().filter(new demo1()::stringJudge).forEach(System.out::println);
引入构造方法
格式:类名::new
引用构造方法需要在原来的类中再添加一个和函数式接口中需要重写的方法的形参一样的构造方法,并完成需要的操作。
list.stream().map(new Function<String, Student>() {
@Override
public Student apply(String s){
return new Student(s.split(",")[0],Integer.parseInt(s.split(",")[1]));
}
}).collect(Collectors.toList());
// 引用构造方法
list.stream().map(Student::new).collect(Collectors.toList()).forEach(System.out::print);
类名引用成员方法
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"aaaa","bbbb","cccc","dddd","eeee","ffff");
/*
* 通过类名引用成员方法:
* 格式: 类名::成员方法名
* 特殊要求:
* 抽象方法中的第一个形参是什么类型 就只能引用这个类型中的方法。、
* 抽象方法中的第二个形参到最后一个形参必须和引用的方法中的形参保持一致
* 如果抽象方法只有一个参数,那么引用方法必须是空参
*
* 局限性:
* 只能引用抽象方法中第一个参数类型的中的方法。
*
* */
list.stream().map(String::toUpperCase).forEach(System.out::println);
引用数组的构造方法
格式:数据类型[]::new
作用:创建一个数组
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3,4,5,6,7,8,9);
Integer[] arr = list.stream().toArray(Integer[]::new);
System.out.println(Arrays.toString(arr));
更多推荐



所有评论(0)