搞懂java类加载机制和类加载器

2023-11-12

搞懂java类加载机制和类加载器

类加载概述

一个类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载、验证、准备、解析、初始化、使用和卸载七个阶段,其中验证、准备、解析三个部分统称为连接。如下图所示:

未命名文件

其中加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定特性(也称为动态绑定)。

类加载的阶段

加载

加载阶段是整个“类加载”过程中的一个重要阶段。在加载阶段java虚拟机需要完成三件事

  • 通过一个类的全限定名来获取定义此类的二进制字节流

  • 将这个字节流所代表的静态存储结构转化方法区的运行时数据结构(将类的字节码载入方法中,内部采用c++的instanceKlass描述java类),它的重要field有:

    • _java_mirror 即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴 露给 java 使用
    • _super 即父类
    • _fields 即成员变量
    • _methods 即方法
    • _constants 即常量池
    • _class_loader 即类加载器
    • _vtable 虚方法表
    • _itable 接口方法表
  • ​ 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

解释一下虚函数:虚函数的存在是为了多态。Java 中其实没有虚函数的概念,它的普通函数就相当于 C++ 的虚函数,动态绑定是Java的默认行为。如果 Java 中不希望某个函数具有虚函数特性,可以加上 final 关键字变成非虚函数。

注意:

  • instanceKlass这样的【元数据】是存储在方法区(1.8后的元空间内),但_java_mirror是存储在堆中的
  • 可以使用HSDB工具查看(后文会简单使用HSDB工具进行分析,工具的详细使用步骤可以看《深入理解java虚拟机》的第四章)

image-20220512232004402

连接

验证

验证Class文件的字节流中包含的信息是否符合Jvm规范,安全性检查。

准备

为static变量分配空间,设置默认值

  • static 变量在 JDK 7 之前存储于 instanceKlass 末尾,从 JDK 7 开始,存储于 _java_mirror 末尾
  • static 变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成
解析

将常量池中的符号引用解析为直接引用

符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要可以唯一定位到目标即可。符号引用于内存布局无关,所以所引用的对象不一定需要已经加载到内存中。各种虚拟机实现的内存布局可以不同,但是接受的符号引用必须是一致的,因为符号引用的字面量形式已经明确定义在Class文件格式中。

直接引用(Direct References):直接引用时直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用和虚拟机实现的内存布局相关,同一个符号引用在不同虚拟机上翻译出来的直接引用一般不会相同。如果有了直接引用,那么它一定已经存在于内存中了。

代码示例:

为了进一步理解什么是解析可以看下面的例子

先使用classloader.loadClass进行试验,运行这段代码

package cn.itcast.jvm.t3.load;

import java.io.IOException;

/**
 * 解析的含义
 */
public class Load2 {
    public static void main(String[] args) throws ClassNotFoundException, IOException {
        ClassLoader classloader = Load2.class.getClassLoader();
        // loadClass 方法不会导致类的解析和初始化
        Class<?> c = classloader.loadClass("cn.itcast.jvm.t3.load.C");
//        new C();
        System.in.read();
    }
}

class C {
    D d = new D();
}

class D {

}

然后使用jps命令查看进程

image-20220512235938854

使用HSDB工具分析

先进入自己的jdk目录

image-20220513000209895

然后使用java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB命令进入HSDB界面

image-20220513000424703

点击file->Attach to HotSpot process

image-20220513000609635

输入进程id进入使用

image-20220513000705048

点击tools->Class Browser可以看到类c的加载信息

image-20220513001020956

可以点击类C详细查看信息,可以看到类c的常量池信息有类D的相关信息但是Constant TypeJVM_CONSTANT_UnresolvedClass(未解析的类),说明类D还没有被解析

image-20220513001451763

可以将上面的代码中使用classloader.loadClass的部分注释并且使用new C(),并且以同样的方式使用HSDB工具分析,就可以发现类D已经被解析为具体的信息了,这就是将符号引用解析为直接引用(未被解析时仅仅是一个符号,解析后是具体类的地址)

image-20220513002711943

初始化

初始化是类加载的最后一步,在前面的阶段里,除了加载阶段可以通过用户自定义的类加载器加载,其余部分基本都是由虚拟机主导的。但是到了初始化阶段,才开始真正执行用户编写的java代码了。

在准备阶段,变量都被赋予了初始值,但是到了初始化阶段,所有变量还要按照用户编写的代码重新初始化。换一个角度,初始化阶段是执行类构造器<clinit>()方法的过程。

<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static语句块)中的语句合并生成的,编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中可以赋值,但是不能访问。

public class Test {
  static {
    i=0;  //可以赋值
    System.out.print(i); //编译器会提示“非法向前引用”
  }
  static int i=1;
}

<clinit>()方法与类的构造函数<init>()方法不同,它不需要显示地调用父类构造器,虚拟机会宝成在子类的<clinit>()方法执行之前,父类的<clinit>()已经执行完毕,因此在虚拟机中第一个被执行的<clinit>()一定是java.lang.Object的。

也是由于<clinit>()执行的顺序,所以父类中的静态语句块优于子类的变量赋值操作,所以下面的代码段,B的值会是2。

static class Parent {
  public static int A=1;
  static {
    A=2;
  }
}

static class Sub extends Parent{
  public static int B=A;
}

public static void main(String[] args) {
  System.out.println(Sub.B);
}
发生的时机

概括得说,类初始化是【懒惰的】

  • main 方法所在的类,总会被首先初始化
  • 首次访问这个类的静态变量或静态方法时
  • 子类初始化,如果父类还没初始化,会引发
  • 子类访问父类的静态变量,只会触发父类的初始化
  • Class.forName
  • new 会导致初始化

不会导致初始化的情况

  • 访问类的static静态变量(基本类型和字符串)不会触发初始化
  • 类对象.class 不会触发初始化
  • 创建该类的数组不会触发初始化
  • 类加载器的 loadClass 方法
  • Class.forName 的参数 2 为 false 时

实验代码

class A {
	static int a = 0;
    //静态代码块只会执行一次
	static {
		System.out.println("a init");
	}
}
class B extends A {
	final static double b = 5.0;
	static boolean c = false;
	static {
		System.out.println("b init");
	}
}

验证(实验时请先全部注释,每次只执行其中一个)

public class Load3 {
	static {
		System.out.println("main init");
	}
	public static void main(String[] args) throws ClassNotFoundException {
		// 1. 静态常量(基本类型和字符串)不会触发初始化
		System.out.println(B.b);
		// 2. 类对象.class 不会触发初始化
		System.out.println(B.class);
		// 3. 创建该类的数组不会触发初始化
		System.out.println(new B[0]);
		// 4. 不会初始化类 B,但会加载 B、A
		ClassLoader cl = Thread.currentThread().getContextClassLoader();	
		cl.loadClass("cn.itcast.jvm.t3.B");
		// 5. 不会初始化类 B,但会加载 B、A
		ClassLoader c2 = Thread.currentThread().getContextClassLoader();
		Class.forName("cn.itcast.jvm.t3.B", false, c2);
		
        // 1. 首次访问这个类的静态变量或静态方法时
		System.out.println(A.a);
		// 2. 子类初始化,如果父类还没初始化,会引发
		System.out.println(B.c);
		// 3. 子类访问父类静态变量,只触发父类初始化
		System.out.println(B.a);
		// 4. 会初始化类 B,并先初始化类 A
		Class.forName("cn.itcast.jvm.t3.B");
	}
}

类加载器

Java虚拟机设计团队有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码被称为“类加载器”(Classs Loader)。

对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类命名空间。(比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等)

启动类加载器

启动类加载器负责加载存放在<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数指定的路径中存放的而且是Java虚拟机能够识别的类库加载到虚拟机内存中。 用 Bootstrap 类加载器加载类的例子:

package cn.itcast.jvm.t3.load;
public class F {
	static {
		System.out.println("bootstrap F init");
	}
}

执行

package cn.itcast.jvm.t3.load;
public class Load5_1 {
	public static void main(String[] args) throws ClassNotFoundException {
		Class<?> aClass = Class.forName("cn.itcast.jvm.t3.load.F");
        	//启动类加载器由c++编写,打印null即为启动类加载器
			System.out.println(aClass.getClassLoader());
		}
}

输出

E:\git\jvm\out\production\jvm>java -Xbootclasspath/a:.
cn.itcast.jvm.t3.load.Load5
bootstrap F init
null                 
  • -Xbootclasspath 表示设置 bootclasspath
  • 其中 /a:. 表示将当前目录追加至 bootclasspath 之后
  • 可以用这个办法替换核心类
    • java -Xbootclasspath:
    • java -Xbootclasspath/a:<追加路径>
    • java -Xbootclasspath/p:<追加路径>

扩展类加载器

扩展类加载器负责加载<JAVA_HOME>\lib\ext目录下的文件。

使用扩展类加载器的例子:

package cn.itcast.jvm.t3.load;
	public class G {
		static {
			System.out.println("classpath G init");
		}
}

执行

public class Load5_2 {
	public static void main(String[] args) throws ClassNotFoundException {
		Class<?> aClass = Class.forName("cn.itcast.jvm.t3.load.G");
		System.out.println(aClass.getClassLoader());
	}
}

输出

classpath G init
sun.misc.Launcher$AppClassLoader@18b4aac2

写一个同名的类

package cn.itcast.jvm.t3.load;
	public class G {
	static {
		System.out.println("ext G init");
		}
}

使用命令打个jar包jar -cvf my.jar cn/itcast/jvm/t3/load/G.class

将 jar 包拷贝到 JAVA_HOME/jre/lib/ext

重新执行 Load5_2

输出

ext G init
sun.misc.Launcher$ExtClassLoader@29453f44

这个例子说明当扩展类加载器和应用程序类加载器都能加载一个类时,jvm使用的是扩展类加载器

应用程序类加载器

这个类加载器负责加载用户类路径上所有的类库。如果应用程序中没有自定义过自己的类加载器,一般情况系啊这个就是程序中默认的类加载器。

双亲委派模型

image-20220513123805027

上图中各种类加载器之间的层次关系被称为类加载器的“双亲委派模型”。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。不过这里类加载器之间的父子关系一般不是以继承的关系来实现的,而是通常使用组合(Composition)关系来复用父加载器的代码。

双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成的这个加载请求时,子加载器才会尝试自己去完成加载。

使用双亲委派的好处:Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系。例如java.lang.Object,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都能保证是同一个类。反之,如果没有使用双亲委派模型,都由各个类加载器自行去加载的话,如果用户自己也编写了一个名为Java.lang.Object的类,并放在程序的ClassPath中,那系统中就会出现多个不同的Object类,Java类型体系中最基础的行为也就无从保证,应用程序将会变得一片混乱。

loadclass的源码

protected Class<?> loadClass(String name, boolean resolve)
	throws ClassNotFoundException {
	synchronized (getClassLoadingLock(name)) {
		// 1. 检查该类是否已经加载
		Class<?> c = findLoadedClass(name);
		if (c == null) {
			long t0 = System.nanoTime();
		try {
		if (parent != null) {
		// 2. 有上级的话,委派上级 loadClass
		c = parent.loadClass(name, false);
		} else {
			// 3. 如果没有上级了(ExtClassLoader),则委派
			BootstrapClassLoader
			c = findBootstrapClassOrNull(name);
	}
} catch (ClassNotFoundException e) {
	}
		if (c == null) {
		long t1 = System.nanoTime();
		// 4. 每一层找不到,调用 findClass 方法(每个类加载器自己扩展)来加载
		c = findClass(name);
		// 5. 记录耗时
		sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
		sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
		sun.misc.PerfCounter.getFindClasses().increment();
	}
}
if (resolve) {
	resolveClass(c);
	}
	return c;
	}
}

线程上下文类加载器

注:内容较多不展开叙述

线程上下文类加载器(Context Classloader)是从JDK1.2开始引入的,类Thread中的getContextClassLoader()和setContextClassLoader(ClassLoader cl)分别用来获取和设置上线文类加载器。
如果没有通过setContextClassLoader(ClassLoader cl)进行设置的话,线程将继承其父线程的上下文类加载器。Java应用运行时的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过该类加载器来加载类与资源。

它可以打破双亲委托机制,父ClassLoader可以使用当前线程的Thread.currentThread().getContextClassLoader()所指定的classLoader来加载类,这就可以改变父ClassLoader不能使用子ClassLoader或是其他没有直接父子关系的ClassLoader加载的类的情况,即改变了双亲委托模型。

自定义类加载器

什么时候需要自定义类加载器

  • 想加载非 classpath 随意路径中的类文件
  • 都是通过接口来使用实现,希望解耦时,常用在框架设计
  • 这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常见于 tomcat 容器

步骤

  • 继承 ClassLoader 父类

  • 要遵从双亲委派机制,重写 findClass 方法

    • 注意不是重写 loadClass 方法,否则不会走双亲委派机制
  • 读取类文件的字节码

  • 调用父类的 defineClass 方法来加载类

  • 使用者调用该类加载器的 loadClass 方法

示例代码:

class MyClassLoader extends ClassLoader {

    @Override // name 就是类名称
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String path = "e:\\myclasspath\\" + name + ".class";

        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            Files.copy(Paths.get(path), os);

            // 得到字节数组
            byte[] bytes = os.toByteArray();

            // byte[] -> *.class
            return defineClass(name, bytes, 0, bytes.length);

        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException("类文件未找到", e);
        }
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

搞懂java类加载机制和类加载器 的相关文章

  • Java - 因内存不足错误而关闭

    关于如何最好地处理这个问题 我听到了非常矛盾的事情 并且陷入了以下困境 OOME 会导致一个线程崩溃 但不会导致整个应用程序崩溃 我需要关闭整个应用程序 但不能 因为线程没有剩余内存 我一直认为最佳实践是让它们离开 这样 JVM 就会死掉
  • “java.io.IOException:连接超时”和“SocketTimeoutException:读取超时”之间有什么区别

    如果我设置一个套接字 SoTimeout 并从中读取 当读取时间超过超时限制时 我会收到 SocketTimeoutException 读取超时 这是我的例子中的堆栈 java net SocketTimeoutException Read
  • 使用 WebDriver 单击新打开的选项卡中的链接

    有人可以在这种情况下帮助我吗 场景是 有一个网页 我仅在新选项卡中打开所有指定的链接 现在我尝试单击新打开的选项卡中的任何一个链接 在下面尝试过 但它仅单击主 第一个选项卡中的一个链接 而不是在新选项卡中 new Actions drive
  • Android 中的列表(特别是 RecyclerView 和 CardView)如何工作

    请原谅我问这个问题 但我是 Android 开发新手 尽管我正在尝试了解developer android com 网站上的基础知识 但大多数示例 即使他们说它们是为 Android Studio 构建的 尚未设置为使用 Gradle 因此
  • 将SQL数据引入jquery availabletag

    我正在尝试制作自动完成文本框 但如何将 SQL 数据包含到 jquery 可用标记并循环它 我无法根据以下代码执行该功能 任何帮助 将不胜感激 谢谢 这是我的预期输出 预期结果演示 http jsfiddle net VvETA 71 jq
  • Android 中 localTime 和 localDate 的替代类有哪些? [复制]

    这个问题在这里已经有答案了 我想使用从 android API 获得的长值 该值将日期返回为长值 表示为自纪元以来的毫秒数 我需要使用像 isBefore plusDays isAfter 这样的方法 Cursor managedCurso
  • Android蓝牙java.io.IOException:bt套接字已关闭,读取返回:-1

    我正在尝试编写一个代码 仅连接到运行 Android 5 0 KitKat 的设备上的 目前 唯一配对的设备 无论我尝试了多少方法 我仍然会收到此错误 这是我尝试过的最后一个代码 它似乎完成了我看到人们报告为成功的所有事情 有人能指出我做错
  • 是否可以从 servlet 内部以编程方式设置请求上下文路径?

    这是一个特殊情况 我陷入了处理 企业 网络应用程序的困境 企业应用程序正在调用request getContext 并将其与另一个字符串进行比较 我发现我可以使用 getServletContext getContextPath 获取 se
  • 在 Java 中通过 XSLT 分解 XML

    我需要转换具有嵌套 分层 表单结构的大型 XML 文件
  • 如何删除日期对象的亚秒部分

    当 SQL 数据类型为时间戳时 java util Date 存储为 2010 09 03 15 33 22 246 如何在存储记录之前将亚秒设置为零 例如 在本例中为 246 最简单的方法是这样的 long time date getTi
  • Java、Spring:使用 Mockito 测试 DAO 的 DataAccessException

    我正在尝试增加测试覆盖率 所以我想知道 您将如何测试 DAO 中抛出的 DataAccessExceptions 例如在一个简单的 findAll 方法中 该方法仅返回数据源中的所有数据 就我而言 我使用 Spring JdbcTempla
  • 我们如何测试包私有类?

    我正在看书Effective Java in Item 13 Minimize the accessibility of classes and members 它提到 为了方便测试 您可能想让类 接口或成员更易于访问 这在某种程度上是好的
  • 如何通过 Android 按钮单击运行单独的应用程序

    我尝试在 Android 应用程序中添加两个按钮 以从单独的两个应用程序订单系统和库存系统中选择一个应用程序 如图所示 我已将这两个应用程序实现为两个单独的 Android 项目 当我尝试运行此应用程序时 它会出现直到正确选择窗口 但是当按
  • 如何停止执行的 Jar 文件

    这感觉像是一个愚蠢的问题 但我似乎无法弄清楚 当我在 Windows 上运行 jar 文件时 它不会出现在任务管理器进程中 我怎样才能终止它 我已经尝试过 TASKKILL 但它对我也不起作用 On Linux ps ef grep jav
  • Karaf / Maven - 无法解决:缺少需求 osgi.wiring.package

    我无法在 Karaf 版本 3 0 1 中启动捆绑包 该包是使用 Maven 构建的并导入gson http mvnrepository com artifact com google code gson gson 2 3 1 我按照要求将
  • JMS 中的 MessageListener 和 Consumer 有什么区别?

    我是新来的JMS 据我了解Consumers能够从队列 主题中挑选消息 那么为什么你需要一个MessageListener因为Consumers会知道他们什么时候收到消息吗 这样的实际用途是什么MessageListener 编辑 来自Me
  • 源值 1.5 的错误已过时,将在未来版本中删除

    我使用 scala maven plugin 来编译包含 scala 和 java 代码的项目 我已经将源和目标设置为1 7 但不知道为什么maven仍然使用1 5 这是我在 pom xml 中的插件
  • ECDH使用Android KeyStore生成私钥

    我正在尝试使用 Android KeyStore Provider 生成的私有文件在 Android 中实现 ECDH public byte ecdh PublicKey otherPubKey throws Exception try
  • 检查应用程序是否在 Android Market 上可用

    给定 Android 应用程序 ID 包名称 如何以编程方式检查该应用程序是否在 Android Market 上可用 例如 com rovio angrybirds 可用 而 com random app ibuilt 不可用 我计划从
  • 即使调整大小,如何获得屏幕的精确中间位置

    好的 这个问题有两部分 当我做一个JFrame 并在其上画一些东西 即使我将宽度设置为 400 并使其在一个项目击中它时 当然 允许项目宽度 它会反弹回来 但由于某种原因 它总是偏离屏幕约 10 个像素 有没有办法解决这个问题 或者我只需要

随机推荐

  • python 下实现xgboost 调参演示

    基于前阵子京东金融JDD数据探索大赛比赛拿下总决赛季军的经验 发现xgboost真的是一个很好的利器 精确度的提升是很疯狂的 从最远先使用的RF模型到XGBOOST模型 精确度可以说提升了0 3的跨度 相信很多人跟我一样都被xgboost惊
  • hihocoder #1000 : A + B Java实现

    时间限制 1000ms 单点时限 1000ms 内存限制 256MB 描述 求两个整数A B的和 输入 输入包含多组数据 每组数据包含两个整数A 1 A 100 和B 1 B 100 输出 对于每组数据输出A B的和 样例输入 1 2 3
  • 子序列问题

    子序列问题 双指针 动态规划 例题一 判断子序列 力扣392 链接 https leetcode cn com problems is subsequence 题目描述 给定字符串 s 和 t 判断 s 是否为 t 的子序列 字符串的一个子
  • 如何在Power Linux上运行AIX程序(实现验证,附源码开源地址)

    最后更新2021 01 21 静心研究了小半年 终于把原型验证搞定了 可以在Power Linux上直接运行AIX程序 当然 目前只是实现了POC 还有大量的工程工作 源代码地址 https gitee com HarryHurryHung
  • 不用==用equals引发的一系列思考

    毕业刚工作的时候 带我的小师傅给我说判断相等使用equals不要使用 第一次写java的我照办了 但是不清楚为什么 之后还是决定真正了解一下java本身 于是开始看jdk源码 这里以Integer为例 equal public boolea
  • 【Python】多分类算法—Random Forest

    Python 多分类算法 Random Forest 本文将主要就Random Forest 随机森林 的多分类应用进行描述 当然也可运用于二分类中 本文运用scikit learn框架 文章目录 Python 多分类算法 Random F
  • docker私有仓库

    一 docker私有仓库 1 拉取 docker pull registry 2 创建启动容器 docker run id name registry p 5000 5000 registry 3 浏览器输入地址 http ip 5000
  • 【Java】操作Sqlite数据库

    首先在https github com xerial sqlite jdbc下载jar包 import java sql Connection import java sql DriverManager import java sql Re
  • 01Linux常用指令

    所有的指令不要强制记忆 选项要多多查一下 多多练习使用即可 文章目录 一 Linux简单介绍 1 1 Linux的目录结构 1 2 常见的具体目录结构 bin sbin root lib etc usr boot tmp dev media
  • 从字符串中取出指定位置的字符

    首先肯定返回一个char类型
  • excel编写的测试用例转成xmind格式

    软件开发过程中 测试工程师必不可少的一项工作就是编写测试用例 进行测试评审 设计评审 用例评审 写用例常用的几种方法无非就是用excel写 或者xmind这种思维导图形式 而xmind具有条理清晰的特点 所以测试评审时为了提高效率 很多团队
  • 2023华为od机试 Python【不包含回文串】

    前言 本题使用python解答 如果需要Java版本代码 请参考 点我 题目 什么是回文串呢 就是将原字符串翻转过来 和原始字符串一样的字符串 我们现在有一个不包含回文串的字符串 并且 字符串的字符在英语字母的前N个 且字符串不包含任何长度
  • 【网络游戏同步技术】帧同步的一致性

    参考博文 GAD 网络游戏同步技术 引言 帧同步的形式很泛 根据不同游戏 使用的技术范围又不一样 所以大家都在讲方法论 要全面覆盖可能需要较大的篇幅 所以 我简单描述下 假定大家对帧同步和状态同步有一定的认识 理论上的问题 我就不作过多解释
  • 从pandas dataframe中随机删除n个某一列是某个值的元素

    从pandas dataframe中随机删除n个某一列是某个值的元素 import pandas as pd 创建示例 DataFrame data A 1 2 3 4 5 6 B 6 7 8 9 10 11 C X Y Z X Y X d
  • 基本排序算法(直接排序,选择排序,冒泡排序)

    一 直接排序 思路 首先需要两个嵌套的for循环 外层for循环控制轮数 内层for循环控制每轮比较的次数 这里来演示一下遍历的过程 第一轮 首先让i指向数组的首部 让j指向i的后一个元素 两者比较 2比1大 所以交换2跟1的位置 然后j后
  • git proxy

    git config global https proxy http 127 0 0 1 7890 git config global https proxy https 127 0 0 1 7890
  • tar命令的详细解释

    tar命令 root linux tar cxtzjvfpPN 文件与目录 参数 c 建立一个压缩文件的参数指令 create 的意思 x 解开一个压缩文件的参数指令 t 查看 tarfile 里面的文件 特别注意 在参数的下达中 c x
  • CSS 水平居中

    1 若元素内容为文字时 元素设置text align center text align属性指定元素文本的水平对齐方式 center 把文本排列到中间 p 这里是文本内容 p 2 父子元素宽度固定 父元素设置text align cente
  • SQL实用功能手册

    SQL实用功能手册 SQL基础复习 SQL结构化查询语言 是一种访问和处理数据库的计算机语言 对数据库操作 对表操作 对数据进行CRUD操作 操作视图 存储过程 索引 环境基础操作 安装mysql 启动mysql 配置环境变量 检查mysq
  • 搞懂java类加载机制和类加载器

    搞懂java类加载机制和类加载器 类加载概述 一个类从被加载到虚拟机内存中开始 到卸载出内存为止 它的整个生命周期将会经历加载 验证 准备 解析 初始化 使用和卸载七个阶段 其中验证 准备 解析三个部分统称为连接 如下图所示 其中加载 验证