03-详细介绍Stream及其常用API

Stream API

Stream API(java.util.stream)把真正的函数式编程风格引入到Java中,可以极大地提高程序员生产力,让程序员写出高效、简洁的代码

实际开发中项目中多数数据源都是来自MySQL、Oracle等关系型数据库,还有部分来自MongDB、Redis等非关系型数据库

  • 从关系型数据库中查询数据时: 可以先使用SQL语句在数据库中就对数据进行过滤,排序等操作,最后后台Java程序接收
  • 从非关系型数据库中查询数据: 由于不能在数据库中使用SQL语句操作数据,此时只能靠后台Java程序(Stream API)操作数据库中查询到的数据

Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合/数组进行的操作(可是是非常复杂的查找、过滤和映射数据等操作)

  • 使用Stream API对内存数据(集合/数组)进行操作就类似于使用SQL语句对数据库表中数据的操作,即Stream API提供了一种高效且易于使用的处理数据的方式

Stream和Collection集合的区别: Stream关注的是数据的运算(过滤操作)与CPU打交道, 集合关注的是数据的存储(静态的存储结构)与内存打交道

Stream的特性如操作链

  • Stream自己不会存储元素
  • Stream不会改变源对象,相反他们会返回一个持有结果的新Stream
  • Stream操作是延迟执行的,只有执行了终止操作后才会执行中间操作链并产生结果
  • Stream一旦执行了终止操作,就不能再次使用该Stream执行其它中间操作或终止操作了,得重新创建一个新的流才行

在这里插入图片描述

数据准备

编写实体类Employee

@Date
public class Employee {
    private int id;
    private String name;
    private int age;
    private double salary;
    public Employee() {
        System.out.println("Employee().....");
    }
    public Employee(int id) {
        this.id = id;
        System.out.println("Employee(int id).....");
    }
    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public Employee(int id, String name, int age, double salary) {

        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
    @Override
    public String toString() {
        return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}';
    }
    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        Employee employee = (Employee) o;

        if (id != employee.id)
            return false;
        if (age != employee.age)
            return false;
        if (Double.compare(employee.salary, salary) != 0)
            return false;
        return name != null ? name.equals(employee.name) : employee.name == null;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        result = 31 * result + age;
        temp = Double.doubleToLongBits(salary);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }
}

创建一个ArrayList集合提供测试数据

public class EmployeeData {
    public static List<Employee> getEmployees() {
        List<Employee> list = new ArrayList<>();
        list.add(new Employee(1001, "马化腾", 34, 6000.38));
        list.add(new Employee(1002, "马云", 12, 9876.12));
        list.add(new Employee(1003, "刘强东", 33, 3000.82));
        list.add(new Employee(1004, "雷军", 26, 7657.37));
        list.add(new Employee(1005, "李彦宏", 65, 5555.32));
        list.add(new Employee(1006, "比尔盖茨", 42, 9500.43));
        list.add(new Employee(1007, "任正非", 26, 4333.32));
        list.add(new Employee(1008, "扎克伯格", 35, 2500.32));
        return list;
    }
}

通过一个数据源创建Stream

方式一通过集合(常用):Java8中的Collection接口扩展了两个获取Stream流的方法

方法名功能
default Stream stream()返回一个顺序流(按照顺序操作元素)
default Stream parallelStream()返回一个并行流(并发的操作元素)
@Test
public void test01(){
    // 创建List集合
    List<Integer> list = Arrays.asList(1,2,3,4,5);
    // 返回一个顺序流
    Stream<Integer> stream1 = list.stream();
    // 返回一个并行流
    Stream<Integer> stream2 = list.parallelStream();
    List<Employee> employees = EmployeeData.getEmployees();
    // 返回一个顺序流
    Stream<Employee> stream = employees.stream();
    // 返回一个并行流
    Stream<Employee> employeeStream = employees.parallelStream();
}

方式二通过数组: Java8中的Arrays类的静态方法stream()可以获取对应类型的Stream流

方法名功能
static Stream stream(T[] array)返回一个自定义类型的Stream流
public static IntStream stream(int[] array)返回一个int类型的Stream流
public static LongStream stream(long[] array)返回一个long类型的Stream流
public static DoubleStream stream(double[] array)返回一个double类型的Stream流
@Test
public void test(){
    // 获取基本数据类型的Stream流
    int[] arr = {1,2,3,4,5};
    IntStream stream = Arrays.stream(arr);
    
    // 获取引用数据类型的Stram流
    String[] arr = {"hello","world"};
    Stream<String> stream = Arrays.stream(arr); 
    
    // 获取自定义类型的Stream流
    Employee kyle = new Employee(9527, "Kyle");
    Employee lucy = new Employee(9421, "Lucy");
    Employee[] employees = {kyle, lucy};
    Stream<Employee> stream1 = Arrays.stream(employees);
} 

方式三通过指定具体数据: 调用Stream类静态方法of()创建一个流

方法名功能
public static Stream of(T… values)通过手动指定任意个数据创建一个流
@Test
public void test04(){
    Stream<Integer> stream = Stream.of(1,2,3,4,5);
    stream.forEach(System.out::println);
}

方式四调用Stream类的静态方法iterate()和generate()创建无限流(了解)

方法名功能
public static Stream iterate(final T seed, final UnaryOperator f)迭代
public static Stream generate(Supplier s)生成
@Test
public void test() {
    // 从0开始迭代遍历10个数,没有limit限制会无限迭代
    Stream.iterate(0, t -> t + 1).limit(10).forEach(System.out::println);

    // 生成10个随机数,没有limit限制会无限生成
    Stream.generate(Math::random).limit(10).forEach(System.out::println);
}

中间操作筛选与切片

中间操作就是处理数据的具体操作,每次处理都会返回一个持有结果的新Stream,即中间操作的方法返回值仍然是Stream类型的对象

  • 中间操作可以是个操作链可对数据源的数据进行n次处理(多个中间操作),但是所有的中间操作只有在执行终止操作时才会一次性全部处理(惰性求值)
  • Stream流一旦执行了终止操作就不能再次使用该Stream执行其它中间操作,需要根据数据源重新创建一个新的Stream流
方 法描 述
filter(Predicatep)接收Lambda表达式按照规则后从流中排除某些元素
distinct()筛选通过流所生成元素的hashCode() 和 equals()方法去除重复元素
limit(long maxSize)截断流使其元素不超过给定数量,返回一个只保留前n个元素的流
skip(long n)跳过元素返回一个扔掉了前 n个元素的流, 若流中元素不足n个则返回一个空流
@Test
public void test() {
    List<Employee> employees = EmployeeData.getEmployees();
    //1. 查询工资大于7000的员工信息
    employees.stream().filter(employee -> employee.getSalary() > 7000).forEach(System.out::println);
    System.out.println("----------------------------");
    //2. 只输出3条员工信息
    employees.stream().limit(3).forEach(System.out::println);
    System.out.println("----------------------------");
    //3. 跳过前3个元素
    employees.stream().skip(3).forEach(System.out::println);
    System.out.println("----------------------------");
    //4. 通过流所生成元素的hashCode和equals方法去除重复元素
    employees.add(new Employee(9527, "Kyle", 20, 9999));
    employees.add(new Employee(9527, "Kyle", 20, 9999));
    employees.stream().distinct().forEach(System.out::println);
}
/*
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
----------------------------
Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1003, name='刘强东', age=33, salary=3000.82}
----------------------------
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1005, name='李彦宏', age=65, salary=5555.32}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1007, name='任正非', age=26, salary=4333.32}
Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}
----------------------------
Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1003, name='刘强东', age=33, salary=3000.82}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1005, name='李彦宏', age=65, salary=5555.32}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1007, name='任正非', age=26, salary=4333.32}
Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}
Employee{id=9527, name='Kyle', age=20, salary=9999.0}
*/

中间操作映射新的Stream

编写函数逻辑: 遍历Stream中的每个元素转换成其他形式或提取流中元素的信息

方法描述
map(Function f)接收一个函数作为参数, 该函数会被应用到每个元素上最终得到一个新的元素放入Stream中
mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元素上产生一个新的DoubleStream
mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元素上产生一个新的IntStream
mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元素上产生一个新的LongStream
flatMap(Function f)接收一个函数作为参数, 该函数会被应用到每个元素上最终得到一个新的Stream,最后把所有Stream连接成一个Stream

map和flatMap的区别: map方法最终会得到一个Stream(含有多个Stream实例),flatMap最终会得到一个Stream(含有多个元素)

@Test
public void test() {
    ArrayList list1 = new ArrayList();
    list1.add(1);
    list1.add(2);
    list1.add(3);
    
    ArrayList list2 = new ArrayList();
    list2.add(1);
    list2.add(2);
    list2.add(3);
	// 集合中有4个元素,[1,2,3,[4,5,6]],类似map
    list1.add(list2);
    // 集合中有6个元素,[1,2,3,4,5,6],类似flatMap
    list1.addAll(list2);
    System.out.println(list1);
}
public class LambdaTest{
    @Test
    public void test() {
        List<String> strings = Arrays.asList("aa", "bb", "cc", "dd");
        List<Employee> employees = EmployeeData.getEmployees();
        // 将集合所有元素转为大写并输出
        strings.stream().map(s -> s.toUpperCase()).forEach(System.out::println);
        System.out.println("--------------------------");
        // 获取员工姓名长度大于3的员工的姓名
        employees.stream().map(Employee::getName).
            // 过滤出name长度>3的员工
            filter(name -> name.length() > 3).
            // 遍历输出
            forEach(System.out::println);
        System.out.println("--------------------------");
        // 使用map将字符串中的多个字符构成的集合转换为对应的Stream实例,然后再获取每一个Stream中的元素
        strings.stream().map(LambdaTest::formStringToStream).
            forEach(characterStream -> characterStream.forEach(System.out::println));
        // 使用flatMap可以直接获取Stream中Stream实例中的元素
        System.out.println("--------------------------");
        strings.stream().flatMap(LambdaTest::formStringToStream).forEach(System.out::println);
    }

    public static Stream<Character> formStringToStream(String str) {
        ArrayList<Character> list = new ArrayList<>();
        for (char c : str.toCharArray()) {
            list.add(c);
        }
        return list.stream();
    }

}
/*
AA
BB
CC
DD
--------------------------
比尔盖茨
扎克伯格
--------------------------
a
a
b
b
c
c
d
d
--------------------------
*/

中间操作排序

方法描述
sorted()产生一个新流其中按自然顺序排序
sorted(Comparator com)产生一个新流其中按比较器顺序排序
@Test
public void test20() {
    List<Integer> nums = Arrays.asList(13, 54, 97, 52, 43, 64, 27);
    List<Employee> employees = EmployeeData.getEmployees();
    //自然排序,如果要对employees自然排序就需要Employee类实现Comparable接口
    nums.stream().sorted().forEach(System.out::println);
    //定制排序,先按照年龄升序排,再按照工资降序排
    employees.stream().sorted((o1, o2) -> {
        int compare = Integer.compare(o1.getAge(), o2.getAge());
        if (compare != 0) {
            return compare;
        } else {
            return -Double.compare(o1.getSalary(), o2.getSalary());
        } 
    }).forEach(System.out::println);
}

/*
13
27
43
52
54
64
97
------------------
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1007, name='任正非', age=26, salary=4333.32}
Employee{id=1003, name='刘强东', age=33, salary=3000.82}
Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1005, name='李彦宏', age=65, salary=5555.32}
*/

终止操作匹配与查找

终端操作会从流的流水线生成结果,方法返回值类型就不再是Stream了可以是任何不是流的值(如List,Integer,void等),并且不能再执行其他中间操作或终止操作

方法描述
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFirst()返回第一个元素
findAny()返回当前流中的任意元素,顺序流会取第一个元素
count()返回流中元素总数
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
forEach(Consumer c)Stream API帮你把迭代做了内部迭代
使用 Collection接口需要用户去做迭代(称为外部迭代)
@Test
public void test21(){
    List<Employee> employees = EmployeeData.getEmployees();
    // 练习:是否所有的员工的工资是否都大于5000
    System.out.println("是否所有的员工的工资是否都大于5000:"+employees.stream().allMatch(employee -> employee.getSalary() > 5000));
    // 练习:是否存在员工年龄小于15
    System.out.println("是否存在员工年龄小于15:"+employees.stream().anyMatch(employee -> employee.getAge() < 15));
    // 练习:是否不存在员工姓“马”
    System.out.println("是否不存在员工姓马:"+employees.stream().noneMatch(employee -> employee.getName().startsWith("马")));
    // 返回流中的第一个元素
    System.out.println("返回第一个元素:"+employees.stream().findFirst());
    // 返回当前流中的任意元素
    System.out.println("返回当前流中的任意元素"+employees.stream().findAny());
    // 返回流中元素的总个数(返回总个数前可以先过滤)
    System.out.println("返回元素总数:"+employees.stream().count());
    // 返回流中最高工资
    System.out.println("返回最高工资:"+employees.stream().map(Employee::getSalary).max(Double::compare));
    // 返回最低工资的员工
    System.out.println("返回最高工资:"+employees.stream().min(e1,e2 -> Double.compare(e1.getSalary(),e2.getSalary()));
    // 返回流中最小值
    System.out.println("返回最小年龄:"+employees.stream().map(Employee::getAge).min(Integer::compare));
    // 内部迭代
    employees.stream().forEach(System.out::println);
    System.out.println("-------------");
    // 使用集合的遍历操作
    employees.forEach(System.out::println);
}

/*
是否所有的员工的工资是否都大于5000:false
是否存在员工年龄小于15:true
是否不存在员工姓马:false
返回第一个元素:Optional[Employee{id=1001, name='马化腾', age=34, salary=6000.38}]
返回当前流中的任意元素Optional[Employee{id=1001, name='马化腾', age=34, salary=6000.38}]
返回元素总数:8
返回最高工资:Optional[9876.12]
返回最小年龄:Optional[12]
Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1003, name='刘强东', age=33, salary=3000.82}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1005, name='李彦宏', age=65, salary=5555.32}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1007, name='任正非', age=26, salary=4333.32}
Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}
-------------
Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1003, name='刘强东', age=33, salary=3000.82}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1005, name='李彦宏', age=65, salary=5555.32}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1007, name='任正非', age=26, salary=4333.32}
Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}
*/    

终止操作归约

map和reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名

方法描述
reduce(T identity, BinaryOperator b)可以将流中元素反复结合起来返回一个值T
reduce(BinaryOperator b)可以将流中元素反复结合起来返回一个值Optional
@Test
public void test22() {
    List<Integer> nums = Arrays.asList(13, 32, 23, 31, 94, 20, 77, 21, 17);
    List<Employee> employees = EmployeeData.getEmployees();
    // 练习1:计算1-10的自然数的和(0是初始值)
    System.out.println(nums.stream().reduce(0, Integer::sum));
    // 练习2:手动计算公司所有员工工资总和
    System.out.println(employees.stream().map(Employee::getSalary).reduce((o1, o2) -> o1 + o2));
    // 调用Integer的sum方法计算年龄总和
    System.out.println(employees.stream().map(Employee::getAge).reduce(Integer::sum));
    // 计算公司所有员工工资综合
    Stream<Double> salaryStream = employ.stream.map(Employee::getSalary);
    Optional<Double> sumMoney = salaryStream.reduce(Double::sum);
    System.out.println(sumMoney);
}
/*
328
Optional[48424.08]
Optional[273]
Optional[48424.08]
*/

终止操作收集

方 法描 述
collect(Collector c)接收一个Collector接口的实现类, 将Stream转换为其他形式,用于给Stream中元素做汇总

Collector接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map),可以方便地创建常见收集器实例

方法返回类型作用
toListList把流中元素收集到List
List emps= list.stream().collect(Collectors.toList());
toSetList把流中元素收集到List
Set emps= list.stream().collect(Collectors.toSet());
toCollectionCollection把流中元素收集到创建的集合
Collection emps =list.stream().collect(Collectors.toCollection(ArrayList::new));
countingLong计算流中元素的个数
long count = list.stream().collect(Collectors.counting());
summinglntInteger对流中元素的整数属性求和
int total=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingIntDouble计算流中元素Integer属性的平均值
double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizinglntIntSummaryStatistics收集流中Integer属性的统计值
如平均值
int SummaryStatisticsiss=list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
joiningString连接流中每个字符串
String str= list.stream().map(Employee::getName).collect(Collectors.joining());
maxByOptional根据比较器选择最大值
optionalmax=list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary));
minByOptional根据比较器选择最小值
Optionalmin = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary));
reducing归约产生的类型从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值
int total=list.stream().collect(Collectors.reducing(0, Employe::getSalar, Integer::sum));
collectingAndThen转换函数返回的类型包裹另一个收集器,对其结果转换函数
int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingByMap<K, List>根据某属性值对流分组
属性为K,结果为V
Map<Emp.Status, List> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus));
partitioningByMap<Boolean,List>根据true或false进行分区
Map<Boolean,List>vd=list.stream().collect(Collectors.partitioningBy(Employee::getManage));
@Test
public void test23() {
    // 练习1:查找工资大于6000的员工,结果返回为一个List
    List<Employee> employees = EmployeeData.getEmployees();
    List<Employee> list = employees.stream().filter(employee -> employee.getSalary() > 6000).collect(Collectors.toList());
    list.forEach(System.out::println);
    System.out.println("--------------------");
    // 练习2:查找年龄大于20的员工,结果返回为一个Set
    employees.add(new Employee(9527,"Kyle",21,9999));
    employees.add(new Employee(9527,"Kyle",21,9999));
    Set<Employee> set = employees.stream().filter(employee -> employee.getAge() > 20).collect(Collectors.toSet());
    set.forEach(System.out::println);
}

/*
Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
--------------------
Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1007, name='任正非', age=26, salary=4333.32}
Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1005, name='李彦宏', age=65, salary=5555.32}
Employee{id=1003, name='刘强东', age=33, salary=3000.82}
Employee{id=9527, name='Kyle', age=21, salary=9999.0}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
*/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/181620.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Exception in thread “消费者“ java.lang.IllegalMonitorStateException

这两天学习生产者消费者模型的时候&#xff0c;使用Java线程来实现&#xff0c;出现了一个问题“Exception in thread "消费者" java.lang.IllegalMonitorStateException”&#xff0c;并且&#xff0c;线程不结束。报错图片如下&#xff1a; 那我们怎么解决呢&…

Selenium(12):层级定位_通过父元素找到子元素

层级定位 在实际的项目测试中&#xff0c;经常会遇到无法直接定位到需要选取的元素&#xff0c;但是其父元素比较容易定位&#xff0c;通过定位父元素再遍历其子元素选择需要的目标元素&#xff0c;或者需要定位某个元素下所有的子元素。 层级定位的思想是先定位父对象&#xf…

二叉树题目:具有所有最深结点的最小子树

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;具有所有最深结点的最小子树 出处&#xff1a;865. 具有所有最深结点的最小子树 难度 5 级 题目描述 要求 给定…

【史上最细教程】一台服务器上搭建2个MySQL实例

史上最细教程-一台服务器上搭建2个MySQL实例 文章目录 史上最细教程-一台服务器上搭建2个MySQL实例环境准备&#xff1a;操作步骤&#xff1a;1.安装MySQL2.配置搭建3306、3307实例3.初始化3306、3307实例、远程连接访问支持 推荐文章&#xff1a; 环境准备&#xff1a; 服务器…

考过了PMP,面试的时候应该怎么办?

近期喜番在后台收到了很多同学们的私信&#xff0c;表示自己已经过了8月份的PMP考试&#xff0c;开始着手往项目管理岗位转型&#xff0c;但是对于项目管理岗位的面试却一筹莫展。放轻松&#xff0c;大家的需求喜番都了解了&#xff0c;喜番给大家总结了一些项目经理在面试的时…

护法革命:CIMIVO+SOTUY洗前发膜让发丝重获“芯”生

爱美之心人皆有之,经常烫染或者是在太阳下暴晒,都会对发丝造成一定的伤害,一旦发丝受损,就会导致发芯内部角蛋白流失、化学键连接断裂,进而出现各种发质问题。为此,日本知名化妆品集团NABOCUL旗下发芯修护引领品牌ENNEO创新研发两大核心成分:CIMIVO、SOTUY,能够从根源修护发芯内…

CLion安装与配置教程

目录 一、下载并安装CLion1、下载1、官网&#xff1a;2、注意&#xff1a; 2、安装1、下载完成后&#xff0c;直接点击安装包安装&#xff0c;即可。2、开始安装&#xff0c;然后下一步3、可以在此处自定义地址&#xff0c;然后下一步4、根据系统版本选择&#xff0c;然后下一步…

C#开发的OpenRA游戏之属性Selectable(9)

C#开发的OpenRA游戏之属性Selectable(9) 在游戏里,一个物品是否具有选中的能力,是通过添加属性Selectable来实现的。当一个物品不能被用户选取,那么就不要添加这个属性。 这个属性定义在下面这段描述里: ^Selectable: Selectable: SelectionDecorations: WithSpriteCon…

网络安全入门教程(非常详细)从零基础入门到精通

网络安全是一个庞大而不断发展的领域&#xff0c;它包含多个专业领域&#xff0c;如网络防御、网络攻击、数据加密等。介绍网络安全的基本概念、技术和工具&#xff0c;逐步深入&#xff0c;帮助您成为一名合格的网络安全从业人员。 一、网络安全基础知识 1.计算机基础知识 了解…

[点云分割] Clustering of Pointclouds into Supervoxels

介绍 “Clustering of Pointclouds into Supervoxels” 是一种点云数据聚类的方法&#xff0c;用于将点云数据分割成具有相似特征的超体素&#xff08;supervoxel&#xff09;。 超体素是一种在点云数据中表示连续区域的方法&#xff0c;类似于像素在图像中表示连续区域。超体…

vue + docxtemplater 导出 word 文档

一、痛点 word 导出 这种功能其实之前都是后端实现的&#xff0c;但最近有个项目没得后端。所以研究下前端导出。 ps&#xff1a; 前端还可以导出 pdf&#xff0c;但是其分页问题需要话精力去计算才可能实现&#xff0c;并且都不是很完善。可参考之前的文章&#xff1a;利用 h…

Peter算法小课堂—前缀和数组的应用

桶 相当于计数排序&#xff0c;看一个视频 桶排序 太戈编程1620题 算法解析 #include <bits/stdc.h> using namespace std; const int R11; int cnt[R];//cnt[t]代表第t天新增几人 int s[R];//s[]数组是cnt[]数组的前缀和数组 int n,t; int main(){cin>>n;for(…

意图交易:为用户思考,而不是让用户思考

意图叙事 在前不久&#xff0c;知名加密投资机构 Paradigm 的 CTO 、研究员 Georgios Konstantopoulos 曾在推特上对现有 ChainAsset 模式的糟糕且割裂的体验进行了吐槽&#xff0c;这也道出了很多链上用户的痛点。同时他也认为现有加密基建设施需要为用户思考&#xff0c;而不…

Nginx:简介、安装与部署

一、Nginx简介 Nginx是一个很好的高性能Web和反向大力服务器&#xff0c;它具有很多非常优越的特性&#xff1a;在高连接并发的情况下&#xff0c;Nginx是Apahe服务器的不错的替代品&#xff1a;Nginx在美国是虚拟主机生意选择的软件平台之一。能够支持50000个并发连接数的响应…

若依vue-修改标题和图标

因为我们拉下来的代码,图标和logo是若依的,这和我们需要做出来的效果有差别 这个时候就需要去对应的文件内去修改标题和图标 (主要就是这两个地方的图标和标题) 修改菜单里面的logo以及文字 修改文字 位置: src/layout/component/Sidebar/Logo.vue 此处的title文字是定义在…

plantUML学习与实战

背景 在日常工作或者生活中&#xff0c;使用交互图来描述想法&#xff0c;往往相对于文字来说&#xff0c;可读性更高&#xff0c;同时一定程度上可以提高沟通效率&#xff0c;但是苦于&#xff0c;不想对一堆控件拖拖拉拉&#xff0c;本人就是一个很讨厌画图&#xff0c;但是…

谈思生物医疗直播 | 利用类器官模型研究肺的发育与稳态

类器官是一种三维细胞培养物&#xff0c;其在细胞类型&#xff0c;空间结构及生理功能上能够模拟对应器官&#xff0c;从而提供一个高度生理相关的系统。自2009年小肠类器官首次建立至今&#xff0c;类器官研究已经延伸到多个组织系统&#xff0c;并成为当下生命科学领域最热门…

改善钢棒直线度检测可靠性 在线直线度测量仪替代人工检测

根据GB/T908-2019标准规定&#xff0c;钢棒的尺寸包括直径或边长、长度、弯曲度等。因此钢棒在生产中进行尺寸检测&#xff0c;保证成品符合规格&#xff0c;是降低废品率的重要一环。 有些钢棒的弯曲很明显&#xff0c;肉眼可看&#xff0c;但更有很多不明显的需要借助工具检测…

亿级流量架构服务降级

什么是服务降级 如果看过我前面对服务限流的分析,理解服务降级就很容易了,对于一个景区,平时随便进出,但是一到春节或者十一国庆这种情况客流量激增,那么景区会限制同时进去的人数,这叫限流,那么什么是服务降级呢? 简单来说就是,将一些不太重要的景区项目砍掉,平时就那么三五…

[PTQ]均匀量化和非均匀量化

均匀量化和非均匀量化 基本概念 量化出发点&#xff1a;使用整型数据类型代替浮点数据&#xff0c;从而节省存储空间同时加快推理速度。量化基本形式 均匀量化&#xff1a;浮点线性映射到定点整型上&#xff0c;可以根据scale/offset完成量化/反量化操作。非均匀量化 PowersO…