第5页


  • 1:HashMap 的数据结构?

    A:哈希表结构(链表散列:数组+链表)实现,结合数组和链表的优点。当链表长度超过 8 时,链表转换为红黑树。

    transient Node<K,V>\[\] table;

    推荐下自己做的 Spring Boot 的实战项目:

    https://github.com/YunaiV/ruoyi-vue-pro

    2:HashMap 的工作原理?

    HashMap 底层是 hash 数组和单向链表实现,数组中的每个元素都是链表,由 Node 内部类(实现 Map.Entry接口)实现,HashMap 通过 put & get 方法存储和获取。

    存储对象时,将 K/V 键值传给 put() 方法:

    ①、调用 hash(K) 方法计算 K 的 hash 值,然后结合数组长度,计算得数组下标;

    ②、调整数组大小(当容器中的元素个数大于 capacity * loadfactor 时,容器会进行扩容resize 为 2n);

    ③、i.如果 K 的 hash 值在 HashMap 中不存在,则执行插入,若存在,则发生碰撞;

    ii.如果 K 的 hash 值在 HashMap 中存在,且它们两者 equals 返回 true,则更新键值对;

    iii. 如果 K 的 hash 值在 HashMap 中存在,且它们两者 equals 返回 false,则插入链表的尾部(尾插法)或者红黑树中(树的添加方式)。

    (JDK 1.7 之前使用头插法、JDK 1.8 使用尾插法)(注意:当碰撞导致链表大于 TREEIFY_THRESHOLD = 8 时,就把链表转换成红黑树)

    获取对象时,将 K 传给 get() 方法:①、调用 hash(K) 方法(计算 K 的 hash 值)从而获取该键值所在链表的数组下标;②、顺序遍历链表,equals()方法查找相同 Node 链表中 K 值对应的 V 值。

    hashCode 是定位的,存储位置;equals是定性的,比较两者是否相等。

    推荐下自己做的 Spring Cloud 的实战项目:

    https://github.com/YunaiV/onemall

    3.当两个对象的 hashCode 相同会发生什么?

    因为 hashCode 相同,不一定就是相等的(equals方法比较),所以两个对象所在数组的下标相同,"碰撞"就此发生。又因为 HashMap 使用链表存储对象,这个 Node 会存储到链表中。为什么要重写 hashcode 和 equals 方法?推荐看下。

    4.你知道 hash 的实现吗?为什么要这样实现?

    JDK 1.8 中,是通过 hashCode() 的高 16 位异或低 16 位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度,功效和质量来考虑的,减少系统的开销,也不会造成因为高位没有参与下标的计算,从而引起的碰撞。

    5.为什么要用异或运算符?

    保证了对象的 hashCode 的 32 位值只要有一位发生改变,整个 hash() 返回值就会改变。尽可能的减少碰撞。

    6.HashMap 的 table 的容量如何确定?loadFactor 是什么?该容量如何变化?这种变化会带来什么问题?

    ①、table 数组大小是由 capacity 这个参数确定的,默认是16,也可以构造时传入,最大限制是1<<30;

    ②、loadFactor 是装载因子,主要目的是用来确认table 数组是否需要动态扩展,默认值是0.75,比如table 数组大小为 16,装载因子为 0.75 时,threshold 就是12,当 table 的实际大小超过 12 时,table就需要动态扩容;

    ③、扩容时,调用 resize() 方法,将 table 长度变为原来的两倍(注意是 table 长度,而不是 threshold)

    ④、如果数据很大的情况下,扩展时将会带来性能的损失,在性能要求很高的地方,这种损失很可能很致命。

    7.HashMap中put方法的过程?

    答:“调用哈希函数获取Key对应的hash值,再计算其数组下标;

    如果没有出现哈希冲突,则直接放入数组;如果出现哈希冲突,则以链表的方式放在链表后面;

    如果链表长度超过阀值( TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表;

    如果结点的key已经存在,则替换其value即可;

    如果集合中的键值对大于12,调用resize方法进行数组扩容。”

    8.数组扩容的过程?

    创建一个新的数组,其容量为旧数组的两倍,并重新计算旧数组中结点的存储位置。结点在新数组中的位置只有两种,原下标位置或原下标+旧数组的大小。

    9.拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?

    之所以选择红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。推荐:面试问红黑树,我脸都绿了。

    而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。

    10.说说你对红黑树的见解?

    11.jdk8中对HashMap做了哪些改变?

    在java 1.8中,如果链表的长度超过了8,那么链表将转换为红黑树。(桶的数量必须大于64,小于64的时候只会扩容)

    发生hash碰撞时,java 1.7 会在链表的头部插入,而java 1.8会在链表的尾部插入

    在java 1.8中,Entry被Node替代(换了一个马甲。

    12.HashMap,LinkedHashMap,TreeMap 有什么区别?

    HashMap 参考其他问题;

    LinkedHashMap 保存了记录的插入顺序,在用 Iterator 遍历时,先取到的记录肯定是先插入的;遍历比 HashMap 慢;

    TreeMap 实现 SortMap 接口,能够把它保存的记录根据键排序(默认按键值升序排序,也可以指定排序的比较器)

    13.HashMap & TreeMap & LinkedHashMap 使用场景?

    一般情况下,使用最多的是 HashMap。

    HashMap:在 Map 中插入、删除和定位元素时;

    TreeMap:在需要按自然顺序或自定义顺序遍历键的情况下;

    LinkedHashMap:在需要输出的顺序和输入的顺序相同的情况下。

    14.HashMap 和 HashTable 有什么区别?

    ①、HashMap 是线程不安全的,HashTable 是线程安全的;

    ②、由于线程安全,所以 HashTable 的效率比不上 HashMap;

    ③、HashMap最多只允许一条记录的键为null,允许多条记录的值为null,而 HashTable不允许;

    ④、HashMap 默认初始化数组的大小为16,HashTable 为 11,前者扩容时,扩大两倍,后者扩大两倍+1;

    ⑤、HashMap 需要重新计算 hash 值,而 HashTable 直接使用对象的 hashCode

    15.Java 中的另一个线程安全的与 HashMap 极其类似的类是什么?同样是线程安全,它与 HashTable 在线程同步上有什么不同?

    ConcurrentHashMap 类(是 Java并发包 java.util.concurrent 中提供的一个线程安全且高效的 HashMap 实现)。

    HashTable 是使用 synchronize 关键字加锁的原理(就是对对象加锁);

    而针对 ConcurrentHashMap,在 JDK 1.7 中采用 分段锁的方式;JDK 1.8 中直接采用了CAS(无锁算法)+ synchronized。

    16.HashMap & ConcurrentHashMap 的区别?

    除了加锁,原理上无太大区别。另外,HashMap 的键值对允许有null,但是ConCurrentHashMap 都不允许。

    17.为什么 ConcurrentHashMap 比 HashTable 效率要高?

    HashTable 使用一把锁(锁住整个链表结构)处理并发问题,多个线程竞争一把锁,容易阻塞;

    ConcurrentHashMap

    18.针对 ConcurrentHashMap 锁机制具体分析(JDK 1.7 VS JDK 1.8)

    JDK 1.7 中,采用分段锁的机制,实现并发的更新操作,底层采用数组+链表的存储结构,包括两个核心静态内部类 Segment 和 HashEntry。

    ①、Segment 继承 ReentrantLock(重入锁) 用来充当锁的角色,每个 Segment 对象守护每个散列映射表的若干个桶;

    ②、HashEntry 用来封装映射表的键-值对;

    ③、每个桶是由若干个 HashEntry 对象链接起来的链表

    彻底服了:HashMap 夺命二十一问,顶不住了!

    彻底服了:HashMap 夺命二十一问,顶不住了!

    来源:cnblogs.com/Young111/p/11519952.html1:HashMap 的数据结构?A:哈希表结构(链表散列:数组+链表)实现,结合数组和链表的优点。当链表长度超过 8 时,链表转换为红黑树。transient Node<K,V>\[\] table...

    Java知识 2021-10-01 47 0
  • 别瞎学了!Docker彻底输了,输给了K8S
摘要:肝疼

    别瞎学了!Docker彻底输了,输给了K8S 摘要:肝疼

    容器内抓包定位网络问题容器进程主动退出、只能运行一个参数JVM 参数在容器中突然失效了解云原生的前世今生以及面试必备技术概念;深入掌握 K8S、Istio、Serverless 核心组件剖析;手把手带你从 0 搭建 K8S 集群;系统性提升云原生架构设计能力。高开和架构师都在研究的云原生技术你需要真正掌握它...

    Java知识 2021-09-30 69 0

  • 在真实业务场景中,数据库中经常需要存储某些客户的关键性敏感信息如:身份证号、银行卡号、姓名、手机号码等,此类信息按照合规要求,通常需要实现加密存储以满足合规要求。

    痛点一

    通常的解决方案是我们书写SQL的时候,把对应的加密字段手动进行加密再进行插入,在查询的时候使用之前再手动进行解密。此方法固然可行,但是使用起来非常不便捷且繁琐,使得日常的业务开发与存储合规的细节紧耦合

    推荐下自己做的 Spring Boot 的实战项目:

    https://github.com/YunaiV/ruoyi-vue-pro

    痛点二

    对于一些为了快速上线而一开始没有实现合规脱敏的系统,如何比较快速的使得已有业务满足合规要求的同时,尽量减少对原系统的改造。(通常的这个过程至少包括:

    1、 新增脱敏列的存储

    2、 同时做数据迁移

    3、 业务的代码做兼容逻辑等

    Apache ShardingSphere下面存在一个数据脱敏模块,此模块集成的常用的数据脱敏的功能。其基本原理是对用户输入的SQL进行解析拦截,并依靠用户的脱敏配置进行SQL的改写,从而实现对字段的加密及加密字段的解密。最终实现对用户无感的加解密存储、查询。

    推荐下自己做的 Spring Cloud 的实战项目:

    https://github.com/YunaiV/onemall

    脱敏配置Quick Start——Spring 显示配置:

    以下介绍基于Spring如何快速让系统支持脱敏配置。

    1、引入依赖

    <!-- for spring namespace -->
    <dependency>
        <groupId>org.apache.shardingsphere</groupId>
        <artifactId>sharding-jdbc-spring-namespace</artifactId>
        <version>${sharding-sphere.version}</version>
    </dependency>

    2、创建脱敏配置规则对象

    在创建数据源之前,需要准备一个EncryptRuleConfiguration进行脱敏的配置,以下是一个例子,对于同一个数据源里两张表card_info,pay_order的不同字段进行AES的加密

    private EncryptRuleConfiguration getEncryptRuleConfiguration() {
    Properties props = new Properties();

    //自带aes算法需要
    props.setProperty("aes.key.value", aeskey);
    EncryptorRuleConfiguration encryptorConfig = new EncryptorRuleConfiguration("AES", props);

    //自定义算法
    //props.setProperty("qb.finance.aes.key.value", aeskey);
    //EncryptorRuleConfiguration encryptorConfig = new EncryptorRuleConfiguration("QB-FINANCE-AES", props);

    EncryptRuleConfiguration encryptRuleConfig = new EncryptRuleConfiguration();
    encryptRuleConfig.getEncryptors().put("aes", encryptorConfig);

    //START: card_info 表的脱敏配置
    {
        EncryptColumnRuleConfiguration columnConfig1 = new EncryptColumnRuleConfiguration("""name""""aes");
        EncryptColumnRuleConfiguration columnConfig2 = new EncryptColumnRuleConfiguration("""id_no""""aes");
        EncryptColumnRuleConfiguration columnConfig3 = new EncryptColumnRuleConfiguration("""finshell_card_no""""aes");
        Map<String, EncryptColumnRuleConfiguration> columnConfigMaps = new HashMap<>();
        columnConfigMaps.put("name", columnConfig1);
        columnConfigMaps.put("id_no", columnConfig2);
        columnConfigMaps.put("finshell_card_no", columnConfig3);
        EncryptTableRuleConfiguration tableConfig = new EncryptTableRuleConfiguration(columnConfigMaps);
        encryptRuleConfig.getTables().put("card_info", tableConfig);
    }

    //END: card_info 表的脱敏配置

    //START: pay_order 表的脱敏配置
    {
        EncryptColumnRuleConfiguration columnConfig1 = new EncryptColumnRuleConfiguration("""card_no""""aes");
        Map<String, EncryptColumnRuleConfiguration> columnConfigMaps = new HashMap<>();
        columnConfigMaps.put("card_no", columnConfig1);
        EncryptTableRuleConfiguration tableConfig = new EncryptTableRuleConfiguration(columnConfigMaps);
        encryptRuleConfig.getTables().put("pay_order", tableConfig);
    }

    log.info("脱敏配置构建完成:{} ", encryptRuleConfig);
    return encryptRuleConfig;
    }

    说明:

    1、 创建 EncryptColumnRuleConfiguration 的时候有四个参数,前两个参数分表叫plainColumn、cipherColumn,其意思是数据库存储里面真实的两个列(名文列、脱敏列),对于新的系统,只需要设置脱敏列即可,所以以上示例为plainColumn为”“。

    2、 创建EncryptTableRuleConfiguration 的时候需要传入一个map,这个map存的value即#1中说明的EncryptColumnRuleConfiguration ,而其key则是一个逻辑列,对于新系统,此逻辑列即为真实的脱敏列。Sharding Shpere在拦截到SQL改写的时候,会按照用户的配置,把逻辑列映射为名文列或者脱敏列(默认)如下的示例

    敏感数据,“一键脱敏”,Sharding Sphere 完美搞定

    敏感数据,“一键脱敏”,Sharding Sphere 完美搞定

    来源:sourl.cn/uLCCPw在真实业务场景中,数据库中经常需要存储某些客户的关键性敏感信息如:身份证号、银行卡号、姓名、手机号码等,此类信息按照合规要求,通常需要实现加密存储以满足合规要求。痛点一通常的解决方案是我们书写SQL的时候,把对应的加密字段手动进行加密再进行插入,在查询的时候使用之前再手动...

    Java知识 2021-09-30 41 0
  • 我们知道实现@Transactional原理是基于spring aop,aop又是动态代理模式的实现,通过对源码的阅读,总结出下面的步骤来了解实际中,在spring 是如何利用aop来实现@Transactional的功能的。

    推荐下自己做的 Spring Boot 的实战项目:

    https://github.com/YunaiV/ruoyi-vue-pro

    spring中声明式事务实现原理猜想

    首先,对于spring中aop实现原理有了解的话,应该知道想要对一个方法进行代理的话,肯定需要定义切点。在@Transactional的实现中,同样如此,spring为我们定义了以 @Transactional 注解为植入点的切点,这样才能知道@Transactional注解标注的方法需要被代理。

    有了切面定义之后,在spring的bean的初始化过程中,就需要对实例化的bean进行代理,并且生成代理对象。

    生成代理对象的代理逻辑中,进行方法调用时,需要先获取切面逻辑,@Transactional注解的切面逻辑类似于@Around,在spring中是实现一种类似代理逻辑。

    面试官:@Transactional 注解是如何实现的?面试必问!

    面试官:@Transactional 注解是如何实现的?面试必问!

    来源:blog.csdn.net/qq_20597727/article/details/84868035@Transactional注解简介@Transactional是spring中声明式事务管理的注解配置方式,相信这个注解的作用大家都很清楚。@Transactional注解可以帮助我们把事务开启、提交...

    Java知识 2021-09-30 46 0
  • 国内用得最多的框架,它排第一!

    国内用得最多的框架,它排第一!

    经常有粉丝后台留言有关Spring全家桶学习的问题,大家遇到的困惑无非是这些方面:学习路线图:而周瑜大都督的这门课程,不仅会深入到源码给大伙剖析Spring全家桶源码,分析Spring全家桶中技术的本质,还会将很抽象的概念讲得足够具体,足够容易理解,还会教你怎么让框架更好地为业务落地去服务,真正做到由点到线...

    Java知识 2021-09-29 54 0

  • 前言

    工作了四年,看过很多思考不够深入的代码,因此写一下总结吧,50个让你代码更好的建议。其中的一些点,我以前的文章也写过啦,这次主要汇总一下。希望大家日常写代码多点思考,多点总结,加油!同时哪里有不对的,也望指出,感谢哈~

    推荐下自己做的 Spring Boot 的实战项目:

    https://github.com/YunaiV/ruoyi-vue-pro

    1. 仅是判断是否存在时,select count 比 select 具体的列,更好。

    我们经常遇到类似的业务场景,如,判断某个用户userId是否是会员。

    (反例):  一些小伙伴会这样实现,先查从用户信息表查出用户记录,然后再去判断是否是会员:

    <select id="selectUserByUserId" resultMap="BaseResultMap" alt="工作四年,分享50个让你代码更好的小建议">
    	

    工作四年,分享50个让你代码更好的小建议

    前言工作了四年,看过很多思考不够深入的代码,因此写一下总结吧,50个让你代码更好的建议。其中的一些点,我以前的文章也写过啦,这次主要汇总一下。希望大家日常写代码多点思考,多点总结,加油!同时哪里有不对的,也望指出,感谢哈~推荐下自己做的 Spring Boot 的实战项目:https://github.co...

    Java知识 2021-09-29 40 0

  • 今天我们来聊一聊在基于SpringBoot前后端分离开发模式下,如何友好的 返回统一的标准格式以及如何优雅的处理全局异常。

    首先我们来看看为什么要返回统一的标准格式?

    为什么要对SpringBoot返回统一的标准格式

    在默认情况下,SpringBoot的返回格式常见的有三种:

    第一种:返回 String

    @GetMapping("/hello")
    public String getStr(){
      return "hello,javadaily";
    }

    此时调用接口获取到的返回值是这样:

    hello,javadaily

    第二种:返回自定义对象

    @GetMapping("/aniaml")
    public Aniaml getAniaml(){
      Aniaml aniaml = new Aniaml(1,"pig" alt="SpringBoot 如何统一后端返回格式?老鸟们都是这样玩的!">

    SpringBoot 如何统一后端返回格式?老鸟们都是这样玩的!

    今天我们来聊一聊在基于SpringBoot前后端分离开发模式下,如何友好的 返回统一的标准格式以及如何优雅的处理全局异常。首先我们来看看为什么要返回统一的标准格式?为什么要对SpringBoot返回统一的标准格式在默认情况下,SpringBoot的返回格式常见的有三种:第一种:返回 String@GetMa...

    Java知识 2021-09-29 41 0
  • 推荐下自己做的 Spring Boot 的实战项目:

    https://github.com/YunaiV/ruoyi-vue-pro

    LayUI 的特性

    在vue、 react、 angular、更有类似 ant design,elementUI 等巨头的围攻下

    LayUI 坚韧的活着,自然有其独特的特征。

    那就是 LayUI:简约实用。

    正像原作者所说,LayUI 原本就是为后端开发人员而做的。

    比如,一个静态表格,基本上在不修改的现有代码的情况下,只要增加 class="layui-table",就能立刻展现出优美的界面。

    这简直就是后台开发人员的福音。

    更具体的说,当你使用 ASP.NET Gridview 控件时,基本上只要增加 class="layui-table" 就能达到 UI 上专业的美观效果。

    这和 Ant Design 这种 UI 设计理念完成不同,在 Ant Design 里,你需要 import/export 各种 JS 包。

    突发!LayUI宣布下线

    突发!LayUI宣布下线

    来源:cnblogs.com/mqingqing123/p/15329717.html 缘起 偶然的事件,看到 LayUi(读音 "类UI")官方说,LayUI官网 https://www.layui.com/ 将关闭,多少有些伤感。或许,有人会说,通知里也说了,“新版下载、文档和示例等仍会在Github...

    Java知识 2021-09-28 51 0
  • 别再乱写 SQL 了,这样才是 MySQL 使用的正确方式!

    别再乱写 SQL 了,这样才是 MySQL 使用的正确方式!

    完课就领:完课就领:Day2:人脸识别项目包、近百家大厂的近千份面经集合、直播抽奖完课就领:Day2:直播抽奖...

    Java知识 2021-09-28 46 0

  • 随着互联网信息技术的飞速发展,数据量不断增大,业务逻辑也日趋复杂,对系统的高并发访问、海量数据处理的场景也越来越多。如何用较低成本实现系统的高可用、易伸缩、可扩展等目标就显得越发重要。

    为了解决这一系列问题,系统架构也在不断演进。传统的集中式系统已经逐渐无法满足要求,分布式系统被使用在更多的场景中。

    分布式系统由独立的服务器通过网络松散耦合组成。在这个系统中每个服务器都是一台独立的主机,服务器之间通过内部网络连接。分布式系统有以下几个特点:

    然而,在分布式系统中,其环境的复杂度、网络的不确定性会造成诸如时钟不一致、“拜占庭将军问题”(Byzantine failure)等。存在于集中式系统中的机器宕机、消息丢失等问题也会在分布式环境中变得更加复杂。

    基于分布式系统的这些特征,有两种问题逐渐成为了分布式环境中需要重点关注和解决的典型问题:

    今天我们就针对这两个问题来进行分析。

    互斥性问题

    先看两个常见的例子:

    例1 :某服务记录关键数据X,当前值为100。A请求需要将X增加200;同时,B请求需要将X减100。

    在理想的情况下,A先读取到X=100,然后X增加200,最后写入X=300。B请求接着从读取X=300,减少100,最后写入X=200。

    然而在真实情况下,如果不做任何处理,则可能会出现:A和B同时读取到X=100;A写入之前B读取到X;B比A先写入等情况。

    例2 :某服务提供一组任务,A请求随机从任务组中获取一个任务;B请求随机从任务组中获取一个任务。

    在理想的情况下,A从任务组中挑选一个任务,任务组删除该任务,B从剩下的的任务中再挑一个,任务组删除该任务。

    同样的,在真实情况下,如果不做任何处理,可能会出现A和B挑中了同一个任务的情况。

    以上的两个例子,都存在操作互斥性的问题。互斥性问题用通俗的话来讲,就是对共享资源的抢占问题。如果不同的请求对同一个或者同一组资源读取并修改时,无法保证按序执行,无法保证一个操作的原子性,那么就很有可能会出现预期外的情况。因此操作的互斥性问题,也可以理解为一个需要保证时序性、原子性的问题。

    在传统的基于数据库的架构中,对于数据的抢占问题往往是通过数据库事务(ACID)来保证的。在分布式环境中,出于对性能以及一致性敏感度的要求,使得分布式锁成为了一种比较常见而高效的解决方案。

    事实上,操作互斥性问题也并非分布式环境所独有,在传统的多线程、多进程情况下已经有了很好的解决方案。因此在研究分布式锁之前,我们先来分析下这两种情况的解决方案,以期能够对分布式锁的解决方案提供一些实现思路。

    推荐下自己做的 Spring Boot 的实战项目:

    https://github.com/YunaiV/ruoyi-vue-pro

    多线程环境解决方案及原理

    解决方案

    《Thinking in Java》书中写到:

    基本上所有的并发模式在解决线程冲突问题的时候,都是采用序列化访问共享资源的方案。

    在多线程环境中,线程之间因为公用一些存储空间,冲突问题时有发生。解决冲突问题最普遍的方式就是用互斥锁把该资源或对该资源的操作保护起来。

    Java JDK中提供了两种互斥锁Lock和synchronized。不同的线程之间对同一资源进行抢占,该资源通常表现为某个类的普通成员变量。因此,利用ReentrantLock或者synchronized将共享的变量及其操作锁住,即可基本解决资源抢占的问题。

    下面来简单聊一聊两者的实现原理。

    原理

    ReentrantLock

    ReentrantLock主要利用CAS+CLH队列来实现。它支持公平锁和非公平锁,两者的实现类似。

    分布式系统互斥性与幂等性问题的分析与解决

    分布式系统互斥性与幂等性问题的分析与解决

    来源:blog.csdn.net/zdy0_2004/article/details/52760404随着互联网信息技术的飞速发展,数据量不断增大,业务逻辑也日趋复杂,对系统的高并发访问、海量数据处理的场景也越来越多。如何用较低成本实现系统的高可用、易伸缩、可扩展等目标就显得越发重要。为了解决这一系列问题,...

    Java知识 2021-09-28 56 0
识海教程

识海教程

每天学习技术知识,每天进步多一点点。

  • 228960阅读数
  • 4评论数

关于识海

网站将会逐步收集越来越多的技术文章,当然将来也会开启投稿功能,各位朋友们有好的文章可以分享给大家