[Java] 如何理解 class 文件中方法的 access flags?

背景

在日常开发过程中,我们有时候需要查看 class\text{class} class 文件的内容。如果我们对 class\text{class} class 文件的结构有基本的了解,那么就会事半功倍。由于这个话题很大,而且我自己的水平也有限,所以每次只写一个很小的主题。本文的主题是理解 class\text{class} class 文件中方法的 access flags\text{access flags} access flags。

要点

目前(JDK26), class\text{class} class 文件中字段的 access flags\text{access flags} access flags一共支持 1212 12 种类型( 如下图所示)。对每一种类型,本文都提供了对应的代码或者命令进行验证。

正文

The Java® Virtual Machine Specification 中的 4.6. Methods 小节详细介绍了 class\text{class} class 文件中 method_info\text{method\_info} method_info 的结构,它的开头是这样的 ⬇️

u2 的含义

The Java® Virtual Machine Specification 中的 Chapter 4. The class File Format 开头解释了 u2\text{u2} u2 的含义 ⬇️ (如下图红色框所示)

u2\text{u2} u2 表示 2 byte\text{2 byte} 2 byte 的无符号数

方法的 access flags\text{access flags} access flags

The Java® Virtual Machine Specification 中的 4.6. Methods 小节的开头提供了一个表格 ⬇️ (表格的名称是 Table 4.6-A. Method access and property flags

Flag Name Value Interpretation
ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package.
ACC_PRIVATE 0x0002 Declared private; accessible only within the defining class and other classes belonging to the same nest (§5.4.4).
ACC_PROTECTED 0x0004 Declared protected; may be accessed within subclasses.
ACC_STATIC 0x0008 Declared static.
ACC_FINAL 0x0010 Declared final; must not be overridden (§5.4.5).
ACC_SYNCHRONIZED 0x0020 Declared synchronized; invocation is wrapped by a monitor use.
ACC_BRIDGE 0x0040 A bridge method, generated by the compiler.
ACC_VARARGS 0x0080 Declared with variable number of arguments.
ACC_NATIVE 0x0100 Declared native; implemented in a language other than the Java programming language.
ACC_ABSTRACT 0x0400 Declared abstract; no implementation is provided.
ACC_STRICT 0x0800 In a class file whose major version number is at least 46 and at most 60: Declared strictfp.
ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code.

我对 access flags\text{access flags} access flags 的理解是:方法中的某些信息可以用布尔值来表示(例如是否为 public\text{public} public,是否为 static\text{static} static,是否为 final\text{final} final),遇到这样的信息,可以考虑将其用 access flags\text{access flags} access flags 来表示。而像 类型 这种取值空间远非布尔值所能表示的信息,则应该通过其他属性来保存。

表格中一共列举了 12\text{12} 12 种情况,我们分别来看。

66 6 种情况

请将以下代码保存为 Simple.java\text{Simple.java} Simple.java

java 复制代码
// 请注意,代码中使用了 JDK 20 中新增的 api,如果 javac 的版本不够高,编译会失败

import java.lang.reflect.Method;

public class Simple {
    private void method1() {
    }

    protected void method2() {
    }

    final void method3() {
    }

    synchronized void method4() {
    }

    public static void main(String[] args) {
        for (Method method : Simple.class.getDeclaredMethods()) {
            String message = String.format("method [%s] has access flags: %s",
                    method.getName(), method.accessFlags());
            System.out.println(message);
        }
    }
}

使用如下的命令可以编译 Simple.java\text{Simple.java} Simple.java

bash 复制代码
javac Simple.java

编译之后,当前目录会多出一个名为 Simple.class\text{Simple.class} Simple.class 的文件。下方的命令可以运行 Simple\text{Simple} Simple 类中的 main\text{main} main 方法

bash 复制代码
java Simple

在我的电脑上,运行结果如下 ⬇️

text 复制代码
method [main] has access flags: [PUBLIC, STATIC]
method [method1] has access flags: [PRIVATE]
method [method2] has access flags: [PROTECTED]
method [method3] has access flags: [FINAL]
method [method4] has access flags: [SYNCHRONIZED]

66 6 个 access flags\text{access flags} access flags 都出现了(其中 main\text{main} main 方法有两个 access flags\text{access flags} access flags 处于置位状态)

使用如下的命令可以查看 Simple.class\text{Simple.class} Simple.class 的详细内容。

bash 复制代码
javap -v -p Simple

完整的结果略有点长,我把和本文相关的部分复制到下方了 ⬇️ (... 表示本文不关心的内容)

text 复制代码
...
public class Simple
...
{
  public Simple();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0

  private void method1();
    descriptor: ()V
    flags: (0x0002) ACC_PRIVATE
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 7: 0

  protected void method2();
    descriptor: ()V
    flags: (0x0004) ACC_PROTECTED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 10: 0

  final void method3();
    descriptor: ()V
    flags: (0x0010) ACC_FINAL
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 13: 0

  synchronized void method4();
    descriptor: ()V
    flags: (0x0020) ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 16: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    ...
}
...

通过查看 javap\text{javap} javap 命令的输出,也可以验证,前 66 6 个 access flags\text{access flags} access flags 都出现了。

77 7 种情况: ACC_BRIDGE\text{ACC\_BRIDGE} ACC_BRIDGE

我向 trae 询问了"什么情况下,javac 编译器会生成 bridge method?"这个问题。它举了些例子。在此基础上,我写了如下的代码 ⬇️ (请将其保存为 Case7.java\text{Case7.java} Case7.java)

java 复制代码
// 请注意,代码中使用了 JDK 20 中新增的 api,如果 javac 的版本不够高,编译会失败

import java.lang.reflect.Method;
import java.util.concurrent.Callable;

public class Case7 implements Callable<String> {

    @Override
    public String call() {
        return "";
    }

    public static void main(String[] args) {
        for (Method method : Case7.class.getDeclaredMethods()) {
            String message = String.format("method [%s] has access flags: %s",
                    method.getName(), method.accessFlags());
            System.out.println(message);
        }
    }
}

使用如下的命令可以编译 Case7.java\text{Case7.java} Case7.java

bash 复制代码
javac -parameters Case7.java

编译之后,当前目录会多出一个名为 Case7.class\text{Case7.class} Case7.class 的文件。下方的命令可以运行 Case7\text{Case7} Case7 类中的 main\text{main} main 方法

bash 复制代码
java Case7

在我的电脑上,运行结果如下 ⬇️

text 复制代码
method [main] has access flags: [PUBLIC, STATIC]
method [call] has access flags: [PUBLIC]
method [call] has access flags: [PUBLIC, BRIDGE, SYNTHETIC]

ACC_BRIDGE\text{ACC\_BRIDGE} ACC_BRIDGE 这个 access flag\text{access flag} access flag 出现了。使用如下的命令可以查看 Case7.class\text{Case7.class} Case7.class 的详细内容。

bash 复制代码
javap -v -p Case7

完整的结果略有点长,我把和本文相关的部分复制到下方了 ⬇️ (... 表示本文不关心的内容)

text 复制代码
...
public class Case7 extends java.lang.Object implements java.util.concurrent.Callable<java.lang.String>
...
{
  public Case7();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 6: 0

  public java.lang.String call();
    descriptor: ()Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: ldc           #7                  // String
         2: areturn
      LineNumberTable:
        line 10: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      ...

  public java.lang.Object call() throws java.lang.Exception;
    descriptor: ()Ljava/lang/Object;
    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #47                 // Method call:()Ljava/lang/String;
         4: areturn
      LineNumberTable:
        line 6: 0
    Exceptions:
      throws java.lang.Exception
}
...

基于 javap\text{javap} javap 命令提供的内容,可以手动反编译 Case7.class\text{Case7.class} Case7.class,我反编译的结果如下

java 复制代码
// 以下内容是我手动反编译的结果,仅供参考

public class Case7 implements java.util.concurrent.Callable<String> {
  public Case7() {
    super();
  }

  // 这个 call 方法返回 String,另一个 call 方法返回 Object,在 java 代码中不允许这样的重载,但是在 class 文件中可以
  public String call() {
    return "";
  }

  public static void main(java.lang.String[] args) {
    // 内容略
  }
  
  // 下面这个方法既是 bridge 方法,又是合成方法,在 java 代码里看不到它
  // 这个 call 方法返回 Object,另一个 call 方法返回 String,在 java 代码中不允许这样的重载,但是在 class 文件中可以
  public Object call() throws Exception {
    return call();
  }
}

88 8 种情况: ACC_VARARGS\text{ACC\_VARARGS} ACC_VARARGS

支持变长参数的方法会用到这个 access flag\text{access flag} access flag。请将其保存为 Case8.java\text{Case8.java} Case8.java

java 复制代码
// 请注意,代码中使用了 JDK 20 中新增的 api,如果 javac 的版本不够高,编译会失败

import java.lang.reflect.Method;

public class Case8 { 

    int calcSum(int... nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        } 
        return sum;
    }

    public static void main(String[] args) {
        for (Method method : Case8.class.getDeclaredMethods()) {
            String message = String.format("method [%s] has access flags: %s",
                    method.getName(), method.accessFlags());
            System.out.println(message);
        }
    }
}

使用如下的命令可以编译 Case8.java\text{Case8.java} Case8.java

bash 复制代码
javac -parameters Case8.java

编译之后,当前目录会多出一个名为 Case8.class\text{Case8.class} Case8.class 的文件。下方的命令可以运行 Case8\text{Case8} Case8 类中的 main\text{main} main 方法

bash 复制代码
java Case8

在我的电脑上,运行结果如下 ⬇️

text 复制代码
method [main] has access flags: [PUBLIC, STATIC]
method [calcSum] has access flags: [VARARGS]

ACC_VARARGS\text{ACC\_VARARGS} ACC_VARARGS 这个 access flag\text{access flag} access flag 出现了。使用如下的命令可以查看 Case8.class\text{Case8.class} Case8.class 的详细内容。

bash 复制代码
javap -v -p Case8

完整的结果略有点长,我把和本文相关的部分复制到下方了 ⬇️ (... 表示本文不关心的内容)

text 复制代码
...
public class Case8
...
{
  public Case8();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      ...

  int calcSum(int...);
    descriptor: ([I)I
    flags: (0x0080) ACC_VARARGS
    Code:
      ...

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      ...
}
...

通过查看 javap\text{javap} javap 命令的输出,也可以验证, ACC_VARARGS\text{ACC\_VARARGS} ACC_VARARGS 这个 access flag\text{access flag} access flag 出现了。

99 9 种情况: ACC_NATIVE\text{ACC\_NATIVE} ACC_NATIVE

Object 类 中就有 native\text{native} native 方法,例如 getClass() 方法 ⬇️

通过执行以下命令,我们就能看到 Object\text{Object} Object 类对应的 class\text{class} class 文件的详细内容 ⬇️

bash 复制代码
javap -v -p 'java.lang.Object'

但是完整的结果比较长,这里我们只关心 getClass() 方法,所以可以用下方的命令

bash 复制代码
javap -v -p 'java.lang.Object' | grep --after-context=2 'getClass()'

该命令的运行结果如下

text 复制代码
  public final native java.lang.Class<?> getClass();
    descriptor: ()Ljava/lang/Class;
    flags: (0x0111) ACC_PUBLIC, ACC_FINAL, ACC_NATIVE

容易验证, ACC_NATIVE\text{ACC\_NATIVE} ACC_NATIVE 这个 access flag\text{access flag} access flag 出现了。

1010 10 种情况: ACC_ABSTRACT\text{ACC\_ABSTRACT} ACC_ABSTRACT

不难联想到,接口中定义的方法都是抽象的(这里不考虑接口中的默认方法,以及静态方法)。以 java.lang.Runnable 接口为例,其中的 run() 方法就是一个抽象方法。 通过执行以下命令,我们就能看到 java.lang.Runnable 接口对应的 class\text{class} class 文件的详细内容 ⬇️

bash 复制代码
javap -v -p 'java.lang.Runnable'

运行结果如下(开头几行已略去)

text 复制代码
public interface java.lang.Runnable
  minor version: 0
  major version: 65
  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
  this_class: #1                          // java/lang/Runnable
  super_class: #3                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 1, attributes: 2
Constant pool:
   #1 = Class              #2             // java/lang/Runnable
   #2 = Utf8               java/lang/Runnable
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               run
   #6 = Utf8               ()V
   #7 = Utf8               SourceFile
   #8 = Utf8               Runnable.java
   #9 = Utf8               RuntimeVisibleAnnotations
  #10 = Utf8               Ljava/lang/FunctionalInterface;
{
  public abstract void run();
    descriptor: ()V
    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
}
SourceFile: "Runnable.java"
RuntimeVisibleAnnotations:
  0: #10()
    java.lang.FunctionalInterface

容易验证, ACC_ABSTRACT\text{ACC\_ABSTRACT} ACC_ABSTRACT 这个 access flag\text{access flag} access flag 出现了。

1111 11 种情况: ACC_STRICT\text{ACC\_STRICT} ACC_STRICT

JEP 306: Restore Always-Strict Floating-Point Semantics 的影响,在高于或等于 17\text{17} 17 的 JDK 版本中, java\text{java} java 代码中使用 strictfp\text{strictfp} strictfp 关键字变成了多余的行为。

参考 4.6. Methods 小节中 Table 4.6-A. Method access and property flags )的描述,要想看到 ACC_ABSTRACT\text{ACC\ABSTRACT} ACC_ABSTRACT 这个 access flag\text{access flag} access flag, class\text{class} class 文件的主版本号 Vmajor V{major} Vmajor 需要满足 46≤ Vmajor ≤60 46 \le V_{major} \le 60 46≤Vmajor≤60 ⬇️

请将以下代码保存为 Case11.java\text{Case11.java} Case11.java

java 复制代码
public class Case11 { 

    strictfp double calcSum(double a, double b) {
        return a + b;
    }
}

用以下命令可以将其编译为指定版本(即 JDK8)的 class\text{class} class 文件

bash 复制代码
javac --source 8 --target 8 Case11.java

执行这个命令时,我看到了如下的警告

text 复制代码
警告: [options] 未与 -source 8 一起设置引导类路径
警告: [options] 源值 8 已过时,将在未来发行版中删除
警告: [options] 目标值 8 已过时,将在未来发行版中删除
警告: [options] 要隐藏有关已过时选项的警告, 请使用 -Xlint:-options。
4 个警告

执行下方的命令可以看到 Case11.class\text{Case11.class} Case11.class 文件的详细内容

bash 复制代码
javap -v -p Case11

运行结果如下(开头几行已略去)

text 复制代码
public class Case11
  minor version: 0
  major version: 52
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #7                          // Case11
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Class              #8             // Case11
   #8 = Utf8               Case11
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               calcSum
  #12 = Utf8               (DD)D
  #13 = Utf8               SourceFile
  #14 = Utf8               Case11.java
{
  public Case11();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  strictfp double calcSum(double, double);
    descriptor: (DD)D
    flags: (0x0800) ACC_STRICT
    Code:
      stack=4, locals=5, args_size=3
         0: dload_1
         1: dload_3
         2: dadd
         3: dreturn
      LineNumberTable:
        line 4: 0
}
SourceFile: "Case11.java"

容易验证, ACC_STRICT\text{ACC\_STRICT} ACC_STRICT 这个 access flag\text{access flag} access flag 出现了。

1212 12 种情况: ACC_SYNTHETIC\text{ACC\_SYNTHETIC} ACC_SYNTHETIC

请将以下代码保存为 Direction.java\text{Direction.java} Direction.java

java 复制代码
// 请注意,代码中使用了 JDK 20 中新增的 api,如果 javac 的版本不够高,编译会失败

import java.lang.reflect.Method;

public enum Direction {
    EAST,
    WEST,
    SOUTH,
    NORTH;

    public static void main(String[] args) {
        for (Method method : Direction.class.getDeclaredMethods()) {
            String message = String.format("method [%s] has access flags: %s",
                    method.getName(), method.accessFlags());
            System.out.println(message);
        }
    }
}

使用如下的命令可以编译 Direction.java\text{Direction.java} Direction.java

bash 复制代码
javac -parameters Direction.java

编译之后,当前目录会多出一个名为 Direction.class\text{Direction.class} Direction.class 的文件。下方的命令可以运行 Direction\text{Direction} Direction 类中的 main\text{main} main 方法

bash 复制代码
java Direction

在我的电脑上,运行结果如下 ⬇️

text 复制代码
method [main] has access flags: [PUBLIC, STATIC]
method [values] has access flags: [PUBLIC, STATIC]
method [valueOf] has access flags: [PUBLIC, STATIC]
method [$values] has access flags: [PRIVATE, STATIC, SYNTHETIC]

使用如下命令可以查看 Direction.class\text{Direction.class} Direction.class 的详细内容

bash 复制代码
javap -v -p Direction

完整的结果略有点长,我把和本文相关的部分复制到下方了 ⬇️ (... 表示本文不关心的内容)

text 复制代码
...
public final class Direction extends java.lang.Enum<Direction>
...
{
  public static final Direction EAST;
    descriptor: LDirection;
    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static final Direction WEST;
    descriptor: LDirection;
    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static final Direction SOUTH;
    descriptor: LDirection;
    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static final Direction NORTH;
    descriptor: LDirection;
    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  private static final Direction[] $VALUES;
    descriptor: [LDirection;
    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC

  public static Direction[] values();
    descriptor: ()[LDirection;
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      ...

  public static Direction valueOf(java.lang.String);
    descriptor: (Ljava/lang/String;)LDirection;
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      ...

  private Direction();
    descriptor: (Ljava/lang/String;I)V
    flags: (0x0002) ACC_PRIVATE
    Code:
      ...

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      ...

  private static Direction[] $values();
    descriptor: ()[LDirection;
    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      ...

  static {};
    descriptor: ()V
    flags: (0x0008) ACC_STATIC
    Code:
      ...
}
...

不难验证, ACC_SYNTHETIC\text{ACC\_SYNTHETIC} ACC_SYNTHETIC 这个 access flag\text{access flag} access flag 出现了。至于 java\text{java} java 语言中,实现枚举类型的更多细节,可以参考我之前写的一篇文章:Java 浅析枚举的实现

参考资料

The Java® Virtual Machine Specification 中的

其他

"要点"中的那张图是怎么画出来的

我用了 python3 来画那张图。完整的代码如下

python3 复制代码
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np

n = 16
fig, ax = plt.subplots(figsize=(n, 2))

ax.set_xlim(0, n)
ax.set_ylim(0, 2)
ax.set_aspect('equal')

ax.axis('off')

def calc_2_pow(n):
    result = 1
    pow = 0
    curr = 1
    while curr != n:
        pow += 1
        curr *= 2 
    return pow

for i in range(n):
    rect = patches.Rectangle((i, 0), 1, 1, linewidth=1, edgecolor='black', facecolor='lightgreen')
    ax.add_patch(rect)
    rect = patches.Rectangle((i, 1), 1, 1, linewidth=1, edgecolor='black', facecolor='lightgrey')
    ax.add_patch(rect)

raw_data = {
    0x0001: 'ACC_PUBLIC',
    0x0002: 'ACC_PRIVATE',
    0x0004: 'ACC_PROTECTED',
    0x0008: 'ACC_STATIC',
    0x0010: 'ACC_FINAL',
    0x0020: 'ACC_SYNCHRONIZED',
    0x0040: 'ACC_BRIDGE',
    0x0080: 'ACC_VARARGS',
    0x0100: 'ACC_NATIVE',
    0x0400: 'ACC_ABSTRACT',
    0x0800: 'ACC_STRICT',
    0x1000: 'ACC_SYNTHETIC',
}

cell_text = {}
for (key, value) in raw_data.items():
    pow = calc_2_pow(key)
    col = n - 1 - pow
    cell_text[(0, col)] = value
    cell_text[(1, col)] = f"0x{key:04x}"

for (row, col), text in cell_text.items():
    if row == 0:
        fontsize = 6
    else:
        fontsize = 10
    ax.text(col + 0.5, row + 0.5, text, ha='center', va='center', fontsize=fontsize, fontweight='bold')

plt.title('access flags')
plt.tight_layout()
plt.show()
相关推荐
智研数智工坊1 小时前
SpringBoot4.0.6 + Security7.x + JWT 最新完整实战|无状态权限认证、统一异常处理、可直接落地
java·spring boot·spring security·jwt·权限认证
DIY源码阁1 小时前
JavaSwing宿舍管理系统 - MySQL版
java·数据库·mysql·eclipse
Han_han9191 小时前
递归相关题目:
java
夜微凉42 小时前
MySQL 事务 ACID
后端
kTR2hD1qb2 小时前
Claude Code Skill的介绍与使用
java·前端·数据库·人工智能
狼爷2 小时前
百万QPS多场次秒杀系统架构全解:解耦设计、防超卖、流量防护体系
后端·架构
汤米粥2 小时前
python学习——核心语法三
java·python·学习
basketball6162 小时前
Kadane算法 C++实现
java·c++·算法
better_liang2 小时前
每日Java面试场景题知识点之-如何设计分布式锁
java·redis·zookeeper·面试·分布式锁