字节码之 Lambda 表达式底层原理

news2025/2/1 9:47:50

文章目录

  • 0.前言
    • 0. lambda程序示例
    • 1. 编译程序:
    • 2. 使用 `javap` 分析字节码
    • 3. 输出字节码
    • 4. 分析指令
  • 1. Lambda 表达式的字节码实现
    • 1.1 什么是invokedynamic 指令
      • `invokedynamic` 的工作原理
      • 为何 `invokedynamic` 如此特殊?
    • 1.2 bootstrap method 详解
    • 1.1 Lambda 表达式在字节码层面的呈现
  • 2. Lambda 表达式的动态生成过程
    • 2.1. 调用动态引导方法生成 Lambda 对象
    • 2.2. MethodHandle 与 Lambda 的关系
      • LambdaMetafactory如何工作
    • 2.3. 动态生成过程的优势和意义
      • 2.3.1 优化
          • 举例:
      • 2.3.2 灵活性
      • 2.3.3 减少膨胀
          • 举例:
      • 2.3.4 支持动态语言
      • 3. Lambda 表达式的性能考虑
        • 1. **JIT编译器的优化**
        • 2. **内存使用分析**
  • 3. 性能对比与实践建议
        • 1. 使用传统的 for 循环
        • 2. 使用 Stream API 和 lambda 表达式
        • 为什么上面使用 Stream API 和 Lambda 表达式带来的性能影响呢
    • 性能对比
    • 实践建议
  • 4. 参考文档

0.前言

前两章我们了解了JVM之所以夸平台的一个重要特性《字节码指令详解》、并且通过javap 反编译了一些常见的java 语句的字节码指令(《常见java语句的底层原理》),了解了之所以这些语句举报那样的特性。今天我们继续学以致用从字节码指令层面聊聊Lambda表达式的底层原理。
在这里插入图片描述


首先我们写一个简单的lambda表达式,并用javap 命令反编译成字节码指令。

0. lambda程序示例

import java.util.function.Consumer;

public class LambdaExample {
    public static void main(String[] args) {
        Consumer<String> consumer = (s) -> System.out.println(s);
        consumer.accept("Hello, Lambda!");
    }
}

定义一个 Consumer,它接受一个字符串参数,并将其打印到控制台。然后,我们调用了 accept 方法,传入字符串 “Hello, Lambda!”。接下来,我们将使用 javac 编译这个程序,并使用 javap 分析编译后的字节码。

1. 编译程序:

javac LambdaExample.java

2. 使用 javap 分析字节码

javap -v -p LambdaExample

这将生成包含字节码的输出。我们只关注 main 方法部分的字节码指令,把这部分拷出来单独分析。

3. 输出字节码

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: invokedynamic #2,  0    //可以看到invokedynamic 指令,这是我们本章节的重点
         5: astore_1
         6: aload_1
         7: ldc           #3            // String Hello, Lambda 
         9: invokeinterface #4,  2     // InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V
        14: return

4. 分析指令

  • 0: invokedynamic #2, 0:这里是 invokedynamic 指令。它用于生成 Lambda 表达式的实例。引导方法在运行时确定 Lambda 的实际实现,并返回一个指向该实现的方法句柄的 CallSite
  • 5: astore_1:将 invokedynamic 返回的 Consumer 实例存储在局部变量表中(索引为1)。
  • 6: aload_1:将局部变量表中的 Consumer 实例加载到操作数栈。
  • 7: ldc #3:将字符串 “Hello, Lambda!” 加载到操作数栈。
  • 9: invokeinterface #4, 2:调用 Consumer 接口的 accept 方法,将字符串传递给 Lambda 表达式。
  • 14: return:返回,结束 main 方法。

从字节码中,可以看到 invokedynamic 指令用于初始化 Lambda 表达式,并创建 Consumer 实例。这里的 invokedynamic 指令使得 Lambda 表达式的实现可以在运行时动态生成,而不是在编译时生成匿名内部类。

所以本章节我们着重了解 invokedynamicbootstrap method(引导方法)。

1. Lambda 表达式的字节码实现

在深入探讨关于“字节码之Lambda表达式底层原理”之前,我们需要先了解一下invokedynamic指令和引导方法(bootstrap method),我觉得这个是相对比较关键的两个知识点, 因为它们在Lambda表达式的底层实现中发挥着不可或缺的角色。下面我们将深入讨论这两者

1.1 什么是invokedynamic 指令

invokedynamic(动态方法调用)是Java 7引入的一个新的字节码指令,目的是为了支持动态类型语言在Java平台上的执行。这个指令特别适用于执行那些在编译时类型未知的方法调用。在Lambda表达式中,invokedynamic用于动态地生成和调用lambda表达式的实现。

与其他方法调用指令(如invokevirtual, invokestatic等)不同,invokedynamic不是固定地调用一个预定义的方法。相反,它依赖于引导方法(bootstrap method)来确定实际被调用的方法。

invokedynamic 的工作原理

  1. 引导方法的调用

    • 当 JVM 首次遇到某个特定的 invokedynamic 指令时,它会调用一个预先定义好的引导方法(bootstrap method)。
    • 这个引导方法返回一个方法句柄,指向真正要被调用的方法。
  2. 动态解析方法

    • 不同于其他指令在编译时确定要调用的方法,invokedynamic 依赖于引导方法在运行时动态解析并确定要调用的方法。
    • 一旦方法被确定,invokedynamic 指令将会调用这个方法,而不是固定地调用一个预定义的方法。
  3. 方法句柄的缓存

    • 为了提高性能,一旦引导方法返回了一个方法句柄,这个句柄将会被 JVM 缓存起来。这意味着,同一个 invokedynamic 指令的后续调用会直接使用这个缓存的方法句柄,而不需要再次触发引导方法。

为何 invokedynamic 如此特殊?

  • 动态语言的支持invokedynamic 是在 Java 7 中为了更好地支持 JVM 上的动态语言而引入的。传统的方法调用指令不够灵活,不能满足动态语言的需求,而 invokedynamic 提供了一种高效且灵活的方法调用机制。

  • Lambda 表达式的实现:在 Java 8 中引入的 Lambda 表达式,在底层也使用了 invokedynamic 来实现。通过这种方式,JVM 可以在运行时为 Lambda 表达式动态生成匿名类,而不是在编译时生成。

  • 性能优化:与传统的反射方式相比,invokedynamic 提供了一种更加高效的动态方法调用方式。反射在每次调用时都需要进行一系列的检查和解析,而 invokedynamic 通过方法句柄的缓存机制,减少了这些额外的开销。


1.2 bootstrap method 详解

引导方法是Java 7中引入的一个概念,用于支持invokedynamic指令。当Java虚拟机首次遇到某个特定的invokedynamic指令时,它会调用相应的引导方法。引导方法的任务是为invokedynamic指令提供一个方法句柄(MethodHandle),该方法句柄指向真正应该被invokedynamic指令调用的方法。

引导方法是由编译器在编译时生成的,常常放在类文件的方法表中。但是,它们与普通的Java方法有所不同,因为它们可以接受额外的参数,并且可以返回MethodHandle或其它调用站点特定的数据类型。


1.1 Lambda 表达式在字节码层面的呈现

当编译包含lambda表达式的Java代码时,编译器不会为lambda表达式生成常规的方法。相反,它将生成一个invokedynamic指令和一个引导方法。当该invokedynamic指令被执行时,相应的引导方法被调用,用于生成lambda表达式的实际方法实现并返回一个指向它的方法句柄。

字节码示例分析

考虑以下Java代码:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));

当上述代码被编译时,对于lambda表达式name -> System.out.println(name),编译器不会生成一个常规的方法。而是生成如下的伪字节码:

invokedynamic Lambda$0:()Ljava/util/function/Consumer; 

这里的Lambda$0是由编译器生成的引导方法。当invokedynamic指令被执行时,Lambda$0会被调用,它返回一个方法句柄,该句柄指向一个实现了该lambda表达式的方法。

此外,编译器还会在类文件中生成一个Lambda$0的实际方法实现。这个方法包含lambda表达式的代码,即System.out.println(name)

这只是一个简化的示例,实际的字节码可能会更复杂,但它给出了lambda表达式如何在字节码层面上被表示的基本概念。

lambda表达式在字节码层面的实现充分利用了Java 7引入的invokedynamic指令和引导方法的能力,为动态语言和新的Java特性提供了强大的支持。


2. Lambda 表达式的动态生成过程

2.1. 调用动态引导方法生成 Lambda 对象

  • 背景:与传统的 Java 方法调用不同(如 invokevirtualinvokestatic),invokedynamic 指令并不直接调用一个特定的方法。而是依靠所谓的引导方法来动态确定应该调用的方法。

  • 过程

    1. 当 JVM 首次遇到某个特定的 invokedynamic 指令,它会触发引导方法。
    2. 引导方法使用 MethodHandles.Lookup 和其他参数来创建并返回一个 MethodHandle 对象,这个对象指向真正要被调用的方法。
    3. JVM 会缓存这个 MethodHandle,以便后续调用,从而避免多次触发引导方法。

2.2. MethodHandle 与 Lambda 的关系

  1. MethodHandle 简介
    MethodHandle 是 Java 7 引入的一个功能,位于 java.lang.invoke 包中。它可以看作是对 Java 中的方法或构造函数的直接引用,可以对任何方法进行高效的、类型安全的、动态的方法调用。方法句柄是一个强类型的,类似于指针的对象。它可以直接引用底层方法、构造函数或字段,无需使用反射。

    如之前所述,Lambda 表达式在 JVM 中使用 invokedynamic 指令来实现。invokedynamic 指令在首次执行时会调用一个特定的引导方法,这个引导方法通常会返回一个 MethodHandle,指向 Lambda 表达式的实际实现。

  2. 与 Lambda 的关系

    1. 当引导方法被触发,它为特定的 lambda 表达式生成一个 MethodHandle
    2. 这个 MethodHandle 直接引用了 lambda 表达式的实体代码。
    3. invokedynamic 指令再次执行时,它直接使用这个方法句柄来调用 lambda 表达式,从而实现快速和高效的调用。
  3. 与反射的比较
    与 Java 的反射相比,MethodHandle 提供了更好的性能和更多的功能。它可以轻易地适应方法的签名变化,并且可以组合、转化和适应其他的 MethodHandle

  4. LambdaMetafactory 的主要作用
    LambdaMetafactory 是 Java 8 引入的,位于 java.lang.invoke 包中,它在 JVM 内部起到了关键的作用,负责将 lambda 表达式转化为具体的方法调用。以下是 LambdaMetafactory 的核心功能和它如何与 lambda 表达式的实现有关:

  5. Lambda 表达式转化:在 Java 8 之前,当创建一个匿名内部类来实现一个接口时,JVM 会为这个匿名内部类生成一个独立的 .class 文件。但在 Java 8 中,使用 lambda 表达式时,并不是通过传统的匿名内部类来实现的。而是通过 LambdaMetafactory 在运行时动态地生成这些实现。

  6. invokedynamic 指令支持:Java 7 引入了 invokedynamic 指令,这是一个支持动态类型语言的字节码指令。在 Java 8 中,lambda 表达式的实现也利用了这个指令。当首次调用一个 lambda 表达式时,invokedynamic 指令会触发引导方法,而这个引导方法通常会使用 LambdaMetafactory 来生成一个方法句柄(MethodHandle),这个方法句柄指向 lambda 表达式的实际实现。

LambdaMetafactory如何工作

当在代码中写下一个 lambda 表达式时,编译器并不会直接生成对应的方法实现。相反,它会为这个 lambda 生成一个 invokedynamic 调用。在运行时,当这个 invokedynamic 调用被执行的时候,它会触发一个特定的引导方法。这个引导方法使用 LambdaMetafactory 来为该 lambda 生成一个具体的方法句柄。

例如,考虑下面的 lambda 表达式:

Runnable r = () -> System.out.println("Hello World");

对于上面的 lambda,LambdaMetafactory 将会为这个 lambda 动态生成一个 Runnable 的实例。当这个实例的 run 方法被调用时,它实际上会执行 System.out.println("Hello World")

举例说明
通过一个简单的例子来深入了解 MethodHandleLambdaLambdaMetafactory 之间的关系。

这个简单的函数式接口 应该不用多说了。

@FunctionalInterface
interface Greeting {
    void sayHello(String name);
}

在主代码中,可能会基于这个接口创建一个 lambda 表达式

public class LambdaMetafactoryExample {
    public static void main(String[] args) {
        Greeting greeting = (name) -> System.out.println("Hello, " + name);
        greeting.sayHello("World");
    }
}

当这段代码被编译和运行时,JVM 不会为 lambda 表达式生成一个传统的匿名内部类,如我们可能预期的那样。相反,它使用 invokedynamic 指令,而 lambda 的动态创建是在运行时处理的。

在幕后,LambdaMetafactory 在将的 lambda 表达式转化为 Greeting 接口的具体实例中起到了重要作用。大致的过程如下:

  1. 引导方法调用invokedynamic 指令首先会调用引导方法。在 lambdas 的情况下,引导方法通常是 LambdaMetafactory.metafactory

  2. MethodHandle 创建LambdaMetafactory.metafactory 方法将接受几个参数,其中之一是一个 MethodHandle,指向当 lambda 被调用时应该执行的实际方法。在我们的例子中,这个方法句柄会指向实际执行 System.out.println("Hello, " + name) 的方法。

  3. Lambda 实例生成:使用上述信息,LambdaMetafactory 会生成一个新的 Greeting 接口的实例,当该实例的 sayHello 方法被调用时,它将实际上执行 System.out.println("Hello, " + name)

此过程是在 JVM 的底层发生的,对于 Java 开发者来说,这一切都是透明的。但了解这些底层细节可以帮助我们理解 lambda 在 JVM 中是如何工作的,以及它如何与 MethodHandleinvokedynamic 指令相关联的。

2.3. 动态生成过程的优势和意义

2.3.1 优化

由于引导方法只在首次遇到 invokedynamic 指令时被调用,后续的 lambda 表达式调用将直接使用方法句柄,这提高了效率。

LambdaMetafactoryinvokedynamic 指令的合作使用,优化了 lambda 表达式的调用。引导方法仅在首次遇到 invokedynamic 指令时被调用。一旦方法句柄被创建并链接,后续的 lambda 表达式调用会直接使用方法句柄,这极大提高了效率。

举例:

考虑如下的 lambda 表达式:

List<String> items = Arrays.asList("A", "B", "C");
items.forEach(item -> System.out.println(item));

在这个例子中,lambda (item -> System.out.println(item)) 在首次执行时通过 invokedynamic 指令调用引导方法。引导方法通过 LambdaMetafactory 创建一个方法句柄。在后续的迭代中,已经创建的方法句柄将被直接使用。

2.3.2 灵活性

动态生成 lambda 表达式允许 JVM 在运行时做出决策,这为即时编译器(JIT)提供了优化的机会。
由于 LambdaMetafactoryinvokedynamic 指令允许在运行时动态生成 lambda 表达式的方法句柄,JVM 可以在运行时做出优化决策。这种动态性给即时编译器(JIT)留下了优化的空间,可以根据实际运行情况进行特定优化。

如果某个 lambda 表达式在应用程序中被频繁使用,JIT 编译器可以选择为其生成优化的机器代码,提高运行效率。

2.3.3 减少膨胀

在编译时,不需要为每一个 lambda 表达式生成一个新的匿名内部类。这减少了类文件的大小和数量,进而也降低了加载这些类的开销。

在使用 lambda 表达式时,由于不会为每一个 lambda 生成一个新的 .class 文件(即不会产生匿名内部类),因此减少了类文件的数量和大小,降低了加载这些类的开销。

举例:
// 使用 Lambda 表达式
Runnable r = () -> System.out.println("Hello");

// 在 Java 8 之前,我们可能会这样做
Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
};

在第二个例子中,会生成一个额外的 .class 文件,而使用 lambda 表达式则不会。

2.3.4 支持动态语言

invokedynamic 指令和引导方法的设计不仅仅是为了支持 Java 的 lambda 表达式,它也可以支持其他在 JVM 上运行的动态语言,增强了 JVM 作为多语言平台的能力。

除了 Java,像 GroovyKotlin 等其他 JVM 语言也可以利用 invokedynamicLambdaMetafactory 的设施,优化其在 JVM 上的执行。

3. Lambda 表达式的性能考虑

Java 8 引入的 lambda 表达式不仅简化了代码,还为性能优化提供了新的机会。在考虑 lambda 表达式的性能时,我们需要考虑以下几点:

1. JIT编译器的优化

当 lambda 表达式在运行时被频繁调用,即时编译器 (JIT) 可以对其生成高度优化的机器代码。这是由于 lambda 的实现使用 invokedynamicLambdaMetafactory,允许 JIT 进行更多的运行时优化。

举例

如果你的应用有一个经常被调用的 lambda 表达式,如一个常见的数据处理管道中的操作,JIT 可以为这个特定的 lambda 生成高效的机器代码,使其运行得更快。

2. 内存使用分析

使用 lambda 表达式代替匿名内部类可以减少内存的使用。由于 lambda 不会生成传统的 .class 文件,并且其运行时的动态生成也比匿名内部类更高效,因此它们通常使用更少的内存。

举例

考虑一个常用的场景,你可能在一个集合上使用一个过滤器:

list.stream().filter(item -> item.startsWith("A")).collect(Collectors.toList());

这里的 lambda 表达式会比一个相应的匿名内部类使用更少的内存。

3. 性能对比与实践建议

在这里插入图片描述

尽管 lambda 表达式在许多情况下具有性能优势,但这并不意味着它们在所有场合都是最佳选择。性能测试和基准测试是确定实际性能差异的关键。

例如在某些计算密集型任务中,传统的方法可能比使用流和 lambda 更快。例如,对于简单的循环,传统的 for 循环可能比使用 Stream API 和 lambda 更高效。

我们依然是举例说明

考虑我们有一个 List<Integer>,我们想找出其中所有偶数的和。我们可以使用传统的 for 循环,也可以使用 Stream API 和 lambda 表达式来实现这个功能。

1. 使用传统的 for 循环
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sum = 0;
for (int num : numbers) {
    if (num % 2 == 0) {
        sum += num;
    }
}
System.out.println(sum);

在这个例子中,我们使用了单个的 for 循环来遍历列表并计算所有偶数的和。

2. 使用 Stream API 和 lambda 表达式
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sum = numbers.stream()
                 .filter(num -> num % 2 == 0)
                 .mapToInt(Integer::intValue)
                 .sum();
System.out.println(sum);

在这个例子中,我们使用了 Stream API 和 lambda 表达式来完成同样的任务。虽然代码看起来更简洁,但由于涉及到多个中间操作(过滤、映射和汇总),可能会稍微影响性能。

为什么上面使用 Stream API 和 Lambda 表达式带来的性能影响呢
  1. 多个中间操作

    在 Stream API 中,每一个中间操作(例如 filtermap 等)都会产生一个新的 Stream,这意味着在执行后续的操作时,每一个元素都需要穿越整个操作链。对于大量数据的操作,这些额外的遍历和函数调用有可能引入额外的开销。

    例如:

   numbers.stream()
          .filter(num -> num % 2 == 0)  // 遍历1
          .map(num -> num * 2)          // 遍历2
          .sum();                       // 遍历3

在上面的代码中,每个元素需要经过三次遍历:一次过滤、一次映射和一次求和。

  1. 装箱和拆箱

    使用 Stream API 时,我们常常使用对象类型(例如 IntegerDouble 等)进行操作,这可能会引入自动装箱和拆箱的额外成本。

   numbers.stream()
          .filter(num -> num % 2 == 0)
          .mapToInt(Integer::intValue)  // 可能涉及拆箱操作
          .sum();
  1. Lambda 表达式的创建成本

    尽管 Lambda 表达式的创建和执行经过了优化(例如通过 invokedynamicLambdaMetafactory),在某些极端情况下(例如超高性能场景或大数据处理),与传统的方法调用相比,这些成本还是值得关注的。

虽然上述的几点在一些场景下可能影响性能,但在大多数情况下,这些影响相对较小,并且 Stream API 和 Lambda 表达式带来的可读性和表达能力的提升往往比这些微小的性能损耗更重要。

对于性能敏感的场景,如果有必要 我们可以通过基准测试来验证不同方法的性能表现,并据此做出合适的选择。

性能对比

所以通过上面的示例,虽然在大多数情况下,这两种方法的性能差异可以忽略不计,但在处理大量数据时,传统的 for 循环可能会稍微快一点。这是因为 for 循环的所有操作都在一个循环中完成,而 Stream API 在每个中间操作中都会遍历整个流。

然而,这并不意味着我们应该避免使用 Stream API 和 lambda 表达式。它们提供了更加简洁和函数式的编程方式,对编写可读和易维护的代码很有帮助。并且,如果我们使用 parallelStream,还可以很方便地利用多核处理器进行并行计算。

所以我觉得在选择使用哪种方法时,我们应该根据具体的情况来考虑,例如代码的可读性、简洁性,以及性能需求等因素。

实践建议

  • 代码可读性:首先,应考虑代码的可读性和维护性。Lambda 表达式和 Stream API 可以使代码更加简洁和易读。

  • 性能测试:在决定使用 lambda 还是传统方法之前,进行性能测试和基准测试。这有助于确定哪种方法更适合你的特定情况。

  • 避免过度优化:除非性能是一个关键问题,否则不要过度优化。通常,简洁、可读和可维护的代码更为重要。

4. 参考文档

  1. https://blog.51cto.com/u_15346609/5646012

  2. https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html#metafactory-java.lang.invoke.MethodHandles.Lookup-java.lang.String-java.lang.invoke.MethodType-java.lang.invoke.MethodType-java.lang.invoke.MethodHandle-java.lang.invoke.MethodType-

  3. https://www.oracle.com/technetwork/java/jvmls2013kuksen-2014088.pdf

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt之给控件添加右键菜单

一、设置控件 在对应控件的属性中&#xff0c;将contextMenuPolicy设置为CustomContextMenu。 二、添加槽函数 在对应控件上右键选择槽函数customContextMenuRequested(QPoint)。 三、在槽函数中添加右键菜单 在槽函数中输入如下代码&#xff0c;添加右键菜单。 //右键菜单 …

红帽Linux的安装和部署

目录 一、红帽Linux的安装阶段 1、下载redhat7.9的iso镜像 2、安装阶段 二、红帽Linux的配置阶段 1、第一次进入装机配置 2、进入机器后的一些配置 三、远程连接阶段 1、关闭防火墙 2、使用Xshell远程连接&#xff08;其他连接工具也行&#xff09; 1.开启SSH服务 2.连…

二十、【钢笔工具组】

文章目录 钢笔工具自由钢笔工具弯度钢笔工具 钢笔工具 钢笔工具在photoshop作图中是一款使用频率较高的路径工具,我们可以在窗口选项栏中将路径编辑栏打开&#xff0c;如果我们需要选中使用路径&#xff0c;需要用到后边的路径工具才能去拖动&#xff0c;而选择工具不能拖动&a…

9月大型语言模型研究论文总结

大型语言模型(llm)在今年发展迅速&#xff0c;随着新一代模型不断地被开发&#xff0c;研究人员和工程师了解最新进展变得非常重要。本文总结9-10月期间发布了一些重要的LLM论文。 这些论文涵盖了一系列语言模型的主题&#xff0c;从模型优化和缩放到推理、基准测试和增强性能…

Sigma中的数字增益放大/降低方法

1 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)&#xff1f;加他微信hezkz17, 本群提供音频技术答疑服务

如何快速分析一款产品?

一、何时需要对一个产品进行分析&#xff1f; 首先&#xff0c;当你刚刚融入一个新的产品团队&#xff0c;尤其是当你需要深入了解你将负责的产品时&#xff0c;分析产品就显得尤为重要。这有助于你对产品的全面理解&#xff0c;发现其中的优势和不足&#xff0c;为未来的工作提…

14.5 Socket 应用组播通信

组播通信是一种基于UDP协议的网络通信方式&#xff0c;它允许发送方将消息同时传递给多个接收方。在组播通信中&#xff0c;发送方和接收方都会加入一个共同的组播组&#xff0c;这个组播组对应一个特定的IP地址&#xff0c;所有加入该组播组的主机都能够接收到发送方发送的消息…

C++概述

一、C特色 1.C是面向对象的高级程序设计语言 2.支持数据封装&#xff0c;将数据和对该数据进行操作的函数封装在一个类中&#xff0c;对象就是某一个具体的类。即类是数据封装的工具&#xff0c;对象是数据封装的实现。 3.具有继承性 4.具有函数重载 二、拓展介绍 1.C标准&a…

基于音频SOC开发板的主动降噪ANC算法源码实现

基于音频SOC开发板的主动降噪ANC算法源码实现 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)?可加我微信hezkz17, 本群提供音频技术答疑服务,+群附加赠送降噪开发资料,

IO流:java中解码和编码出现乱码说明及代码实现

IO流&#xff1a;java中解码和编码的代码实现 一、UTF-8和GBK编码方式二、idea和eclipse的默认编码方式三、解码和编码方法四、代码实现编码解码 五、额外知识扩展 一、UTF-8和GBK编码方式 如果采用的是UTF-8的编码方式&#xff0c;那么1个英文字母 占 1个字节&#xff0c;1个…

深度学习-优化算法与梯度下降

文章目录 前置知识指数移动平均EMAL2正则&#xff08;L2 Regularization)权重衰减&#xff08;Weight Decay) 优化器SDGMomentumNAGAdagradRMSPropAdaDeltaAdamAdamW综上 学习率StepLRMultiStepLRExponentialCosineAnealingRLROP&#xff08;ReduceLRonPlateau)lambda总结 前置…

【图像处理】图像配准、图像增强和图像分割研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

网络编程基础知识总结——IP,端口,协议

目录 1. 什么是网络编程&#xff1f; 2. 网络编程的三要素 3. IP 3.1 IP地址的概念 3.2 IP地址的分类 3.3 IPv4解析 3.4 Ipv6解析 4. IPv4 的使用细节 5. 特殊IP地址 4. 端口号 5. 协议 5.1 UDP协议 5.2 TCP协议 1. 什么是网络编程&#xff1f; 总的来说就是一句…

RuntimeWarning: More than 20 figures have been opened

在画图操作结束后使用plt.close(all)语句&#xff0c;但是此时图像仍然不能正常显示&#xff0c;之前是可以正常显示的&#xff0c;然后又在最后的画图的部分添加plt.show()语句&#xff0c;可以正常显示图像并且不再出现警告了。

Android---DVM以及ART对JVM进行优化

Dalvik Dalvik 是 Google 公司自己设计用于 Android 平台的 Java 虚拟机&#xff0c;Android 工程师编写的 Java 或者 Kotlin 代码最终都是在这台虚拟机中被执行的。在 Android 5.0 之前叫作 DVM&#xff0c;5.0 之后改为 ART&#xff08;Android Runtime&#xff09;。在整个…

oracle创建数据库,导入dmp操作全家桶

背景&#xff1a;小明在一家IT公司就职&#xff0c;通过查看项目&#xff0c;公司使用的是oracle&#xff0c;几天后&#xff0c;经理要求他从服务器导入数据库到公司服务器&#xff0c;聪明的小明就开始干了起来&#xff0c;整理如下教程。 说明&#xff1a;此次演示环境oracl…

【LeetCode力扣】297. 二叉树的序列化与反序列化

目录 1、题目介绍 2、解题思路 2.1、详细过程图解 2.2、代码描述 2.3、完整代码 1、题目介绍 原题链接&#xff1a;297. 二叉树的序列化与反序列化 - 力扣&#xff08;LeetCode&#xff09; 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,null,null,4,5] 输出&#…

代码随想录算法训练营第二十二天丨 二叉树part09

669. 修剪二叉搜索树 思路 递归法 从图中可以看出需要重构二叉树&#xff0c;想想是不是本题就有点复杂了。 其实不用重构那么复杂。 在上图中我们发现节点0并不符合区间要求&#xff0c;那么将节点0的右孩子 节点2 直接赋给 节点3的左孩子就可以了&#xff08;就是把节点…

mac 移动硬盘推出

最近移动硬盘推出总是出现 磁盘无法推出&#xff0c;因为一个或多个程序正在使用它 的问题 解决办法 首先尽可能关闭所有的应用&#xff0c;然后点击左上角的 点击强制退出&#xff0c;注意其中的预览&#xff0c;这个通常就卡在了这里 然后就可以正常退出了

Python点击exe后报错:Failed to execute script xxxx问题的解决办法

最近工作在弄人脸识别的问题&#xff0c;从gitee来pull了一个但是发现报了一个Failed to execute script XXX的问题 造成这个问题的原因是执行文件exe存放的目录不对&#xff0c;可能在打包前exe文件并不是存在在这个位置。 解决方案将exe文件尝试存在在不同目录下&#xff…