JVM - 1.类加载子系统

2024-08-23 14:44
文章标签 java 加载 jvm 子系统

本文主要是介绍JVM - 1.类加载子系统,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.类加载子系统

1.作用

  • 1.负责从文件系统或网络中加载字节码(.class)文件,即将物理磁盘上的字节码文件加载到内存中,生成供程序使用的类对象
    在这里插入图片描述
  • 2.字节码文件要求在文件开头有特定的文件标识(CA FE BA BE)
  • 3.类加载器(ClassLoader)只负责字节码文件的加载,是否可运行,由执行引擎(Execution Engine)决定
  • 4.类加载器是指特定的加载器,而类加载子系统是一个系统流程的统称
  • 5.加载生成的的类信息存放在称为方法区的内存空间中
  • 6.除了类的信息外,方法区还会存放运行时常量池(字节码文件中的Constant pool在运行时加载到内存中称为运行时常量池)信息,可能还包括字符串字面量数字常量,参考附录1

2.角色

在这里插入图片描述

  • 1.Car类通过编译器(javac)编译后生成Car.calss字节码文件并存在于本地磁盘上
  • 2.程序执行时字节码文件通过类加载器加载到JVM中,生成一个类对象
  • 3.通过该类对象可获取到类的构造器,根据该构造器可实例化出多个实例,通过实例的getClass方法也可以获取类对象本身
    在这里插入图片描述
    在这里插入图片描述
  • 4.字节码文件加载到JVM中,被称为DNA元数据模板,放在方法区
  • 5..class文件 -> JVM -> 元数据模板,该过程通过类加载器(Class Loader)实现
  • 6.物理磁盘上的字节码文件通过二进制流的方式加载到内存

3.类的加载过程

在这里插入图片描述
在这里插入图片描述

1.加载

  • 1.通过一个类的全限定名获取定义此类的二进制字节流
    在这里插入图片描述
  • 2.将这个字节流所代表的静态存储结构转化为方法区运行时数据结构
  • 3.方法区:抽象概念;落地实现:1.7及以前叫永久代,之后叫元空间
  • 4.并在内存中生成一个代表这个类的java.lang.Class对象,作为方法区该对象各种数据的访问入口
    在这里插入图片描述
    在这里插入图片描述

2.链接

1.验证(Verify)
  • 1.确保class文件的字节流包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机的自身安全
  • 2.主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证
  • 3.例:文件标识验证(CA FE BA BE),使用Binary Viewer工具进行查看
    在这里插入图片描述
2.准备(Prepare)
  • 1.为类变量/静态变量分配内存空间并且设置该类变量默认初始值
    • 1.成员变量:定义在方法体和语句块之外,不属于任何一个方法,作用域是整个类
      • 1.静态变量/类变量:用 static 修饰的成员变量
      • 2.全局变量/实例变量:无 static 修饰的成员变量
    • 2.局部变量:定义在方法或代码块中的变量,作用域是其所在的代码块
  • 2.例如:
    • 1.private static int a = 1,准备阶段会赋默认初始值为0,即a=0,然后在初始化(initial)阶段会赋值a = 1
  • 3.注意
    • 1.不同类型的类变量默认初始值不同
    • 2.这里不包含final修饰的static类变量,因为final修饰的是常量而不是变量,常量后期不会再被修改,所以在编译阶段就已经分配值,准备阶段只是显示初始化
    • 3.这里不会为实例变量默认初始化,因为当前还没创建对象,只是加载过程,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆
3.解析(Resolve)
  • 1.将常量池内的符号引用转换为直接引用的过程
  • 2.事实上解析操作往往会在JVM执行完初始化后再执行
  • 3.符号引用:一组符号来描述所引用的目标,符号引用的字面量形式明确定义在《java虚拟机规范》的class文件格式中
  • 4.直接引用:直接指向目标的指针,相对偏移量或一个间接定位到目标的句柄
  • 5.解析动作主要针对类或接口,字段,类方法,接口方法,方法类型等
  • 6.通过反编译可以查看class文件中的符号引用和直接引用
package com.java;public class HelloApp {private static int a = 1;public HelloApp() {}public static void main(String[] args) {System.out.println(a);}
}
如下如示:Constant pool是常量池,其中以#开头的是符号引用,其余的是直接引用F:\文档\笔记\代码\JVMDemo\out\java>javap -v HelloApp.class
Classfile /F:/文档/笔记/代码/JVMDemo/out/java/HelloApp.classLast modified 2022-11-9; size 608 bytesMD5 checksum 5964d34bba8f8bf4e817be8fe95a17feCompiled from "HelloApp.java"
public class com.java.HelloAppminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref          #6.#23         // java/lang/Object."<init>":()V#2 = Fieldref           #24.#25        // java/lang/System.out:Ljava/io/PrintStream;#3 = Fieldref           #5.#26         // com/atguigu/java/HelloApp.a:I#4 = Methodref          #27.#28        // java/io/PrintStream.println:(I)V#5 = Class              #29            // com/atguigu/java/HelloApp#6 = Class              #30            // java/lang/Object	<=符号引用 #7 = Utf8               a	<=直接引用#8 = Utf8               I#9 = Utf8               <init>#10 = Utf8               ()V#11 = Utf8               Code#12 = Utf8               LineNumberTable#13 = Utf8               LocalVariableTable#14 = Utf8               this#15 = Utf8               Lcom/atguigu/java/HelloApp;#16 = Utf8               main#17 = Utf8               ([Ljava/lang/String;)V#18 = Utf8               args#19 = Utf8               [Ljava/lang/String;#20 = Utf8               <clinit>#21 = Utf8               SourceFile#22 = Utf8               HelloApp.java#23 = NameAndType        #9:#10         // "<init>":()V#24 = Class              #31            // java/lang/System#25 = NameAndType        #32:#33        // out:Ljava/io/PrintStream;#26 = NameAndType        #7:#8          // a:I#27 = Class              #34            // java/io/PrintStream#28 = NameAndType        #35:#36        // println:(I)V#29 = Utf8               com/atguigu/java/HelloApp#30 = Utf8               java/lang/Object#31 = Utf8               java/lang/System#32 = Utf8               out#33 = Utf8               Ljava/io/PrintStream;#34 = Utf8               java/io/PrintStream#35 = Utf8               println#36 = Utf8               (I)V
{public com.java.HelloApp();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 7: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       5     0  this   Lcom/atguigu/java/HelloApp;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: getstatic     #2                  // Field >java/lang/System.out:Ljava/io/PrintStream;3: getstatic     #3                  // Field a:I6: invokevirtual #4                  // Method >java/io/PrintStream.println:(I)V9: returnLineNumberTable:line 12: 0line 13: 9LocalVariableTable:Start  Length  Slot  Name   Signature0      10     0  args   [Ljava/lang/String;static {};descriptor: ()Vflags: ACC_STATICCode:stack=1, locals=0, args_size=00: iconst_11: putstatic     #3                  // Field a:I4: returnLineNumberTable:line 8: 0
}
SourceFile: "HelloApp.java"

3.初始化

  • 1.初始化阶段就是执行类构造器方法(<clinit>())的过程,可使用jclasslib查看(下载地址)
  • 2.此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作(类变量的显式赋值)和静态代码块中的语句合并而来,注意不包含静态方法
  • 3.类构造器方法中指定按语句在源文件中出现的顺序执行
  • 4.<clinit>()不同于类的构造器,类的构造器方法对应的是init()方法
  • 5.若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕
  • 6.虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁(多线程下如果有一个线程加载,则其他加载线程会被阻塞;即保证类只会被加载一次,加载后的类对象保存在方法区)
  • 7.<clinit>()只有在类中有对静态变量静态代码块操作时才会有,其他情况不会存在(已测试静态方法不会存在)
public class HelloApp2 {private static int num = 1; //prepare:num = 0 ---> initial : num = 1 ---> num = 2static {num = 2;number = 20;}private static int number = 10; //prepare:number = 0 ---> initial : number = 20 ---> number = 10public static void main(String[] args) {System.out.println(num);  // num = 2System.out.println(number); // number = 10}
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 8.非法的前项引用,可以提前赋值,但不是不能提前引用

在这里插入图片描述

4.类加载器

  • 1.JVM支持两种类型的类加载器:
    • 1.引导类加载器(Bootstrap ClassLoader)
    • 2.自定义类加载器(User-Defined ClassLoader)
  • 2.一般来说自定义类加载器指的是程序中由开发人员自定义的类加载;但是《Java虚拟机规范》中将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
  • 3.程序中最常见的三个类加载器
    • 1.Bootstrap Class Loader:引导类加载器
    • 2.Extension Class Loader:扩展类加载器
    • 3.System Class Loader:系统类加载器
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
  • 4.以上四者之间(引导,扩展,系统,自定义)是包含关系,不是上层下层,也不是子父类的继承关系
  • 5.引导类加载器通过C/C++语言编写无法直接获取;扩展类加载器包含系统类加载器
    public class ClassLoaderTest {public static void main(String[] args) {//获取系统类加载器ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2//获取其上层:扩展类加载器ClassLoader extClassLoader = systemClassLoader.getParent();System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19//获取其上层:获取不到引导类加载器ClassLoader bootstrapClassLoader = extClassLoader.getParent();System.out.println(bootstrapClassLoader);//null//对于用户自定义类来说:默认使用系统类加载器进行加载ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2//String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载ClassLoader classLoader1 = String.class.getClassLoader();System.out.println(classLoader1);//nullClassLoader classLoader2 = Integer.class.getClassLoader();System.out.println(classLoader2);//null}
    }
    
  • 7.通过下列代码可以动态获取到引导类,扩展类,系统类加载器负责加载的类,其中越底层能加载的类就越多
    public class ClassLoaderTest1 {public static void main(String[] args) {System.out.println("**********引导类加载器**************");//获取BootstrapClassLoader能够加载的api的路径URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();for (URL element : urLs) {System.out.println(element.toExternalForm());}//从上面的路径中随意选择一个类,来看看他的类加载器是什么:引导类加载器ClassLoader classLoader = Provider.class.getClassLoader();System.out.println(classLoader);System.out.println("***********扩展类加载器*************");String extDirs = System.getProperty("java.ext.dirs");for (String path : extDirs.split(";")) {System.out.println(path);}//从上面的路径中随意选择一个类,来看看他的类加载器是什么:扩展类加载器ClassLoader classLoader1 = CurveDB.class.getClassLoader();System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19dSystem.out.println("***********系统类加载器*************");String appDirs = System.getProperty("java.class.path");for (String path : appDirs.split(";")) {System.out.println(path);}//从上面的路径中随意选择一个类,来看看他的类加载器是什么:系统类加载器ClassLoader classLoaderTest = ClassLoaderTest1.class.getClassLoader();System.out.println(classLoaderTest);//sun.misc.Launcher$AppClassLoader@18b4aac2}
    }
    

1.引导类加载器(Bootstrap ClassLoader)

  • 1.该类加载器使用C/C++语言实现,是JVM的一部分,通过Java代码是无法获取的
  • 2.该类加载器用来加载Java的核心库,提供JVM自身需要的类
    • 1.JAVA_HOME/jre/lib目录下的rt.jarresources.jar
    • 2.sun.boot.class.path路径下的内容
  • 3.该类加载器并不继承自java.lang.ClassLoader,没有父加载器
  • 4.该类加载器也用来加载扩展类和系统类加载器,并指定为他们的父类加载器
  • 5.出于安全考虑,引导类加载器只加载包名为javajavaxsun等开头的类

2.扩展类加载器(Extension ClassLoader)

  • 1.Java语言编写,由sun.misc.Launcher$ExtClassLoader实现(内部类),该加载器是JVM自带的
  • 2.ExtClassLoader派生于ClassLoader抽象类
  • 3.该类加载器的父类加载器为引导类加载器
  • 4.从java.ext.dirs系统属性所指定的目录中加载类库或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库
  • 5.如果用户创建的jar包放在此目录(jre/lib/ext)下,也会自动由扩展类加载类加载,主要用来加载核心包外的扩展目录下的jar包
    在这里插入图片描述

3.系统类加载器(System Class Loader)

  • 1.Java语言编写,由sun.misc.Launcher$AppClassLoader实现(内部类),该加载器是JVM自带的
  • 2.AppClassLoade派生于ClassLoader抽象类
  • 3.该类加载器的父类加载器为扩展类加载器
  • 4.该类加载器负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
  • 5.该类加载器是程序中默认的类加载器,一般来说Java应用的类都是由它来完成加载
  • 6.通过ClassLoader.getSystemClassLoader()方法可以获取到该类加载器
    在这里插入图片描述

4.用户自定义类加载器

  • 1.Java开发可以自定义类加载器,定制类的加载方式
  • 2.自定义类加载器的优势
    • 1.隔离加载类(不同中间件的加载是隔离的,确保加载jar包时相同名称的路径不会冲突)
    • 2.修改类加载的方式(修改为需要的时候动态的加载)
    • 3.扩展加载源(本地磁盘,网络,扩展其他加载源)
    • 4.防止源码泄露(自定义类加载器实现加密解密)
  • 3.实现步骤
    • 1.通过继承抽象类java.class.ClassLoader的方式,实现自定义类加载器
    • 2.JDK1.2之前,自定义类加载器会去继承ClassLoader类并重写loadClass()方法,从而实现自定义的类加载类
    • 3.JDK1.2之后,不建议覆盖loadClass()方法,建议把自定义的类加载逻辑写在findClass()方法中
    • 4.编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样可以避免去编写findClass()方法以及获取字节码流的方式,使自定义类加载器编写更加简洁
    package com.java;import java.io.FileNotFoundException;/*** 自定义用户类加载器*/
    public class CustomClassLoader extends ClassLoader {@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] result = getClassFromCustomPath(name);if(result == null){throw new FileNotFoundException();}else{//如果不为null则继续调用该方法return defineClass(name,result,0,result.length);}} catch (FileNotFoundException e) {e.printStackTrace();}throw new ClassNotFoundException(name);}//根据指定路径已二进制流的方式读入到内存中形成字节数组private byte[] getClassFromCustomPath(String name){//从自定义路径中加载指定类:细节略//如果指定路径的字节码文件进行了加密,则需要在此方法中进行解密操作。return null;}public static void main(String[] args) {CustomClassLoader customClassLoader = new CustomClassLoader();try {Class<?> clazz = Class.forName("One",true,customClassLoader);Object obj = clazz.newInstance();System.out.println(obj.getClass().getClassLoader());} catch (Exception e) {e.printStackTrace();}}
    }
    

5.ClassLoader

  • 1.ClassLoader是一个抽象类,除引导类加载器其余的类加载器都继承自ClassLoaser
  • 2.sun.misc.LauncherJVM的入口应用,ExtClassLoaderAppClassLoader都是Launcher的内部类
  • 3.加载类生成Class的方式:
    • 1.通过loadClass方法传入一个想要加载的路径然后返回Class的类实例
    • 2.通过findClassdefineClass配合传入一个想要加载的路径然后返回Class的类实例

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

1.获取ClassLoader的途径

在这里插入图片描述
在这里插入图片描述

6.双亲委派机制

  • 1.Java虚拟机class文件采用的是按需加载的方式,当需要使用该类时才会将它的class文件加载到内存生成Class对象
  • 2.Java虚拟机加载某个类的class文件时,采用的双亲委派机制,即把请求交由父类处理,它是一种任务委派模式

1.工作原理

  • 1.如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行
  • 2.如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的引导类加载器
  • 3.如果父类加载器可以完成类加载任务,则成功返回,如果父类加载器无法完成此加载任务,子类才会尝试自己去加载,这就是双亲委派机制
    在这里插入图片描述

2.实例

  • 1.本地创建一个和Java.lang.String同样包级的String类,如果该类被加载,则会输出静态代码块中的内容
    package java.lang;public class String {static{System.out.println("我是自定义的String类的静态代码块");}
    }
    
  • 2.创建一个测试类,调用Java.lang.String
    public class StringTest {public static void main(String[] args) {java.lang.String str = new java.lang.String();System.out.println("hello");StringTest test = new StringTest();System.out.println(test.getClass().getClassLoader()); //自定义的类加载器一般是系统类加载器System.out.println(str.getClass().getClassLoader()); //核心类库加载器是引导类加载器}
    }
    
  • 3.测试结果发现自动调用的是核心类库中String类而不是本地中的String类(只限测试使用),因此可防止恶意攻击导致项目崩溃
    在这里插入图片描述
  • 4.上述自定义String类添加main方法并执行,因为双亲委派机制,最后本地String类的加载交给引导类加载器去加载核心类库中的String类,而该类不存在main方法,所以会报错
    在这里插入图片描述
  • 5.系统类接口由引导类加载器加载,实现其功能的第三方的jar包加载一般是通过线程上下文类加载器加载,默认为系统类型加载器
    在这里插入图片描述

3.优势

  • 1.避免类的重复加载
  • 2.保护程序的安全,防止核心API被随意篡改,防止自定义的类使JVM崩溃
    • 例:自定义类java.lang.String
  • 3.禁止自定义包名与系统包名冲突,因为该包名下的类由引导类加载器加载,但是该类并不存在于引导类加载器要加载的路径中
    在这里插入图片描述

4.沙箱安全机制

  • 1.沙箱:是一个限制程序运行的环境
  • 2.沙箱机制:是将Java代码限定在虚拟机JVM特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏,可参考Java沙箱机制
  • 3.简单来说
    • 1.上述自定义String类,加载的时候会优先使用引导类加载器加载
    • 2.引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中java\lang\String.class)
    • 3.上述报错信息说没有main方法就是因为加载的是rt.jar包中的String类,这样可以保证对java核心源代码的保护,这就是沙箱安全机制

5.判断两个Class对象是否为同一个类

  • 1.JVM中表示两个Class对象是否为同一个类存在两个必要条件
    • 1.类的完整类名必须一致,包括包名
    • 2.加载这个类的classLoader(指:ClassLoader实例对象)必须相同(例:上述两个String类不同,因为类加载器不同)
  • 2.JVM中即使两个类对象(Class对象)来源同一个class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不同,那么这两个对象也是不相等的
  • 3.JVM必须知道一个类型是由引导类加载器加载还是由自定义类加载器加载的
  • 4.如果一个类型是由自定义类加载器加载的,JVM会将这个类加载器的一个引用作为类型信在这里插入代码片息的一部分保存在方法区中,引导类除外因为该类标识为null
  • 5.当解析一个类型到另一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的(动态链接)

6.类的主动使用和被动使用

  • 1.Java程序对类的使用方式分为:主动使用和被动使用
    • 1.主动使用:分为七种情况
      • 1.创建类的实例
      • 2.访问某个类或接口的静态变量,或者对该静态变量赋值
      • 3.调用类的静态方法
      • 4.反射(Class.forName等)
      • 5.初始化一个类的子类
      • 6.Java虚拟机启动被标明启动类的类
      • 7.JDK7开始提供的动态语言支持:
        • 1.java.lang.invoke.MethodHandle实例的解析结果
        • 2.REF_getStatic,REF_putStatic,REF_invokeStatic句柄对应的类没有初始化,则初始化
    • 2.被动使用
      • 1.除以上七种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化
  • 2.不管是主动使用还是被动使用都会被加载,只要使用都会被加载,但是只有主动使用会执行类加载的初始化步骤

这篇关于JVM - 1.类加载子系统的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1099673

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring AI集成DeepSeek的详细步骤

《SpringAI集成DeepSeek的详细步骤》DeepSeek作为一款卓越的国产AI模型,越来越多的公司考虑在自己的应用中集成,对于Java应用来说,我们可以借助SpringAI集成DeepSe... 目录DeepSeek 介绍Spring AI 是什么?1、环境准备2、构建项目2.1、pom依赖2.2