热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【Tomcat源码篇】自定义类加载器那点儿事儿

Tomcat进阶篇一、聊聊ClassLoader的那些事儿我们要分析清楚Tomcat中的类加载器相关的内容之前我们还是需要把JVM中的类加载器给大家理清楚。1.类加载器的过程类加
Tomcat进阶篇

在这里插入图片描述

一、聊聊ClassLoader的那些事儿

  我们要分析清楚Tomcat中的类加载器相关的内容之前我们还是需要把JVM中的类加载器给大家理清楚。

1.类加载器的过程

  类加载器的作用就是从文件系统或者网络中加载Class文件,至于他是否可以运行就不是ClassLoader的工作了。

image.png

2.类加载器的分类

  JVM中支持的类加载器有两种类型,分别是 引导类加载器【Bootstrap ClassLoader】和 自定义类加载器【User-Defined ClassLoader】

image.png

  在Java虚拟机层面定义:所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。

image.png

可以通过源码看到对应的类加载器的继承关系

ExtClassLoader

image.png

AppClassLoader

image.png

通过具体的案例代码可以来看看类加载器的使用

public static void main(String[] args) {// 获取系统类加载器ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();System.out.println("systemClassLoader = " + systemClassLoader);// 获取父类加载器ClassLoader parent = systemClassLoader.getParent();System.out.println("parent = " + parent);// 继续获取上层的类加载器ClassLoader bootstrapClassLoader = parent.getParent();System.out.println("bootstrapClassLoader = " + bootstrapClassLoader);// 自定义Java类ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();System.out.println("classLoader = " + classLoader);// Java系统类ClassLoader classLoader1 = String.class.getClassLoader();System.out.println("classLoader1 = " + classLoader1);}

对应的输出结果

systemClassLoader = sun.misc.Launcher$AppClassLoader@18b4aac2
parent = sun.misc.Launcher$ExtClassLoader@61bbe9ba
bootstrapClassLoader = null
classLoader = sun.misc.Launcher$AppClassLoader@18b4aac2
classLoader1 = null

3.Bootstrap ClassLoader

  虚拟机自带的类加载器,启动类加载器

  • 通过C/C++实现的,JVM内置类加载器
  • 作用是用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resources.jar或者sun.boot.class.path路径下的内容)、用于提供JVM自身需要的类。
  • 没有继承java.lang.ClassLoader、没有父加载器,自己就是祖先了。
  • 加载扩展类和应用程序类加载器,并指定为他们的父类加载器
  • 出于安全考虑,Bootstrap启动类加载器只加载报名为java,javax,sun等开头的类

通过代码来看看具体的加载路径有哪些

public static void main(String[] args) {System.out.println("**************启动类加载器*************");URL[] urLs &#61; Launcher.getBootstrapClassPath().getURLs();for (int i &#61; 0 ; i < urLs.length ; i &#43;&#43;){URL urL &#61; urLs[i];System.out.println("urL &#61; " &#43; urL.toExternalForm());}}

输出结果&#xff1a;

**************启动类加载器*************
urL &#61; file:/D:/software/java/jdk8/jre/lib/resources.jar
urL &#61; file:/D:/software/java/jdk8/jre/lib/rt.jar
urL &#61; file:/D:/software/java/jdk8/jre/lib/sunrsasign.jar
urL &#61; file:/D:/software/java/jdk8/jre/lib/jsse.jar
urL &#61; file:/D:/software/java/jdk8/jre/lib/jce.jar
urL &#61; file:/D:/software/java/jdk8/jre/lib/charsets.jar
urL &#61; file:/D:/software/java/jdk8/jre/lib/jfr.jar
urL &#61; file:/D:/software/java/jdk8/jre/classes

4.Extension ClassLoader

  虚拟机自带的加载器。扩展类加载器&#xff0c;Java语音编写&#xff0c;是sun.misc.Launcher的内部类

image.png

派生于ClassLoader所以是自定义类加载器

image.png

父类加载器是BootstrapClassLoader。

image.png

  扩展类加载器是从扩展目录 java.ext.dirs系统属性指定的目录中加载类库&#xff0c;或者从JDK的安装目录的 jre/lib/ext子目录下加载类库&#xff0c;如果用户创建的jar包也放在了这个目录下&#xff0c;那么该类加载器也会自动加载的。

然后通过案例来看看扩展类加载器加载的路径

public static void main(String[] args) {System.out.println("**************扩展类加载器*************");String extDirs &#61; System.getProperty("java.ext.dirs");System.out.println("extDirs &#61; " &#43; extDirs);}

对应的输出结果

**************扩展类加载器*************
extDirs &#61; D:\software\java\jdk8\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext

然后通过加载路径下的Java类测试也能证明

image.png

5.App ClassLoader

  虚拟机自带的加载器&#xff0c;APPClassLoader也叫应用程序类加载器。Java语音编写&#xff0c;由sun.misc.Launcher下的内部类提供。也是派生于ClassLoader。

image.png

父类加载器为ExtClassLoader

image.png

  主要负责加载环境变量classpath或系统属性 java.class.path 指定路径下的类库&#xff0c;该类加载器是程序中的默认类加载器&#xff0c;一般来说&#xff0c;Java应用的类都是由它来完成加载的。通过 ClassLoader#getSystemClassLoader() 方法可以获取到该类加载器。

6.自定义类加载器

  在我们的日常应用程序的开发中&#xff0c;我基本是用不到自定义类加载器的&#xff0c;基本就是由前面介绍的这三个类加载器来相互配合搞定的。但是在有些特殊的情况下我们不希望通过系统的类加载器来处理&#xff0c;这时我们就可以通过自定义类加载来实现了。

  用户自定义类加载器的实现步骤&#xff1a;

  1. 我们可以直接编写 java.lang.ClassLoader 类的实现来完成自定义的处理
  2. 在JDK1.2之前&#xff0c;自定义类加载器时我们总是会继承ClassLoader然后重写loadClass方法&#xff0c;达到自定义类加载的目的&#xff0c;但是在JDK1.2之后建议把自定义的类加载逻辑放在findClass()方法中
  3. 最后在编写自定义类加载的时候&#xff0c;如果没有太过于复杂的需求&#xff0c;可以直接继承URLClassLoader类&#xff0c;这样可以达到简化自定义类加载器的目的

7.双亲委派机制


Java虚拟机对class文件采用的是 按需加载的方式&#xff0c;也就是当需要使用该类时才会将他的class文件加载到内存中生成class对象&#xff0c;而且加载某个类的class文件时&#xff0c;Java虚拟机采用的而是双亲委派模式。即把请求交由父类加载器处理&#xff0c;它是一种任务委派模式。

image.png

双亲委派的工作原理&#xff1a;

  1. 如果一个类加载器收到了类加载的请求&#xff0c;它并不会自己先去加载&#xff0c;而且把这个请求委托给父类的加载器去执行。
  2. 如果父类加载器还存在其父类加载器&#xff0c;则进一步向上委托&#xff0c;依次递归请求&#xff0c;最终将请求流转到顶层的启动类加载器
  3. 如果父类加载器可以完成类加载任务&#xff0c;就成功返回&#xff0c;如果父类无法完成任务&#xff0c;子加载器才会尝试自己去加载。

举个简单的例子&#xff0c;我们自定义一个java.lang.String类&#xff0c;然后添加对应的main方式执行。

public class String {public static void main(String[] args) {System.out.println("自定义String类");}
}

报错信息为&#xff1a;

image.png

原因就是双亲委派机制通过BootstrapClassLoader加载的是java包下的String&#xff0c;而不会加载我们自定义的。

双亲委派机制的有点&#xff1a;

  1. 避免类的重复加载
  2. 保护程序的安全&#xff0c;防止核心API被随意的篡改

二、Catalina为什么不new出来?

  掌握了Java的类加载器和双亲委派机制&#xff0c;现在我们就可以回答正题上来了&#xff0c;Tomcat的类加载器是怎么设计的&#xff1f;

1.Web容器的特性

  Web容器有其自身的特殊性&#xff0c;所以在类加载器这块是不能完全使用JVM的类加载器的双亲委派机制的。在Web容器中我们应该要满足如下的特性&#xff1a;

隔离性&#xff1a;

  部署在同一个Web容器上的两个Web应用程序所使用的Java类库可以实现相互隔离。设想一下&#xff0c;两个Web应用&#xff0c;一个使用了Spring3.0&#xff0c;另一个使用了新的的5.0&#xff0c;应用服务器使用一个类加载器&#xff0c;Web应用将会因为jar包覆盖而无法启动。

image.png

灵活性:

  Web应用之间的类加载器相互独立&#xff0c;那么就能针对一个Web应用进行重新部署&#xff0c;此时Web应用的类加载器会被重建&#xff0c;而且不会影响其他的Web应用。如果采用一个类加载器&#xff0c;类之间的依赖是杂乱复杂的&#xff0c;无法完全移出某个应用的类。

性能:

  性能也是一个比较重要的点。部署在同一个Web容器上的两个Web应用程序所使用的Java类库可以互相共享。这个需求也很常见&#xff0c;例如&#xff0c;用户可能有10个使用Spring框架的应用程序部署在同一台服务器上&#xff0c;如果把10份Spring分别存放在各个应用程序的隔离目录中&#xff0c;将会是很大的资源浪费——这主要倒不是浪费磁盘空间的问题&#xff0c;而是指类库在使用时都要被加载到Web容器的内存&#xff0c;如果类库不能共享&#xff0c;虚拟机的方法区就会很容易出现过度膨胀的风险。

2.Tomcat类加载器结构

  明白了Web容器的类加载器有多个&#xff0c;再来看tomcat的类加载器结构。

首先上张图&#xff0c;整体看下tomcat的类加载器&#xff1a;

image.png

  可以看到在原先的java类加载器基础上&#xff0c;tomcat新增了几个类加载器&#xff0c;包括3个基础类加载器和每个Web应用的类加载器&#xff0c;其中3个基础类加载器可在conf/catalina.properties中配置&#xff0c;具体介绍下&#xff1a;

Common&#xff1a;以应用类加载器为父类&#xff0c;是tomcat顶层的公用类加载器&#xff0c;其路径由conf/catalina.properties中的common.loader指定&#xff0c;默认指向${catalina.base}/lib下的包。

image.png

Catalina&#xff1a;以Common类加载器为父类&#xff0c;是用于加载Tomcat应用服务器的类加载器&#xff0c;其路径由server.loader指定&#xff0c;默认为空&#xff0c;此时tomcat使用Common类加载器加载应用服务器。

image.png

Shared&#xff1a;以Common类加载器为父类&#xff0c;是所有Web应用的父类加载器&#xff0c;其路径由shared.loader指定&#xff0c;默认为空&#xff0c;此时tomcat使用Common类加载器作为Web应用的父加载器。

Web应用&#xff1a;以Shared类加载器为父类&#xff0c;加载/WEB-INF/classes目录下的未压缩的Class和资源文件以及/WEB-INF/lib目录下的jar包&#xff0c;该类加载器只对当前Web应用可见&#xff0c;对其他Web应用均不可见。

  默认情况下&#xff0c;Common、Catalina、Shared类加载器是同一个&#xff0c;但可以配置3个不同的类加载器&#xff0c;使他们各司其职。

  首先&#xff0c;Common类加载器复杂加载Tomcat应用服务器内部和Web应用均可见的类&#xff0c;如Servlet规范相关包和一些通用工具包。

  其次&#xff0c;Catalina类加载器负责只有Tomcat应用服务器内部可见的类&#xff0c;这些类对Web应用不可见。比如&#xff0c;想实现自己的会话存储方案&#xff0c;而且该方案依赖了一些第三方包&#xff0c;当然是不希望这些包对Web应用可见&#xff0c;这时可以配置server.load&#xff0c;创建独立的Catalina类加载器。

  再次&#xff0c;Shared类复杂加载Web应用共享类&#xff0c;这些类tomcat服务器不会依赖

3.Tomcat源码分析


3.1 CatalinClassLoader

  首先来看看Tomcat的类加载器的继承结构

image.png

可以看到继承的结构和我们上面所写的类加载器的结构不同。

大家需要注意双亲委派机制并不是通过继承来实现的&#xff0c;而是相互之间组合而形成的。所以AppClassLoader没有继承自 ExtClassLoader&#xff0c;WebappClassLoader也没有继承自AppClassLoader。至于Common ClassLoader &#xff0c;Shared ClassLoader&#xff0c;Catalina ClassLoader则是在启动时初始化的三个不同名字的URLClassLoader。

先来看看Bootstrap#init()方法。init方法会调用initClassLoaders&#xff0c;同样也会将Catalina ClassLoader设置到当前线程设置到当前线程&#xff0c;进入initClassLoaders来看看。

private void initClassLoaders() {try {// 创建 commonLoader catalinaLoader sharedLoadercommonLoader &#61; createClassLoader("common", null);if (commonLoader &#61;&#61; null) {// no config file, default to this loader - we might be in a &#39;single&#39; env.commonLoader &#61; this.getClass().getClassLoader();}// 默认情况下 server.loader 和 shared.loader 都为空则会返回 commonLoader 类加载器catalinaLoader &#61; createClassLoader("server", commonLoader);sharedLoader &#61; createClassLoader("shared", commonLoader);} catch (Throwable t) {handleThrowable(t);log.error("Class loader creation threw exception", t);System.exit(1);}}

我们可以看到在initClassLoaders()方法中完成了CommonClassLoader&#xff0c; CatalinaClassLoader&#xff0c;和SharedClassLoader的创建&#xff0c;而且进入到createClassLoader方法中。

image.png

可以看到这三个基础类加载器所加载的资源刚好对应conf/catalina.properties中的common.loader&#xff0c;server.loader&#xff0c;shared.loader

3.2 层次结构

  Common/Catalina/Shared ClassLoader的创建好了之后就会维护相互之间的组合关系

image.png

其实也就是设置了父加载器

3.3 具体的加载过程

  源码比较长&#xff0c;直接进入到 WebappClassLoaderBase中的 LoadClass方法

&#64;Overridepublic Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {if (log.isDebugEnabled()) {log.debug("loadClass(" &#43; name &#43; ", " &#43; resolve &#43; ")");}Class<?> clazz &#61; null;// Log access to stopped class loadercheckStateForClassLoading(name);// (0) Check our previously loaded local class cache// 检查WebappClassLoader中是否加载过此类clazz &#61; findLoadedClass0(name);if (clazz !&#61; null) {if (log.isDebugEnabled()) {log.debug(" Returning class from cache");}if (resolve) {resolveClass(clazz);}return clazz;}// (0.1) Check our previously loaded class cache// 如果第一步没有找到&#xff0c;则继续检查JVM虚拟机中是否加载过该类clazz &#61; findLoadedClass(name);if (clazz !&#61; null) {if (log.isDebugEnabled()) {log.debug(" Returning class from cache");}if (resolve) {resolveClass(clazz);}return clazz;}// (0.2) Try loading the class with the bootstrap class loader, to prevent// the webapp from overriding Java SE classes. This implements// SRV.10.7.2// 如果前两步都没有找到&#xff0c;则使用系统类加载该类&#xff08;也就是当前JVM的ClassPath&#xff09;。// 为了防止覆盖基础类实现&#xff0c;这里会判断class是不是JVMSE中的基础类库中类。String resourceName &#61; binaryNameToPath(name, false);ClassLoader javaseLoader &#61; getJavaseClassLoader();boolean tryLoadingFromJavaseLoader;try {// Use getResource as it won&#39;t trigger an expensive// ClassNotFoundException if the resource is not available from// the Java SE class loader. However (see// https://bz.apache.org/bugzilla/show_bug.cgi?id&#61;58125 for// details) when running under a security manager in rare cases// this call may trigger a ClassCircularityError.// See https://bz.apache.org/bugzilla/show_bug.cgi?id&#61;61424 for// details of how this may trigger a StackOverflowError// Given these reported errors, catch Throwable to ensure any// other edge cases are also caughtURL url;if (securityManager !&#61; null) {PrivilegedAction<URL> dp &#61; new PrivilegedJavaseGetResource(resourceName);url &#61; AccessController.doPrivileged(dp);} else {url &#61; javaseLoader.getResource(resourceName);}tryLoadingFromJavaseLoader &#61; (url !&#61; null);} catch (Throwable t) {// Swallow all exceptions apart from those that must be re-thrownExceptionUtils.handleThrowable(t);// The getResource() trick won&#39;t work for this class. We have to// try loading it directly and accept that we might get a// ClassNotFoundException.tryLoadingFromJavaseLoader &#61; true;}if (tryLoadingFromJavaseLoader) {try {clazz &#61; javaseLoader.loadClass(name);if (clazz !&#61; null) {if (resolve) {resolveClass(clazz);}return clazz;}} catch (ClassNotFoundException e) {// Ignore}}// (0.5) Permission to access this class when using a SecurityManagerif (securityManager !&#61; null) {int i &#61; name.lastIndexOf(&#39;.&#39;);if (i >&#61; 0) {try {securityManager.checkPackageAccess(name.substring(0,i));} catch (SecurityException se) {String error &#61; sm.getString("webappClassLoader.restrictedPackage", name);log.info(error, se);throw new ClassNotFoundException(error, se);}}}// 检查是否 设置了delegate属性&#xff0c;设置为true&#xff0c;那么就会完全按照JVM的"双亲委托"机制流程加载类。boolean delegateLoad &#61; delegate || filter(name, true);// (1) Delegate to our parent if requestedif (delegateLoad) {if (log.isDebugEnabled()) {log.debug(" Delegating to parent classloader1 " &#43; parent);}try {clazz &#61; Class.forName(name, false, parent);if (clazz !&#61; null) {if (log.isDebugEnabled()) {log.debug(" Loading class from parent");}if (resolve) {resolveClass(clazz);}return clazz;}} catch (ClassNotFoundException e) {// Ignore}}// (2) Search local repositories// 若是没有委托&#xff0c;则默认会首次使用WebappClassLoader来加载类。通过自定义findClass定义处理类加载规则。// findClass()会去Web-INF/classes 目录下查找类。if (log.isDebugEnabled()) {log.debug(" Searching local repositories");}try {clazz &#61; findClass(name);if (clazz !&#61; null) {if (log.isDebugEnabled()) {log.debug(" Loading class from local repository");}if (resolve) {resolveClass(clazz);}return clazz;}} catch (ClassNotFoundException e) {// Ignore}// (3) Delegate to parent unconditionally// 若是WebappClassLoader在/WEB-INF/classes、/WEB-INF/lib下还是查找不到class&#xff0c;// 那么无条件强制委托给System、Common类加载器去查找该类。if (!delegateLoad) {if (log.isDebugEnabled()) {log.debug(" Delegating to parent classloader at end: " &#43; parent);}try {clazz &#61; Class.forName(name, false, parent);if (clazz !&#61; null) {if (log.isDebugEnabled()) {log.debug(" Loading class from parent");}if (resolve) {resolveClass(clazz);}return clazz;}} catch (ClassNotFoundException e) {// Ignore}}}throw new ClassNotFoundException(name);}

Web应用类加载器默认的加载顺序是&#xff1a;

(1).先从缓存中加载&#xff1b;

(2).如果没有&#xff0c;则从JVM的Bootstrap类加载器加载&#xff1b;

(3).如果没有&#xff0c;则从当前类加载器加载&#xff08;按照WEB-INF/classes、WEB-INF/lib的顺序&#xff09;&#xff1b;

(4).如果没有&#xff0c;则从父类加载器加载&#xff0c;由于父类加载器采用默认的委派模式&#xff0c;所以加载顺序是AppClassLoader、Common、Shared。

tomcat提供了delegate属性用于控制是否启用java委派模式&#xff0c;默认false&#xff08;不启用&#xff09;&#xff0c;当设置为true时&#xff0c;tomcat将使用java的默认委派模式&#xff0c;这时加载顺序如下&#xff1a;

(1).先从缓存中加载&#xff1b;

(2).如果没有&#xff0c;则从JVM的Bootstrap类加载器加载&#xff1b;

(3).如果没有&#xff0c;则从父类加载器加载&#xff0c;加载顺序是AppClassLoader、Common、Shared。

(4).如果没有&#xff0c;则从当前类加载器加载&#xff08;按照WEB-INF/classes、WEB-INF/lib的顺序&#xff09;&#xff1b;


推荐阅读
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 本文介绍了解决java开源项目apache commons email简单使用报错的方法,包括使用正确的JAR包和正确的代码配置,以及相关参数的设置。详细介绍了如何使用apache commons email发送邮件。 ... [详细]
  • 精讲代理设计模式
    代理设计模式为其他对象提供一种代理以控制对这个对象的访问。代理模式实现原理代理模式主要包含三个角色,即抽象主题角色(Subject)、委托类角色(被代理角色ÿ ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
author-avatar
玛丽
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有