7 - 函数式编程

news/2024/9/24 20:42:50/

文章目录

  • Interface
  • 函数式接口
  • Lambda表达式
    • 语法格式
    • 替代匿名类
    • 集合迭代
    • 方法引用
    • 作用域范围
    • this关键字
  • Stream流
    • 流类型
    • 创建流
    • 中间操作流
      • 过滤 filter
      • 映射 map
      • 去重 distinct
      • 排序 sorted
      • 限制 limit
      • 跳过 skip
      • flatMap
    • 终结操作流
      • forEach
      • count
      • min/max
      • collect
      • 匹配
      • 组合 reduce
    • 延迟执行
    • 并行流
  • Optional
    • 创建对象
    • 判断值是否存在
    • 非空表达式
    • 设置默认值
      • orElse
      • orElseGet
    • 获取值
    • 过滤值 filter
    • 转换值 map

Interface

初衷是面向抽象,提高扩展性。Interface 修改的时候,实现它的类也必须跟着改。

为了解决接口的修改与现有的实现不兼容的问题。新 interface 的方法可以用defaultstatic修饰,这样就可以有方法体,实现类也不必重写此方法。

这 2 个修饰符的区别主要也是普通方法和静态方法的区别:

  • default修饰的方法,是普通实例方法,可以用this调用,可以被子类继承、重写
  • static修饰的方法,使用上和一般类静态方法一样。但它不能被子类继承,只能用Interface调用
java">public interface InterfaceNew {static void sm() {System.out.println("interface提供的方式实现");}default void def() {System.out.println("interface default方法");}//须要实现类重写void f();
}public interface InterfaceNew1 {default void def() {System.out.println("InterfaceNew1 default方法");}
}

这里可以看到两个接口都有 def 方法,而且两个接口没有继承关系,那么如果一个类实现了这两个接口,就必须要重写 def 方法

java">public class InterfaceNewImpl implements InterfaceNew , InterfaceNew1{public static void main(String[] args) {InterfaceNewImpl interfaceNew = new InterfaceNewImpl();interfaceNew.def();}// `def` 方法的重写@Overridepublic void def() {InterfaceNew1.super.def();}//对应的方法实现@Overridepublic void f() {}
}

这样接口就可有自己的方法实现了!!!那岂不是和抽象类很像了???
区别:

  • 接口多实现,类单继承
  • 接口的方法是 public abstract 修饰,变量是 public static final 修饰。 abstract class 可以用其他修饰符

牢记:接口是接口,类是类

函数式接口

SAM 接口:Single Abstract Method interfaces 有且只有一个抽象方法,但可以有多个非抽象方法的接口

在包 java.util.function 包里

Lambda表达式

Lambda 表达式可以使代码变的更加简洁紧凑,提高代码的可读性

语法格式

java">(parameters) -> expression 或
(parameters) ->{ statements; }

替代匿名类

过去给方法传动态参数的唯一方法是使用内部类

  1. Runnnable接口
java">//以前的格式
new Thread(new Runnable() {@Overridepublic void run() {System.out.println("The runable now is using!");}
}).start();
//用lambda的格式
new Thread(() -> System.out.println("It's a lambda function!")).start();
  1. Comparator接口
java">List<Integer> strings = Arrays.asList(1, 2, 3);Collections.sort(strings, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {return o1 - o2;}
});
//Lambda
Collections.sort(strings, (Integer o1, Integer o2) -> o1 - o2);
  1. Listener接口
java">JButton button = new JButton();
button.addItemListener(new ItemListener() {@Overridepublic void itemStateChanged(ItemEvent e) {e.getItem();}
});
//lambda
button.addItemListener(e -> e.getItem());
  1. 自定义接口
java">@FunctionalInterface
public interface LambdaInterface {void f();
}
//使用
public class LambdaClass {public static void forEg() {lambdaInterfaceDemo(()-> System.out.println("自定义函数式接口"));}//函数式接口参数static void lambdaInterfaceDemo(LambdaInterface i){i.f();}
}

集合迭代

java">void lamndaFor() {List<String> strings = Arrays.asList("1", "2", "3");//传统foreachfor (String s : strings) {System.out.println(s);}//Lambda foreachstrings.forEach((s) -> System.out.println(s));//or 这里把参数也一同省略了strings.forEach(System.out::println);//mapMap<Integer, String> map = new HashMap<>();map.forEach((k,v)->System.out.println(v));
}

方法引用

:: 关键字来传递方法或者构造函数引用,要求表达式的返回类型是 functiuon-interface

java">public class LambdaClassSuper {LambdaInterface sf(){return null;}
}public class LambdaClass extends LambdaClassSuper {public static LambdaInterface staticF() {return null;}public LambdaInterface f() {return null;}void show() {//1.调用静态函数,返回类型必须是functional-interfaceLambdaInterface t = LambdaClass::staticF;//2.实例方法调用LambdaClass lambdaClass = new LambdaClass();LambdaInterface lambdaInterface = lambdaClass::f;//3.超类上的方法调用LambdaInterface superf = super::sf;//4. 构造方法调用LambdaInterface tt = LambdaClassSuper::new;}
}

作用域范围

和匿名内部类一样,不要在 Lambda 表达式主体内对方法内的局部变量进行修改,否则编译也不会通过

Lambda 表达式中要用到的,但又未在 Lambda 表达式中声明的变量,必须声明为 final 或者是 effectively final,否则就会出现编译错误

final 变量:

  • 一旦被初始化之后,其值就不能被改变,不能再次修改
  • 可以是类的成员变量、局部变量或者方法参数
  • final变量在声明时可以不立即初始化,但必须在成为非局部作用域之前被初始化

effectively final 变量:

  • 指在代码的实际执行过程中,变量的值在初始化之后不会被改变,尽管在语法上它并没有被声明为final
  • 通常用于匿名类和Lambda表达式中。在这些场景下,如果变量不在这个匿名类或Lambda表达式内部被声明,那么它必须不可变,即使没有使用final关键字
  • 实际上是一个语言规则,确保变量在匿名类或Lambda表达式中安全使用
  1. 把变量声明为 static
java">public class ModifyVariable2StaticInsideLambda {static int limit = 10;public static void main(String[] args) {Runnable r = () -> {limit = 5;for (int i = 0; i < limit; i++) {System.out.println(i);}};new Thread(r).start();}
}
//输出
// 0 1 2 3 4   方案可行
  1. 变量声明为 AtomicInteger
    AtomicInteger 可以确保 int 值的修改是原子性的,可以使用 set() 方法设置一个新的 int 值,get() 方法获取当前的int
java">public class ModifyVariable2AtomicInsideLambda {public static void main(String[] args) {final AtomicInteger limit = new AtomicInteger(10);Runnable r = () -> {limit.set(5);for (int i = 0; i < limit.get(); i++) {System.out.println(i);}};new Thread(r).start();}
}
  1. 使用数组
    在声明数组的时候设置为 final,但更改int的值时却修改的是数组的一个元素
java">public class ModifyVariable2ArrayInsideLambda {public static void main(String[] args) {final int [] limits = {10};Runnable r = () -> {limits[0] = 5;for (int i = 0; i < limits[0]; i++) {System.out.println(i);}};new Thread(r).start();}
}

this关键字

Lambda 表达式并不会引入新的作用域,这一点和匿名内部类是不同的。也就是说,Lambda 表达式主体内使用的 this 关键字和其所在的类实例相同。

Stream流

Stream不存储数据,可以检索(Retrieve)和逻辑处理集合数据、包括筛选、排序、统计、计数等。
就好像一个高级的迭代器,但只能遍历一次。

  1. 通过简单的链式编程,使得它可以方便地对遍历处理后的数据进行再处理。
  2. 方法参数都是函数式接口类型
  3. 一个 Stream 只能操作一次,操作完就关闭了,继续使用这个 stream 会报错。
  4. Stream 不保存数据,不改变数据源

流类型

  1. stream 串行流
  2. parallelStream 并行流,可多线程执行

创建流

如果是数组的话,可以使用Arrays.stream()或者 Stream.of() 创建流;
如果是集合的话,可以直接使用 stream() 方法创建流,因为该方法已经添加到 Collection 接口中

java">public class CreateStreamDemo {public static void main(String[] args) {String[] arr = new String[]{"加油1", "加油2", "加油3"};Stream<String> stream = Arrays.stream(arr);stream = Stream.of("加油1", "加油2", "加油3");List<String> list = new ArrayList<>();list.add("加油1");list.add("加油2");list.add("加油3");stream = list.stream();}
}

注: of() 方法内部其实调用了Arrays.stream()方法

java">public static<T> Stream<T> of(T... values) {return Arrays.stream(values);
}

集合还可以调用 parallelStream() 方法创建并发流,默认使用的是 ForkJoinPool.commonPool()线程池

java">List<Long> aList = new ArrayList<>();
Stream<Long> parallelStream = aList.parallelStream();

中间操作流

过滤 filter

通过filter()方法可以从流中筛选出我们想要的元素
filter() 方法接收的是一个 Predicate(Java 8 新增的一个函数式接口,接受一个输入参数返回一个布尔值结果)类型的参数
forEach() 方法接收的是一个 Consumer(Java 8 新增的一个函数式接口,接受一个输入参数并且无返回的操作)类型的参数

java">public class FilterStreamDemo {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("周杰伦");list.add("王力宏");list.add("陶喆");list.add("林俊杰");Stream<String> stream = list.stream().filter(element -> element.contains("王"));stream.forEach(System.out::println);}
}

映射 map

通过某种操作把一个流中的元素转化成新的流中的元素,map()接收的是一个 Function(Java 8 新增的一个函数式接口,接受一个输入参数 T,返回一个结果 R)类型的参数

java">public class MapStreamDemo {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("周杰伦");list.add("王力宏");list.add("陶喆");list.add("林俊杰");Stream<Integer> stream = list.stream().map(String::length);stream.forEach(System.out::println);}
}

去重 distinct

它能够去除流中的重复元素

java">public class StreamDistinctExample {public static void main(String[] args) {// 创建一个包含重复数字的列表List<Integer> numbersWithDuplicates = Arrays.asList(1, 2, 2, 3, 4, 4, 5, 5, 5);// 使用Stream API对列表进行处理,去除重复的数字List<Integer> distinctNumbers = numbersWithDuplicates.stream()  // 创建一个Stream.distinct()  // 去除重复元素.collect(Collectors.toList());  // 将结果收集到一个新的列表中// 输出结果distinctNumbers.forEach(System.out::println);}
}

排序 sorted

它可以对流中的元素进行排序

java">public class StreamSortedExample {public static void main(String[] args) {// 创建一个包含数字的列表,这些数字将被排序List<Integer> numbers = Arrays.asList(3, 5, 1, 4, 2);// 使用Stream API对列表进行处理,对数字进行升序排序List<Integer> sortedNumbers = numbers.stream()  // 创建一个Stream.sorted()  // 对流中的元素进行排序.collect(Collectors.toList());  // 将排序后的流收集到一个新的列表中// 输出排序后的结果sortedNumbers.forEach(System.out::println);}
}

这里使用的是`sorted()``的默认方案,是进行自然排序,即为进行升序排序

可以传递一个Comparator给sorted()方法:来实现改变排序规则

java">// 对字符串列表按照字符串长度降序排序
List<String> strings = Arrays.asList("banana", "apple", "cherry", "date");
List<String> sortedStrings = strings.stream().sorted((s1, s2) -> s2.length() - s1.length())  // 按字符串长度降序排序.collect(Collectors.toList());
sortedStrings.forEach(System.out::println);

限制 limit

用来限制流中元素的数量,当想要从流中获取前 n 元素时,limit()操作非常有用

java">public class StreamLimitExample {public static void main(String[] args) {// 创建一个包含多个数字的列表List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);// 使用Stream API对列表进行处理,获取前5个数字List<Integer> firstFiveNumbers = numbers.stream()  // 创建一个Stream.limit(5)  // 限制流中的元素数量为5.collect(Collectors.toList());  // 将结果收集到一个新的列表中// 输出获取到的数字firstFiveNumbers.forEach(System.out::println); // 1,2,3,4,5}
}

跳过 skip

用于跳过流中的前n个元素

java">// 获取列表中的第6到10个数字
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> sixthToTenthNumbers = numbers.stream().skip(5)  // 跳过前5个数字.limit(5)  // 限制流中的元素数量为5.collect(Collectors.toList());sixthToTenthNumbers.forEach(System.out::println);  // 6,7,8,9,10

flatMap

可以简洁地将复杂的多级结构简化为单级结构

java">public class StreamFlatMapExample {public static void main(String[] args) {// 创建一个包含多个列表的列表,即列表的列表List<List<String>> listOfLists = Arrays.asList(Arrays.asList("a1", "a2"),Arrays.asList("b1", "b2", "b3"),Arrays.asList("c1"));// 使用Stream API对列表进行处理,将列表的列表扁平化为一个单一的流List<String> flattenedList = listOfLists.stream()  // 创建一个Stream.flatMap(list -> list.stream())  // 将每个列表扁平化为流.collect(Collectors.toList());  // 将扁平化的流收集到一个新的列表中// 输出扁平化后的列表flattenedList.forEach(System.out::println);// a1 a2 b1 b2 b3 c1}
}

终结操作流

forEach

java">public class MapStreamDemo {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("周杰伦");list.add("王力宏");list.add("陶喆");list.add("林俊杰");Stream<Integer> stream = list.stream().map(String::length);stream.forEach(System.out::println);}
}

count

count操作是一个终端操作,它返回一个long类型的值,表示流中元素的数量

java">public class StreamCountExample {public static void main(String[] args) {// 创建一个包含一些数字的列表List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);// 使用Stream API对列表进行处理,计算大于5的数字的数量long count = numbers.stream()  // 创建一个Stream.filter(num -> num > 5)  // 过滤出大于5的数字.count();  // 计算过滤后的数量// 输出结果System.out.println("大于5的数字的数量是: " + count);}
}

min/max

用于找出流中元素的最小值和最大值。这些操作返回的是Optional类型的结果,因为流中可能不包含任何元素,在这种情况下,Optional将不会包含任何值

java">public class StreamMinMaxExample {public static void main(String[] args) {// 创建一个包含数字的列表List<Integer> numbers = Arrays.asList(10, 4, 5, 8, 6, 11, 2);// 使用Stream API找出数字中的最小值Optional<Integer> minNumber = numbers.stream().min(Integer::compareTo); // 使用Integer::compareTo作为比较器// 使用Stream API找出数字中的最大值Optional<Integer> maxNumber = numbers.stream().max(Integer::compareTo); // 使用Integer::compareTo作为比较器// 输出最小值和最大值minNumber.ifPresent(System.out::println); // 输出: 2maxNumber.ifPresent(System.out::println); // 输出: 11}
}

这两个方法都需要一个Comparator作为参数来定义如何比较元素。在这个例子中,我们使用Integer::compareTo作为比较器,它是比较两个Integer对象的标准方式

collect

将流转换成其他形式。最常见的用途是将流收集到一个集合中,如列表、集合或映射。

java">public class StreamCollectExample {public static void main(String[] args) {// 创建一个包含数字的列表List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);// 使用Stream API将流收集到一个列表中List<Integer> collectedNumbers = numbers.stream().collect(Collectors.toList());// 输出收集到的列表collectedNumbers.forEach(System.out::println);}
}

除了toList()Collectors类还提供了许多其他有用的收集器,例如:

  • toSet(): 将流收集到一个Set中,自动去除重复元素。
  • toMap(): 将流收集到一个Map中,需要提供键和值的函数。
  • groupingBy(): 根据某个属性对流中的元素进行分组。
  • joining(): 将流中的元素连接成一个字符串。

匹配

Stream 类提供了三个方法可供进行元素匹配,它们分别是:

  • anyMatch(),只要有一个元素匹配传入的条件,就返回 true。

  • allMatch(),只有有一个元素不匹配传入的条件,就返回 false;如果全部匹配,则返回 true。

  • noneMatch(),只要有一个元素匹配传入的条件,就返回 false;如果全部不匹配,则返回 true。

java">public class MatchStreamDemo {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("周杰伦");list.add("王力宏");list.add("陶喆");list.add("林俊杰");boolean  anyMatchFlag = list.stream().anyMatch(element -> element.contains("王"));boolean  allMatchFlag = list.stream().allMatch(element -> element.length() > 1);boolean  noneMatchFlag = list.stream().noneMatch(element -> element.endsWith("沉"));System.out.println(anyMatchFlag);  // trueSystem.out.println(allMatchFlag);  // trueSystem.out.println(noneMatchFlag); // true}
}

组合 reduce

把 Stream 中的元素组合起来,最终得到一个汇总的结果。

有两种用法

  • Optional<T> reduce(BinaryOperator<T> accumulator)
    没有起始值,只有一个参数,就是运算规则,此时返回 Optional
  • T reduce(T identity, BinaryOperator<T> accumulator)
    有起始值,有运算规则,两个参数,此时返回的类型和起始值类型一致
java">// 创建一个包含数字的列表
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);// 使用Stream API和reduce操作计算数字的总和
Optional<Integer> sum = numbers.stream().reduce((x, y) -> x + y);// 输出总和
sum.ifPresent(System.out::println); // 输出: 15

reduce方法返回的是Optional<Integer>,因为如果流中没有元素,reduce操作将没有结果。我们使用ifPresent()方法来检查Optional是否包含值,并在控制台上打印出来

java">Integer[] ints = {0, 1, 2, 3};
List<Integer> list = Arrays.asList(ints);int reduce = list.stream().reduce(6, (a, b) -> a + b);
System.out.println(reduce);  //12
int reduce1 = list.stream().reduce(6, Integer::sum);
System.out.println(reduce1); // 12

相当于给了一个起始值:6

延迟执行

在执行返回 Stream 的方法时,并不立刻执行,而是等返回一个非Stream的方法后才执行。因为拿到 Stream 并不能直接用,而是需要处理成一个常规类型。

java">@Test
public void laziness(){List<String> strings = Arrays.asList("abc", "def", "gkh", "abc");Stream<Integer> stream = strings.stream().filter(new Predicate() {@Overridepublic boolean test(Object o) {System.out.println("Predicate.test 执行");return true;}});System.out.println("count 执行");stream.count();
}
/*-------执行结果--------*/
count 执行
Predicate.test 执行
Predicate.test 执行
Predicate.test 执行
Predicate.test 执行

filter 中的方法并没有立刻执行,而是等调用count()方法后才执行

并行流

并行 parallelStream 在使用方法上和串行一样。主要区别是parallelStream可多线程执行,是基于ForkJoin框架实现的。
这里可以简单的理解它是通过线程池来实现的,这样就会涉及到线程安全,线程消耗等问题

java">@Test
public void parallelStreamTest(){List<Integer> numbers = Arrays.asList(1, 2, 5, 4);numbers.parallelStream() .forEach(num->System.out.println(Thread.currentThread().getName()+">>"+num));
}
//执行结果
main>>5
ForkJoinPool.commonPool-worker-2>>4
ForkJoinPool.commonPool-worker-11>>1
ForkJoinPool.commonPool-worker-9>>2

Optional

该类提供了一种用于表示可选值而非空引用的类级别解决方案
就是一种针对 NPE(NullPointerException) 解决方案

创建对象

  1. 可以使用静态方法empty()创建一个空的 Optional 对象
java">Optional<String> empty = Optional.empty();
System.out.println(empty); // 输出:Optional.empty
  1. 可以使用静态方法of()创建一个非空的 Optional 对象
java">Optional<String> opt = Optional.of("你好");
System.out.println(opt); // 输出:Optional[你好]

传递给 of() 方法的参数必须是非空的,也就是说不能为 null,否则仍然会抛出 NullPointerException

  1. 可以使用静态方法ofNullable()创建一个即可空又可非空的Optional对象
java">String name = null;
Optional<String> optOrNull = Optional.ofNullable(name);
System.out.println(optOrNull); // 输出:Optional.empty

ofNullable() 方法内部有一个三元表达式:

  • 如果为参数为 null,则返回私有常量 EMPTY;
  • 否则使用 new 关键字创建了一个新的 Optional 对象.

判断值是否存在

通过方法 isPresent() 判断一个Optional对象是否存在,如果存在,该方法返回 true,否则返回 false——取代了 obj != null 的判断

java">Optional<String> opt = Optional.of("hello");
System.out.println(opt.isPresent()); // 输出:trueOptional<String> optOrNull = Optional.ofNullable(null);
System.out.println(optOrNull.isPresent()); // 输出:false

Java 11 后还可以通过方法 isEmpty() 判断与 isPresent() 相反的结果

非空表达式

ifPresent() 可以直接将 Lambda 表达式传递给该方法

java">Optional<String> opt = Optional.of("hello");
opt.ifPresent(str -> System.out.println(str.length()));

Java 9 后还可以通过方法 ifPresentOrElse(action, emptyAction) 执行两种结果,非空时执行action,空时执行 emptyAction

java">Optional<String> opt = Optional.of("hello");
opt.ifPresentOrElse(str -> System.out.println(str.length()), () -> System.out.println("为空"));

设置默认值

在创建(获取) Optional 对象的时候,需要一个默认值

orElse

orElse() 方法用于返回包裹在 Optional 对象中的值,如果该值不为 null,则返回;否则返回默认值

java">String nullName = null;
String name = Optional.ofNullable(nullName).orElse("hello");
System.out.println(name); // 输出:hello

orElseGet

参数类型不同。如果 Optional 对象中的值为 null,则执行参数中的函数

java">String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(()->"hello");
System.out.println(name); // 输出:hello

获取值

建议 orElseGet() 方法获取Optional对象的值
使用get() 方法的话,因为假如 Optional 对象的值为 null,该方法会抛出NoSuchElementException异常

过滤值 filter

filter() 方法的参数类型为 Predicate(Java 8 新增的一个函数式接口),也就是说可以将一个 Lambda 表达式传递给该方法作为条件
如果表达式的结果为 false,则返回一个 EMPTY Optional 对象,否则返回过滤后的 Optional 对象

java">Predicate<String> len6 = pwd -> pwd.length() > 6;
Predicate<String> len10 = pwd -> pwd.length() < 10;password = "1234567";
Optional<String> opt = Optional.ofNullable(password);
boolean result = opt.filter(len6.and(len10)).isPresent();
System.out.println(result);

转换值 map

将原有 Optional 对象转换为一个新的 Optional 对象,原有的 Optional 对象不会更改

java">// 创建一个非空的Optional
Optional<String> optionalString = Optional.of("Hello");
// 使用map方法将String转换为大写形式
Optional<String> optionalUpperCase = optionalString.map(String::toUpperCase);
// 输出转换后的结果
optionalUpperCase.ifPresent(System.out::println); // 输出: HELLO
// 创建一个空的Optional
Optional<String> emptyOptional = Optional.empty();
// 使用map方法尝试转换空Optional中的值
Optional<String> emptyOptionalResult = emptyOptional.map(String::toUpperCase);
// 输出空Optional的结果
emptyOptionalResult.ifPresent(System.out::println); // 不会执行,因为没有值

http://www.ppmy.cn/news/1454704.html

相关文章

Py深度学习基础|python中类的特殊方法-__getitem__()

1.基本介绍 在Python中&#xff0c;__getitem__是一个特殊方法&#xff08;也常被称为“魔术方法”&#xff0c;即双下划线方法&#xff09;&#xff0c;它使一个类的实例对象能够支持通过键来获取其内部数据&#xff0c;类似于操作列表、元组或字典的方式。当你尝试使用方括号…

OpenGL 的内置矩阵种种

文章目录 一、说明二、glMatrixMode的三个选项三、OpenGL 矩阵操作四、入栈和弹出矩阵五、设置内置 OpenGL 矩阵六、矩阵设置异端七、正常矩阵 一、说明 内置 OpenGL有几个重要矩阵&#xff0c;围绕这几个矩阵&#xff0c;OpenGL 有一小组 矩阵操作。在本例中为 glMatrixMode&a…

RFC 791 (1)-导论

目录 浅论 IP是啥 IP可以管啥 操作 范例查看 提示&#xff1a;本系列将会开始RFC文档阅读&#xff0c;这里会给出我的一些笔记 浅论 我们这篇RFC文档描述的是IP和ICMP协议&#xff0c;我们都知道&#xff0c;在传统的OSI七层或者是现在被简化的五层&#xff1a;应用层&…

Docker 入门与实践:从零开始构建容器化应用环境

Docker 一、docker常用命令docker ps 格式化输出Linux设置命令别名 二、数据卷相关命令挂载到默认目录&#xff08;/var/lib/docker&#xff09;挂载到本地目录 三、自定义镜像Dockerfile构建镜像的命令 四、网络自定义网络 五、DockerCompose相关命令 一、docker常用命令 dock…

知识图谱实用网站推荐

​ 知识图谱是一种用于描述和组织的图形化表示方法&#xff0c;它是一种基于语义的知识表示方式&#xff0c;可以将各种实体、概念、属性以及关系等知识元素以图谱的形式进行展示和组织。 本文给大家分享了如何实现知识图谱中图关系的构建&#xff0c;以下是相关网站&#xff…

从新手到聊天专家:ChatGPT对话技巧全解析

关于为什么AI未能为你所用&#xff0c;许多人在拿到GPT后的首个问题便是&#xff1a;使用不佳。 这意味着&#xff0c;你可能会觉得ChatGPT的回答过于空洞&#xff0c;缺乏足够的参考价值。 其次&#xff0c;一个常见的问题是&#xff1a;不知如何利用。 即便拥有了GPT&#…

OpenHarmony实战开发-动画曲线、如何实现动画衔接

UI界面除了运行动画之外&#xff0c;还承载着与用户进行实时交互的功能。当用户行为根据意图变化发生改变时&#xff0c;UI界面应做到即时响应。例如用户在应用启动过程中&#xff0c;上滑退出&#xff0c;那么启动动画应该立即过渡到退出动画&#xff0c;而不应该等启动动画完…

Redis Cluster集群方案什么情况下会导致整个集群不可用?

Redis 没有使用哈希一致性算法&#xff0c;而是使用哈希槽。 Redis 中的哈希槽一共有16384个&#xff0c;计算给定 密钥的哈希槽&#xff0c;我们只需要对密钥的 CRC16 去取 16384。假设集群中有A、B、C三个集群节点&#xff0c; 不存在复制模式下&#xff0c;每个集群的节点包…