深入解析Java中的String对象及其性能优化

  • 作者主页: 🔗进朱者赤的博客

  • 精选专栏:🔗经典算法

  • 作者简介:阿里非典型程序员一枚 ,记录在大厂的打怪升级之路。 一起学习Java、大数据、数据结构算法(公众号同名

  • ❤️觉得文章还不错的话欢迎大家点赞👍➕收藏⭐️➕评论,💬支持博主,记得点个大大的关注,持续更新🤞
    ————————————————-

引言

在Java编程中,String对象无疑是我们日常工作中使用最为频繁的数据类型之一。然而,其性能问题却往往被开发者所忽视。由于String对象在内存中占用空间较大,因此高效地使用字符串对于提升系统整体性能至关重要。本文将从String对象的实现、特性以及实际使用中的优化三个方面入手,深入探讨其性能优化的方法。

String对象的实现与特性

在这里插入图片描述

Java 6及以前版本

在Java 6及以前的版本中,String对象是对char数组进行了封装实现的对象。它主要包括四个成员变量:char数组、偏移量offset、字符数量count、哈希值hash。通过offsetcount两个属性,String对象能够高效地定位char[]数组,从而快速获取字符串。这种设计有助于实现字符串的共享,减少内存空间的占用,但也可能导致内存泄漏问题。

Java 7至Java 8版本

从Java 7版本开始,Java对String类做了一些调整。在String类中,不再包含offsetcount两个变量。这样的改动使得String对象占用的内存稍微减少,同时,String.substring方法也不再共享原始的char[]数组,从而解决了使用该方法可能导致的内存泄漏问题。

Java 9及以后版本

在Java 9及以后的版本中,工程师们对String类进行了进一步的优化。他们将char[]字段改为了byte[]字段,并新增了一个名为coder的属性。这个新属性用于标识字符串的编码格式。

之所以进行这样的修改,是因为在存储单字节编码内的字符时,使用char数组(每个字符占2个字节)会显得非常浪费。因此,JDK 1.9中的String类采用了占1个字节的byte数组来存放字符串,以节约内存空间。而coder属性则用于在计算字符串长度或使用indexOf()函数时,判断如何计算字符串长度。coder属性有两个可能的值:0代表Latin-1(单字节编码),1代表UTF-16。

实际使用中的优化

字符串创建与比较

在Java中,字符串的创建和比较是常见的操作。然而,不正确的创建和比较方式可能会导致性能问题。例如,通过new String("abc")方式创建的字符串与通过字符串常量池(String Pool)创建的字符串(如"abc")在内存中的表示是不同的。因此,在比较两个字符串是否相等时,应该使用equals()方法而不是==运算符。

此外,intern()方法也是一个值得注意的方法。它可以将一个字符串添加到字符串常量池中,并返回该字符串在常量池中的引用。如果字符串已经存在于常量池中,则直接返回该引用。这个方法在某些场景下可以用于优化字符串的使用。

面试题解析

以下是一个常见的面试题,用于考察面试者对字符串的理解:

String str1 = "abc";
String str2 = new String("abc");
String str3 = str2.intern();
assertSame(str1 == str2); // 预期为false
assertSame(str2 == str3); // 预期为false
assertSame(str1 == str3); // 预期为true(在Java 7及以上版本)

在这个例子中,str1是通过字符串常量池创建的,而str2是通过new关键字在堆内存中创建的。因此,str1str2在内存中的引用是不同的,所以str1 == str2的结果为false。而str3是通过调用str2.intern()方法得到的,由于字符串"abc"已经存在于字符串常量池中,所以str3实际上是指向常量池中"abc"的引用,因此str1 == str3的结果为true(在Java 7及以上版本)。

深入解析Java中String对象的不可变性

当我们探讨Java中的String对象时,一个不可忽视的特性就是其不可变性(immutable)。这种特性使得String对象一旦创建,其内容就无法被修改。了解String的不可变性对于正确使用Java字符串和避免常见的性能问题至关重要。

String对象的不可变性

在Java中,String类被final关键字修饰,意味着这个类不能被继承。同时,String对象内部用于存储字符的char数组也被声明为finalprivate,这确保了String对象的内容在创建后无法被修改。

不可变性的好处

  1. 安全性:由于String对象是不可变的,它们可以被安全地共享而无需担心被其他代码段意外修改。这提高了代码的安全性和可维护性。

  2. 缓存哈希值:由于String对象的内容不会改变,因此其哈希值(hashCode()方法返回的值)在对象被创建时就可以被计算并缓存起来。这提高了String对象在哈希表(如HashMap)中的性能。

  3. 字符串常量池:Java提供了字符串常量池(String Constant Pool),用于存储字符串字面量。当使用字面量创建String对象时(如String str = "abc";),JVM会首先检查常量池中是否已存在相同内容的字符串。如果存在,则返回该字符串的引用;否则,在常量池中创建一个新的字符串对象。这种机制减少了不必要的对象创建,提高了内存使用效率。

  4. 线程安全:由于String对象是不可变的,因此它们在多线程环境中是安全的。无需额外的同步机制即可在多个线程之间安全地共享String对象。

对象与对象引用的区别

在Java中,我们经常混淆对象和对象引用。当我们说String str = "hello";时,str实际上是一个指向String对象的引用,而不是对象本身。对象在内存中占用一块地址空间,而str则是这个地址的引用。

当我们执行str = "world";时,并没有修改原来的"hello"对象,而是创建了一个新的"world"对象,并将str的引用指向了这个新对象。原来的"hello"对象仍然存在于内存中(除非没有任何引用指向它,从而被垃圾收集器回收)。

这种机制使得Java中的字符串操作非常灵活和高效。但是,也需要注意不要在不必要的情况下频繁地创建新的字符串对象,以避免不必要的内存分配和垃圾收集开销。

小结

String对象的不可变性是Java语言中的一个重要特性,它带来了安全性、性能优化和线程安全等多方面的好处。同时,了解对象和对象引用的区别对于正确使用Java字符串至关重要。通过合理使用字符串常量池和避免不必要的字符串对象创建,我们可以进一步提高Java程序的性能和内存使用效率。

String对象的优化

了解了String对象的实现原理和特性后,我们将结合实际场景探讨如何优化String对象的使用,并指出在优化过程中需要注意的事项。

1. 如何构建超大字符串?

字符串常量的拼接

在编程中,字符串的拼接很常见。尽管String对象是不可变的,但在某些情况下,如字符串常量的拼接,编译器会进行优化。例如:

String str = "ab" + "cd" + "ef";

在编译时,上述代码会被优化为:

String str = "abcdef";

这意味着编译器在编译阶段就进行了优化,只生成了一个String对象。

字符串变量的拼接

然而,当涉及到字符串变量的拼接时,情况就不同了。以下代码示例:

String str = "abcdef";
for (int i = 0; i < 1000; i++) {
    str = str + i;
}

虽然从代码上看似简单,但实际上每次循环都会创建一个新的String对象,这会导致大量内存分配和垃圾回收,从而影响性能。为了提高性能,可以使用StringBuilderStringBuffer来构建超大字符串。

使用StringBuilder
StringBuilder sb = new StringBuilder("abcdef");
for (int i = 0; i < 1000; i++) {
    sb.append(i);
}
String str = sb.toString();

使用StringBuilder可以避免不必要的内存分配和垃圾回收,显著提高性能。

使用StringBuffer(线程安全)

如果在多线程环境中需要构建字符串,则应使用StringBuffer。但请注意,由于StringBuffer是线程安全的,因此其性能通常低于StringBuilder

2. 使用String.intern()节省内存

Twitter每次发布消息状态的时候,都会产生一个地址信息,以当时Twitter用户的规模预估,服务器需要32G的内存来存储地址信息。
在某些情况下,如Twitter中存储大量重复的地址信息,可以使用String.intern()方法来节省内存。String.intern()方法返回字符串对象的规范表示形式。如果字符串常量池中已经存在一个与该字符串相等的字符串,则返回该字符串的引用;否则,将此字符串添加到常量池中,并返回此字符串的引用。

示例

public class SharedLocation {
    private String city;
    private String region;
    private String countryCode;

    public void setCity(String city) {
        this.city = city.intern();
    }

    // ... 其他setter方法 ...
}

通过在SharedLocation类中使用String.intern()方法,可以确保相同的字符串只被存储一次在字符串常量池中,从而显著减少内存的使用。但请注意,这种方法可能会降低性能,并且需要谨慎使用,以避免潜在的问题。

小结

在优化String对象的使用时,我们需要根据具体场景选择适当的方法。对于字符串常量的拼接,编译器会自动进行优化;对于字符串变量的拼接,应使用StringBuilderStringBuffer来提高性能;在需要节省内存的场景中,可以考虑使用String.intern()方法。但请务必注意每种方法的优缺点,并根据实际情况进行选择。

如何使用String.intern节省内存?

讲完了构建字符串的优化后,我们再来探讨下如何通过String.intern()方法来节省内存。在实际应用中,尤其是处理大量重复字符串数据时,内存使用是一个需要考虑的重要因素。Twitter这样的社交平台就是一个很好的例子,其中用户的地址信息可能包含大量重复的城市、省份和国家等字符串。

案例背景

Twitter每次发布消息时,都会记录用户的地址信息。考虑到Twitter庞大的用户规模,地址信息的存储可能会占用大量的内存。为了优化内存使用,Twitter的工程师们采取了多种策略,其中之一就是使用String.intern()

使用String.intern()

String.intern()方法用于将字符串添加到Java的字符串常量池中,并返回该字符串在常量池中的引用。如果常量池中已经存在具有相同内容的字符串,则intern()方法会直接返回该字符串的引用,而不是创建一个新的字符串对象。这有助于减少内存中的重复字符串对象,从而节省内存。

示例代码

首先,我们假设有一个MessageInfo类,它包含了地址信息:

public class MessageInfo {
    private String city;
    private String region;
    private String countryCode;
    // ... 其他字段和getter/setter方法 ...
}

public class SharedLocation {
    private String city;
    private String region;
    private String countryCode;

    public void setCity(String city) {
        this.city = city.intern();
    }

    public void setRegion(String region) {
        this.region = region.intern();
    }

    public void setCountryCode(String countryCode) {
        this.countryCode = countryCode.intern();
    }
    // ... 其他setter方法 ...
}

public class Location {
    private SharedLocation sharedLocation;
    private double longitude;
    private double latitude;

    // ... 构造器、getter/setter方法 ...
}

在上面的代码中,我们在SharedLocation类的setter方法中调用了intern()方法。这样,当设置城市、省份或国家代码时,如果常量池中已经存在具有相同内容的字符串,就会直接使用该字符串的引用,从而避免了重复创建字符串对象。

通过优化,数据存储大小减到了20G左右。但对于内存存储这个数据来说,依然很大,怎么办呢?

这个案例来自一位Twitter工程师在QCon全球软件开发大会上的演讲,他们想到的解决方法,就是使用String.intern来节省内存空间,从而优化String对象的存储。

具体做法就是,在每次赋值的时候使用String的intern方法,如果常量池中有相同值,就会重复使用该对象,返回对象引用,这样一开始的对象就可以被回收掉。这种方式可以使重复性非常高的地址信息存储大小从20G降到几百兆。

SharedLocation sharedLocation = new SharedLocation();

sharedLocation.setCity(messageInfo.getCity().intern());		sharedLocation.setCountryCode(messageInfo.getRegion().intern());
sharedLocation.setRegion(messageInfo.getCountryCode().intern());

Location location = new Location();
location.set(sharedLocation);
location.set(messageInfo.getLongitude());
location.set(messageInfo.getLatitude());

为了更好地理解,我们再来通过一个简单的例子,回顾下其中的原理:

String a =new String("abc").intern();
String b = new String("abc").intern();
          
if(a==b) {
    System.out.print("a==b");
}

输出结果:

a==b

在字符串常量中,默认会将对象放入常量池;在字符串变量中,对象是会创建在堆内存中,同时也会在常量池中创建一个字符串对象,String对象中的char数组将会引用常量池中的char数组,并返回堆内存对象引用。

如果调用intern方法,会去查看字符串常量池中是否有等于该对象的字符串的引用,如果没有,在JDK1.6版本中会复制堆中的字符串到常量池中,并返回该字符串引用,堆内存中原有的字符串由于没有引用指向它,将会通过垃圾回收器回收。

在JDK1.7版本以后,由于常量池已经合并到了堆中,所以不会再复制具体字符串了,只是会把首次遇到的字符串的引用添加到常量池中;如果有,就返回常量池中的字符串引用。

了解了原理,我们再一起看下上边的例子。

在一开始字符串"abc"会在加载类时,在常量池中创建一个字符串对象。

创建a变量时,调用new Sting()会在堆内存中创建一个String对象,String对象中的char数组将会引用常量池中字符串。在调用intern方法之后,会去常量池中查找是否有等于该字符串对象的引用,有就返回引用。

创建b变量时,调用new Sting()会在堆内存中创建一个String对象,String对象中的char数组将会引用常量池中字符串。在调用intern方法之后,会去常量池中查找是否有等于该字符串对象的引用,有就返回引用。

而在堆内存中的两个对象,由于没有引用指向它,将会被垃圾回收。所以a和b引用的是同一个对象。

如果在运行时,创建字符串对象,将会直接在堆内存中创建,不会在常量池中创建。所以动态创建的字符串对象,调用intern方法,在JDK1.6版本中会去常量池中创建运行时常量以及返回字符串引用,在JDK1.7版本之后,会将堆中的字符串常量的引用放入到常量池中,当其它堆中的字符串对象通过intern方法获取字符串对象引用时,则会去常量池中判断是否有相同值的字符串的引用,此时有,则返回该常量池中字符串引用,跟之前的字符串指向同一地址的字符串对象。

以一张图来总结String字符串的创建分配内存地址情况:
在这里插入图片描述

使用intern方法需要注意的一点是,一定要结合实际场景。因为常量池的实现是类似于一个HashTable的实现方式,HashTable存储的数据越大,遍历的时间复杂度就会增加。如果数据过大,会增加整个字符串常量池的负担。

原理回顾

  • 在Java中,字符串常量(即直接通过字面量创建的字符串)默认会被放入字符串常量池中。
  • 当我们使用new String("abc")创建一个新的字符串对象时,这个对象会被创建在堆内存中,并且如果此时常量池中还没有"abc"这个字符串,则会在常量池中创建一个新的字符串对象(在JDK 1.6及之前版本)。
  • 调用intern()方法时,JVM会检查常量池中是否已经存在与当前字符串内容相同的字符串。如果存在,则返回该字符串的引用;如果不存在,则在常量池中创建一个新的字符串对象(在JDK 1.6及之前版本),并返回该字符串的引用。
  • 在JDK 1.7及之后的版本中,字符串常量池被移动到了堆中,因此intern()方法的行为稍有不同。但核心思想仍然是检查常量池中是否存在相同内容的字符串,并返回其引用。

注意事项

  • String.intern()方法虽然可以节省内存,但也会带来一定的性能开销,因为每次调用都需要检查常量池。因此,在决定是否使用intern()方法时,需要权衡内存节省和性能开销之间的利弊。
  • String.intern()方法返回的是字符串在常量池中的引用,这意味着它返回的字符串是不可变的。如果你需要修改字符串的内容,那么你应该考虑使用StringBuilderStringBuffer
  • 在多线程环境中使用String.intern()时需要特别小心,因为字符串常量池是全局共享的,并且intern()方法的调用是线程安全的。但是,如果多个线程同时调用intern()方法并期望获得相同的字符串引用,那么它们可能会因为竞态条件而得到不同的结果。因此,在多线程环境中使用String.intern()时需要谨慎处理线程同步问题。

如何使用字符串的分割方法?

最后我想跟你聊聊字符串的分割,这种方法在编码中也很最常见。Split()方法使用了正则表达式实现了其强大的分割功能,而正则表达式的性能是非常不稳定的,使用不恰当会引起回溯问题,很可能导致CPU居高不下。

所以我们应该慎重使用Split()方法,我们可以用String.indexOf()方法代替Split()方法完成字符串的分割。如果实在无法满足需求,你就在使用Split()方法时,对回溯问题加以重视就可以了。

**总结:

在深入探讨了String字符串的性能优化后,我们认识到优化字符串处理对提升系统整体性能的重要性。Java在版本迭代中,通过精心调整成员变量和内存管理机制,不断对String对象进行优化,以更高效地使用内存资源。

String对象的不可变性是Java语言设计中的一个关键特性,它不仅确保了字符串的安全性,也为字符串常量池的实现提供了基础。通过减少相同值的字符串对象的重复创建,常量池有效地节约了内存空间。

然而,不可变性也带来了挑战。在处理长字符串拼接时,我们需要显式使用StringBuilder类来避免性能下降。StringBuilder通过其可变性,允许我们在一个对象内多次进行字符串的追加操作,从而显著提高拼接性能。

除了使用StringBuilder,我们还可以通过intern方法进一步优化字符串使用。这个方法允许我们将一个字符串引用到常量池中的现有对象,如果常量池中已经存在相同值的对象,则直接返回该对象的引用,从而避免了重复对象的创建。

在此,我想分享一个个人见解:在软件开发中,细节决定成败。对于字符串这样看似简单的数据类型,如果我们对其了解不够深入,使用不够恰当,很可能引发意想不到的问题。例如,我曾在实际工作中因不当地使用正则表达式进行字符串匹配而导致并发瓶颈,这就是一个典型的字符串性能问题。

欢迎一键三连(关注+点赞+收藏),技术的路上一起加油!!!代码改变世界

  • 关于我:阿里非典型程序员一枚 ,记录在大厂的打怪升级之路。 一起学习Java、大数据、数据结构算法(公众号同名),回复暗号,更能获取学习秘籍和书籍等

  • —⬇️欢迎关注下面的公众号:进朱者赤,认识不一样的技术人。⬇️—

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

uniapp乡村社区户籍问外来人员管理系统 微信小程序python+java+node.js+php

基于微信小程序的外来人员管理系统项目的概述设计分析&#xff0c;主要内容有的私教预约平台系统平台的具体分析&#xff0c;进行数据库的是设计&#xff0c;数据采用MySQL数据库&#xff0c;并且对于系统的设计采用比较人性化的操作设计&#xff0c;对于系统出现的错误信息可以…

用Jenkins Gerrit-Trigger插件实现提交gerrit后自动启动编译验证-解决编译依赖问题

用Jenkins Gerrit-Trigger插件实现提交gerrit后自动启动编译验证-CSDN博客讨论了如何利用插件在提交gerrit的时候自动出发一个jenkins job编译固件,但是没有解决编译依赖问题。本文提出一种解决方案 首先在git commit -m ""的时候在commit message中设置Depend-On:…

Typescript基础语法(四)

模块化 模块化是指将复杂的程序拆解为多个独⽴的⽂件单元&#xff0c;每个⽂件被称为⼀个模块。在 TypeScript 中&#xff0c;默认情况下&#xff0c;每个模块都拥有⾃⼰的作⽤域&#xff0c;这意味着在⼀个模块中声明的任何内容&#xff08;如变量、函数、类等&#xff09;在该…

我们的手机是如何连接上网的?骨干网又是什么?

什么是骨干网&#xff08;Backbone Network&#xff09; 几台计算机连接起来&#xff0c;互相可以看到其他人的文件&#xff0c;这叫局域网。整个城市的计算机都连接起来&#xff0c;就是城域网。把城市之间连接起来的网就叫骨干网。 这些骨干网是国家批准的可以直接和国外连…

CUDA CPP Unity Compute Shader

为学 开始一个新的学习计划&#xff0c;涵盖&#xff1a; 主题学习内容CUDAProfessional CUDA C Programming/NVIDIA CUDA初级教程视频(周斌)CCPrimer / The Cherno CPPUnity Compute ShaderUdemy Learn to Write Unity Compute ShadersLinear AlgebraMIT 18.06 Prof.Gilbert…

【Anaconda 3 】Jupyter Notebook 的安装配置及使用

Jupyter Notebook 的安装配置及使用 一、引言 Jupyter Notebook 是一种交互式笔记本&#xff0c;它允许用户将代码、注释、方程式、可视化内容等整合到一个文档中&#xff0c;并支持多种编程语言&#xff0c;如 Python、R、Julia 等。它在数据科学、机器学习和教育领域中得到…

Idea 自动生成测试

先添加测试依赖&#xff01;&#xff01; <!--Junit单元测试依赖--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.9.1</version><scope>test</scope><…

MATLAB 集成

MATLAB 集成&#xff08;Integration&#xff09; 集成处理两种本质上不同的问题。 在第一种类型中&#xff0c;给出了函数的导数&#xff0c;我们想找到函数。因此&#xff0c;我们从根本上扭转了分化的过程。这种反向过程称为反微分&#xff0c;或者找到原始函数&#xff0…

基于SSM的宠物领养平台(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的宠物领养平台&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring Spri…

专项技能训练五《云计算网络技术与应用》实训7-1:安装mininet

文章目录 mininet安装1. 按6-1教程安装opendaylight控制器。2. 按6-2教程安装RYU控制器。3. 按5-1教程安装openvswitch虚拟交换机并开启服务。4. 将老师所给mininet安装包试用winSCP传送至电脑端。5. 安装net-tools。6. 安装mininet7. 安装完成后&#xff0c;使用命令建立拓扑&…

Stable Diffusion webUI 配置指南

Stable Diffusion webUI 配置指南 本博客主要介绍部署Stable Diffusion到本地&#xff0c;生成想要的风格图片。 文章目录 Stable Diffusion webUI 配置指南1、配置环境&#xff08;1&#xff09;pip环境[可选]&#xff08;2&#xff09;conda环境[可选] 2、配置Stable Diffu…

JavaScript 动态网页实例 —— 文字移动

前言 介绍文字使用的特殊效果。本章介绍文字的移动效果,主要包括:文字的垂直滚动、文字的渐隐渐显、文字的闪烁显示、文字的随意拖动、文字的坠落显示、页面内飘动的文字、漫天飞舞的文字、文字的下落效果。对于这些效果,读者只需稍加修改,就可以应用在自己的页面设计中。 …

农作物害虫检测数据集VOC+YOLO格式3575张10类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;3575 标注数量(xml文件个数)&#xff1a;3575 标注数量(txt文件个数)&#xff1a;3575 标注…

电话号码的字母组合 【C++】【力扣刷题】

解题思路&#xff1a; 以第一个为例,digits “23”&#xff0c;表明从电话号码的按键中选取2和3这两个字符&#xff0c;然后去寻找它们各自所对应的字母&#xff0c;这里每一个数字字符所对应的字母的不同&#xff0c;0对应的是空字符&#xff0c;而1的话题目中讲到是不对应任…

中药辨别二

声明&#xff1a;参考懒兔子公益课&#xff0c;参考网络资料和部分网络图片整理而成&#xff0c;仅供学习使用&#xff0c;不提供商业活动价值&#xff0c;文章描述的中药仅供学习&#xff0c;请在专业医师或专业医生指导下使用药材&#xff0c;擅自或其他情况下使用&#xff0…

LeetCode406:根据身高重建队列

题目描述 假设有打乱顺序的一群人站成一个队列&#xff0c;数组 people 表示队列中一些人的属性&#xff08;不一定按顺序&#xff09;。每个 people[i] [hi, ki] 表示第 i 个人的身高为 hi &#xff0c;前面 正好 有 ki 个身高大于或等于 hi 的人。 请你重新构造并返回输入数…

初学python记录:力扣1235. 规划兼职工作

题目&#xff1a; 你打算利用空闲时间来做兼职工作赚些零花钱。 这里有 n 份兼职工作&#xff0c;每份工作预计从 startTime[i] 开始到 endTime[i] 结束&#xff0c;报酬为 profit[i]。 给你一份兼职工作表&#xff0c;包含开始时间 startTime&#xff0c;结束时间 endTime …

[嵌入式AI从0开始到入土]17_Ascend C算子开发

[嵌入式AI从0开始到入土]嵌入式AI系列教程 注&#xff1a;等我摸完鱼再把链接补上 可以关注我的B站号工具人呵呵的个人空间&#xff0c;后期会考虑出视频教程&#xff0c;务必催更&#xff0c;以防我变身鸽王。 第1期 昇腾Altas 200 DK上手 第2期 下载昇腾案例并运行 第3期 官…

JDK14特性

JDK14 1 概述2 语法层面的变化1_instanceof的模式匹配(预览)2_switch表达式(标准)3_文本块改进(第二次预览)4_Records 记录类型(预览 JEP359) 3 API层面的变化4 关于GC1_G1的NUMA内存分配优化2_弃用SerialCMS,ParNewSerial Old3_删除CMS4_ZGC on macOS and Windows 4 其他变化1…

PPT基础

5种ppt仅可读形式 Ⅰ 开始选项卡 1.【幻灯片】组中&#xff1a;新建幻灯片&#xff0c;从大纲中导入幻灯片&#xff1b;修改幻灯片的版式&#xff1b;节&#xff08;新增节&#xff0c;重命名节&#xff09;。 2.【字体】组中&#xff1a;设置字体&#xff0c;字体大小&…