自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(174)
  • 资源 (2)
  • 收藏
  • 关注

原创 Spring 依赖注入源码

Spring早期依赖注入的两种方式:BY_NAME 和 BY_TYPE寻找所有的注入点,为注入点赋值,入口处理@Value注解,根据type,然后遍历所有的BeanDefinition,找到匹配的beanName集合。判断1,处理@Bean注解的autowireCandidate=false,判断BeanDefinition中的autowireCandidate属性值判断2,处理泛型的情况判断3,处理@Qualifier注解,

2023-04-30 20:22:05 371 1

原创 Spring Bean生命周期源码详解

之后child需要根据BeanDefinition来生成Bean对象之前,需要进行BeanDefinition的合并,得到完整的child的BeanDefinition,也就是RootBeanDefinition。我们知道BeanPostProcessor接口的作用是在bean初始化前和初始化后执行一些方法,Spring提供了该接口的子接口来进行实例化前后执行的一些方法。可以在这个步骤中,对Bean最终进行处理,Spring中的AOP就是基于初始化后实现的,初始化后返回的对象才是最终的Bean对象。

2023-04-23 18:59:07 611

原创 Spring Bean生命周期源码之包扫描、创建BeanDefinition、合并BeanDefinition源码

之后child需要根据BeanDefinition来生成Bean对象之前,需要进行BeanDefinition的合并,得到完整的child的BeanDefinition,也就是RootBeanDefinition。先看包扫描的逻辑,ClassPathBeanDefinitionScanner类主要做的事情就是包扫描完后再将得到的BeanDefinition注册进Spring容器中。如果child它自己定义了scope属性那么就用自己的,如果没有定义那么就用的parent的。

2023-04-20 20:04:14 288

原创 Spring底层架构核心概念

Spring启动的时候需要去扫描,如果指定的包路径比较宽泛,那么扫描的类是非常多的,那如果在Spring启动时就把这些类全部加载进JVM了,这样不太好,因为JVM的类加载是在类要使用时才会加载,而现在都没有使用就都加载了,所以使用了ASM技术。Bean的后置处理器,我们可以定义多个BeanPostProcessor,在创建Bean时,每个Bean都会执行这其中的前置和后置方法,我们也可以加if来判断给特定某个Bean执行某些特定的方法。Spring中需要去解析类的元数据信息,比如类名、方法名、类上注解等。

2023-04-19 19:11:49 1325

原创 Spring底层核心原理

cjlib和jdk两种动态代理的实现都是有一个target属性来存储普通对象,代理对象中重写要执行的方法,首先执行增强逻辑,然后通过target属性去执行目标方法。下面这几行代码是一个Spring的入门代码,第一行是通过java配置类 注解的方式创建一个Spring容器,第二行是通过XML配置文件的方式创建一个Spring容器。代理对象中操作的是事务管理器的连接对象,而业务方法却是使用的JdbcTemplate,所以就导致了Spring事务失效。如果显示写了多个构造方法没有空参的构造方法时,运行时会报错。

2023-04-18 15:50:58 554

原创 Synchronized原理总结

MonitorSynchronized的实现是基于Monitor的,而Monitor是基于管程的MESA模型,ObjectMonitor数据结构,三个队列,等待唤醒机制基于Object对象中的方法对象的内存布局锁的状态信息是标记在对象头的Mark Word中的。一个对象由对象头、实例数据、对齐填充三部分组成。对象头由Mark Word、Klass point、数组长度组成。无锁状态下Mark Word存储的是对象的hash值、gc分代年龄、偏向锁标识位0、锁标识01。

2023-04-14 17:55:41 348

原创 synchronized原理、偏向锁、轻量级锁、重量级锁、锁升级

偏向锁是一种加锁操作的优化机制。经过研究发现大部分情况下是不存在锁竞争,一直都是一个线程去获取锁,因此为了消除在无竞争情况下重入锁(CAS操作)的开销,而引入了偏向锁。对于没有竞争的场合,偏向锁有很好的优化效果。JVM1.6默认开启偏向锁。新创建一个对象,此时给对象的Mark Word中的ThreadID为0,说明该对象处于可偏向但未偏向任何线程,也叫作匿名偏向状态。

2023-04-13 19:27:43 496

原创 CPU缓存架构+Disruptor内存队列

cpu与内存的交互数据之间,有一个高速缓存层。有些处理器有3层缓冲,有些处理器有4层缓存当下CPU都是有多核,每核cpu都有一份自己的高速缓存内存中的数据读取到高速缓存中每次读取的数据至少是一次缓存行64字节。

2023-04-12 15:30:59 331

原创 并发原子性、可见性、有序性与JMM内存模型

java的线程之间通信是由java内存模型(Java Memory Model,简称JMM)控制。它来决定一个线程对共享变量的写入何时对另一个线程可见。JMM模型是屏蔽底层各种处理器的差异的,因为java是跨平台的,不同的处理器对内存屏障的支持也是不一样的。线程之间的数据交互,JMM内存模型中有一块主内存空间,各个线程还有自己的工作内存。在主内存读取变量的时候是拷贝一份变量的副本到自己的工作内存,之后的读取都是先从之间的工作内存中找。修改操作会先更新工作内存区中的值,然后在写回主内存中。

2023-04-09 20:18:22 298

原创 ForkJoinPool线程池工作原理

归并排序(Merge Sort)是一种基于分治思想的排序算法。归并排序的基本思想是将一个大数组分成两个相等大小的子数组,对每个子数组分别进行排序,然后将两个子数组合并成一个有序的大数组。因为常常使用递归实现(由先拆分后合并的性质决定的),所以我们称其为归并排序。归并排序的时间复杂度为O(nlogn),空间复杂度为O(n),其中n为数组的长度。分治思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。

2023-04-08 19:04:43 293

原创 线程池ThreadPoolExecutor源码

不过还有一个种情况,就是目前所有工作线程都在执行任务,但是阻塞队列中还有剩余任务,那逻辑应该就是这些工作线程执行完当前任务后要继续执行队列中的剩余任务,但是根据我们看到的shutdown方法的逻辑,发现这些工作线程在执行完当前任务后,就会释放锁,那就可能会被中断掉,那队列中剩余的任务怎么办呢?当线程池状态为SHUTDOWN时,如果阻塞队列中的任务执行完了那么也淘汰所有线程,直接返回null就是淘汰退出线程,因为。0,就表示线程池的状态为RUNNING,线程池中目前在工作的线程有10个。

2023-04-07 20:57:23 333

原创 线程池ThreadPoolExecutor原理

刚开始线程池中是没有线程的,如果来了任务那么就直接去创建线程处理,如果核心线程处理完任务了,但是线程数量还没有达到核心线程数,此时来了任务也还是会去创建新线程处理,直到线程数量达到了核心线程数。我们首先要计算出每个任务的执行耗时,然后再看所有核心线程数去拿队列中的最后一个任务的耗时,业务能否接收,如果能接收那么队列长度的设置就可以。线程数 >= 核心线程数后,再来新任务就会直接放入阻塞队列中,并唤醒等待的线程去处理,当队列中没有任务了那么线程就阻塞。创建线程池时,线程池中是不会创建线程的。

2023-04-07 16:34:31 308

原创 阻塞队列BlockingQueue

ArrayBlockingQueue它队列使用的数据结构是双指针的环形数组,入队出队是基于生产者消费者模型实现的,它入队和出队使用的是同一把锁。基本使用//向队列中添加元素 Object object = queue . take();//从队列中取出元素其实// 生产者,把方法的入参往队列中存,队列是一个环形数组 public void put(Object x) throws InterruptedException {try {

2023-04-06 19:49:35 220

原创 并发容器(Map、List、Set)原理

是java线程安全的哈希表。JDK1.8之前采用的分段锁,JDK1.8时采用的循环CAS+synchronized来实现的。官方的解释是:分段锁的锁对象需要占用更多内存空间、提高GC效率用的比较少,了解即可ConcurrentSkipListMap 是 Java 中的一种线程安全、基于跳表实现的有序映射(Map)数据结构。它是对 TreeMap 的并发实现,支持高并发读写操作。ConcurrentSkipListMap适用于需要高并发性能、支持有序性。

2023-04-06 15:22:38 243

原创 ReentrantReadWriteLock读写锁底层实现、StampLock详解

适用于读多写少的场景,特点是读读不互斥,读写与写写互斥。ReentrantReadWriteLock它有个潜在的问题:如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁,即读的过程中不允许写,这是一种悲观的读锁。Java 8引入了新的读写锁:StampedLock读的过程中也允许获取写锁后写入!在原先读写锁的基础上新增了一种叫乐观读(Optimistic Reading)的模式。该模式并不会加锁,所以不会阻塞线程,会有更高的吞吐量和更高的性能。

2023-04-05 16:59:05 250

原创 AQS独占锁、Reentrantlock源码底层实现

AQS的全程:AbstractQueuedSynchronizer包中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获取等,而这些行为的抽象就是基于(简称AQS)实现的,AQS是一个抽象同步框架,可以用来实现一个依赖状态的同步器。JDK中提供的大多数的同步器如Lock, Latch, Barrier等,都是基于AQS框架来实现的阻塞等待队列共享/独占公平/非公平可重入允许中断同步等待队列: 主要用于维护获取锁失败时入队的线程。

2023-04-04 18:50:29 368

原创 JUC并发工具类

可中断可选择设置公平锁可以设置超时时间支持多个条件变量解决多线程竞争资源的问题,例如多个线程同时对同一个数据库进行写操作,可以使用ReentrantLock保证每次只有一个线程能够写入。实现多线程任务的顺序执行,例如在一个线程执行完某个任务后,再让另一个线程执行任务。实现多线程等待/通知机制,例如在某个线程执行完某个任务后,通知其他线程继续执行任务。ReentrantLock具体的引用场景如下:解决多线程之间资源竞争问题。

2023-04-03 18:54:17 228

原创 多线程并发安全问题

是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。死锁是必然发生在多操作者(M>=2 个)争夺多个资源(N>=2 个,且 N

2023-04-01 18:49:09 323

原创 CAS&Atomic 原子操作详解

Mysql事务中的原子性就是一个事务中执行的多条sql,要么同时成功,要么同时失败,他们不可拆分。并发中的原子操作也一样,多个线程中,站在线程A的角度看线程B的操作,线程B的操作就是一个原子的;站在线程B的角度看线程A,线程A的操作是原子的。一整个操作要么全部执行完了,要么就没有执行,中间不能拆分。那么要怎么实现原子性嘞?可以使用synchronized锁来保证一段代码的原子性,但是加锁影响性能,甚至还有死锁方面的问题需要考虑。

2023-04-01 17:16:46 238

原创 导致JVM内存泄露的ThreadLocal详解

它的实现,它类似于jdk1.7版本的hashmap,底层存储的是一个Entry对象的数组,初始容量也是16,存值时先用hash结果和数组长度取余得到数组下标位置,然后判断是否产生了hash冲突,然后使用。为了提高性能,才没有采用加锁的方式,而是将map和各个线程thread对象进行关联,这样就避免了产生线程安全问题,也避免了加锁,提高了性能。而且如果是多个线程保存一个变量的副本,一个静态的ThreadLocal也足够了,因为它是作为多个map中的key存在的。它是使用的线性探测再散列法,如下所示。

2023-04-01 13:37:44 190

原创 深入学习java线程、线程间的通信与协调、CompleteableFuture使用案例

总结计算可以由FutureConsumer或者Runnable接口中的applyaccept或者run等方法表示。计算的执行主要有以下默认执行使用默认的的异步执行提供者异步执行。这些方法名使用这种格式表示。使用Executor提供者异步执行。这些方法同样也是这种格式,但是会增加一个Executor参数。

2023-03-31 20:46:21 120

原创 java并发基本概念、线程启动终止

cpu调度中我们知道cpu是频繁切换不同的线程执行的,线程在使用cpu执行时会使用cpu的寄存器与程序计数器的,如果出现了线程切换,那么cpu就会把当前线程在寄存器与程序计数器中的上下文数据拷贝到内存中去,然后再执行另一个的线程,此时会从内存中把另一个线程的上下文在cpu寄存器中进行恢复。这种情况下,使用中断会更好,因为,方法如果被调用则立刻强制停止该线程的执行,这种方式本身就是不安全的,比如正在往磁盘中写一个文件,强制终止线程运行后那么连文件的结束符都不会写,就造成了文件损坏。

2023-03-30 21:01:50 285

原创 Redis HyperLogLog底层实现和Redis 7.0特性主从复制优化

剩余的50位的数据就决定往桶中放入什么内容,从第14位开始往前数,找到第一个为1的比特位数,比如17位是1,那么就是从第14位开始往前数第3位是1,然后把这个3转换为二进制数存入桶中,相当于就是存了一个K。将原来一整大块内存拆分为了很多小块内存(Block),使用链表的结构将多个block连接起来,每个block中还维护了一个变量refcount用来表示从库引用计数,各个从库的复制缓冲区都是读取的一块内存,只是移动指针表示各个从库读取到了哪一个位置。它是有误差的,误差在0.81%。,不是采用的算术平均数。

2023-03-28 20:36:54 769

原创 Redis Stream队列与多线程模型

Redis5.0版本新出stream数据结构,是实现消息队列的功能的。Redis Stream 的结构如上图所示,每一个Stream都有一个消息链表,将所有加入的消息都串起来,每个消息都有一个唯一的 ID 和对应的内容。每个 Stream 都有唯一的名称,它就是 Redis 的 key,在我们首次使用xadd指令追加消息时创建的。每个 Stream 都可以挂多个消费组,每个消费组会有个游标last_delivered_id在 Stream 数组之上往前移动,表示当前消费组已经消费到哪条消息了。每个消费组都有

2023-03-28 15:49:20 475

原创 Redis底层核心数据结构与Redis6.0新特性

在移动数据过程中如果客户端进行了更新操作,Redis会操作两个dictht,先去老数组ht[0]中找,如果没有找到就直接去新数组ht[1]中操作,如果老数组找到了就是在老数组中去操作,同时会把这个hash桶中的数据全都移动到新数组中去。中的level记录的是最高的层高数,因为索引遍历时是从最高的那一层开始往下找的,这个层高是通过一个随机函数生成的,越高的层数出现的概率越低。Redis中所有的key都是String类型的,底层是使用的SDS类型,没有使用c语言的字符数组去实现字符串。

2023-03-27 16:28:05 281

原创 一线大厂高并发Redis缓存架构

的问题,我们都是请求–>web服务–>redis,如果并发量很大,多个web服务的请求到发到了一个redis节点上,redis处理不过来一秒十几万或更多的请求,那么redis就可能宕机或者是用户线程一直等待redis响应,此时并发很高,web服务中线程得不到释放,又不断有新的请求进来,又导致微服务可能宕机,进而影响到整个系统宕机。问题,热点数据过期或者是冷门数据忽然变成热点数据,这时缓存中没数据,DB中有数据,一瞬间大量的请求要查询这条数据就都落到了DB上。,锁的粒度要小,比如电商中使用锁前缀+商品id。

2023-03-25 21:42:07 1389

原创 Redis分布式锁、Redisson原理

方法加锁,但是只会有一个线程加锁成功,如果线程1加锁成功了那么就会另外开启一个线程,默认每隔10s去检查锁是否还存在,如果还存在则重新设置锁过期时间为30秒。如果加锁成功是返回null,如果加锁没成功是返回的锁过期时间,所以这里接下来就是一个while(true)死循环,不断尝试获取锁。核心思想是首先等一段时间,延迟执行TimerTask类的run()方法,等待的时间是key过期时间的三分之一,默认是10s。方法没有加锁成功,那么返回的是这个锁的过期时间,那么接下来也就回到了加锁部分的第一张图中了。

2023-03-25 01:33:37 472

原创 redis cluster集群节点的扩缩容、重新分配槽位

至此,我们已经成功的把8007主节点的数据迁移到8001上去了,我们可以看一下现在的集群状态如下图,你会发现8007下面已经没有任何hash槽了,证明迁移成功!主节点的里面是有分配了hash槽的,所以我们这里必须先把8007里的hash槽放入到其他的可用主节点中去,然后再进行移除节点操作,不然会出现数据丢失问题。(ps:输入all为从所有主节点(8001,8002,8003)中分别抽取相应的槽数指定到新节点中,抽取的总槽数为600个)(ps:需要多少个槽移动到新的节点上,自己设置,比如600个hash槽)

2023-03-23 08:42:29 644

原创 Redis Cluster集群搭建、Cluster集群扩缩容、底层原理

写操作只能在一台master节点上进行单台redis的使用内存一般不超过10G,因为内存如果太大持久化时会影响性能master宕机后,重新选举,这一段时间中整个redis集群是不可用的而Redis3.0版本开始就提供了RedisCluster的集群模式整个服务数据是分片存储在多个master节点上的,每个master中存储的数据是不一样的,各个master节点下又可以加多个从节点,这样就组成了一个一个的小集群,多个小集群就组成了整个Cluster集群。

2023-03-22 23:30:10 176 1

原创 redis持久化机制、主从哨兵架构原理

混合模式结合了rdb和aof两种文件的优点,在触发aof文件重写时,此时会把当前内存中的数据生成二进制文件的格式存储在appendonly.aof文件中,之后还是以之前aof文件存储的格式存储。StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。aof文件的重写其实就是结合当前内存中的数据生成命令,然后保存在appendonly.aof文件中。持久化的文件的文件名与文件存储路径的配置在redis.conf文件中是下面的配置。

2023-03-21 22:02:06 385 1

原创 Redis五种核心数据结构的基本使用与应用场景

相比较于Set有排序的功能,一般适用于排名/热搜。相比string操作消耗内存与cpu更小。同类数据归类整合储存,方便数据管理。相比string储存更节省空间。更方便的存储一个对象,比如。可以实现栈、队列的数据结构。

2023-03-21 08:54:31 77

原创 Arthas工具、GC日志、常量池

2、Full GC(Metadata GC Threshold)指这是一次full gc,括号里是gc的原因, PSYoungGen是年轻代的GC,ParOldGen是老年代的GC,Metaspace是元空间的GC。下图是jdk1,6 的情况,两个字面量会在常量池中创建,new对象还会在堆中创建,最后会创建一个hello的字符串返回给s1,但是方法区永久代中不存在当前hello字符串,则会创再创建一个对象返回给s2。如果这个字符串对象在堆中存在,但常量池中不存在则直接返回堆中的这个对象地址。

2023-03-19 13:52:30 563

原创 JVM调优命令、调优思路

如果找到了我们就去代码中找一下哪些地方创建了这个对象进而优化代码,如果创建对象的地方非常多,不太好定位问题那么我们可以尝试使用stack命令找一下占用cpu较多的线程,根据输出提示找到具体的java代码。调优,增加年轻代内存空间后,可能频繁产生FullGC的情况就不会出现了,也有可能还会更加频繁,这种情况的出现我们还是一样慢慢排查触发FullGC的各种情况,最大的可能性是老年代空间分配担保机制的原因,因为年轻代现在增大了,老年代减少了。主要查看堆内存中的一些信息,看看哪些类的实例对象占用了多大的堆内存。

2023-03-16 09:03:53 195

原创 G1与ZGC垃圾回收器

如果一个Region100的对象,其中95个对象都是存活对象,那么复制的意义就不大,而且复制95个对象很耗时。因为它会计算这一次如果进行MinorGC过程中大概STW的耗时,默认STW最大停顿时间是200ms,如果这一次只需要50ms,那么就不会进行MinorGC,会增加年轻代的region,继续给新对象存放,不会马上做Young GC,直到下一次Eden区放满,如果这一次MinorGC过程中SWT耗时接近200ms了,那么就开始进行MinorGC。区别是CMS叫重新标记,G1叫最终标记,但其实是一样的;

2023-03-14 08:10:34 97

原创 JVM垃圾收集算法、垃圾收集器、三色标记算法

当灰色对象要删除与白色对象的引用关系时,这个删除被保存下来,在并发表姐结束后,再将这些记录过的引用关系中的灰色对象为根, 重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑色(显然在可达性分析刚刚开始的阶段, 所有的对象都是白色的, 若在分析结束的阶段, 仍然是白色的对象, 即代表不可达。在标记阶段的某一个时刻中,还未完成标记阶段,这个时候完成了A对象的标记,刚完成标记B对象引用C对象,还没进行D对象的引用标记判断。老年代使用的是Serial Old垃圾收集器,采用的是标记整理算法。

2023-03-12 20:26:00 230

原创 java对象的创建与内存分配机制

finalize()方法最终判定对象是否存活,当一个对象被标记为垃圾对象后,如果该对象重写了finalize()方法则不会立刻回收该对象,此时该对象有一次自救的机会,只要重新与引用链上的任何的一个对象建立关联即可。如果对象这时候还没逃脱,那基本上它就真的被回收了。一般情况下对象会在Eden区中创建,当存满后进行MinorGC,将存活的对象移至S0区,下一次MinorGC就回收Eden区和S0区,将存活对象移至S1,这其中分代年龄一直递增,默认达到15后就移至老年代。

2023-03-10 22:53:03 451

原创 JVM内存模型

由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存的机器来说,一般我会将这两个值都设置为256M。继续创建对象,继续存放在Eden区中,当又一次存满后就又会触发MinorGC,将Eden去和S0区的存活对象复制到S1区,然后清空Eden去和S0区。

2023-03-08 21:59:32 145

原创 Mysql全局优化参数

此参数用来设置innodb线程的并发数,默认值为0表示不被限制,若要设置则与服务器的CPU核心数相同或是CPU的核心数的2倍,如果超过配置并发数,则需要排队,这个值不宜太大,不然可能会导致线程之间锁争用严重,影响性能。连接对象的创建和销毁都是需要系统资源的,一个连接最小占用内存是256K,最大占用内存是64M。指的是mysql client连接mysql进行操作完毕后,空闲300秒后断开,默认是28800,单位秒,即8个小时。行锁锁定时间,默认50s,根据公司业务定,没有标准值。

2023-03-07 23:34:40 79

原创 Mysql8.0的特性

窗口函数与 SUM()、COUNT() 这种分组聚合函数类似,在聚合函数后面加上over()就变成窗口函数了,在括号里可以加上partition by等分组关键字指定如何分组,窗口函数即便分组也不会将多行查询结果合并为一行,而是将结果放回多行当中,即窗口函数不需要再使用 GROUP BY。如果想删除某个索引,可以先改为隐藏索引,这个时候隐藏索引是不会被sql语句使用,但还是会进行维护,这样如果此索引还在使用就能立刻改回来进行使用,避免了因为数据量大创建索引的耗时操作。for update。

2023-03-07 23:31:43 97

原创 Sql执行流程与Redo log、 Undo log、 Bin log日志文件

开始执行的时候,要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误,如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。默认2个,最大100个。思路是首先通过上面的命令查看到binlog文件的内容,找到要恢复的一个起始和结束位置,在执行数据恢复命令,或者是直接执行整个binlog文件。当进行更新操作时就会生成redo log文件,它是用来保证事务的持久性的,事务提交前就会把内存中的redo log写入到磁盘中。

2023-03-07 23:30:12 169

upload jar包.zip

upload_bill.jar

2021-04-15

css.chm html5.chm javaee.chm javajcript.chm jdk api jQueryAPI

各种常用的api 都是中文 css+html+javaEE+javaScript+Javase+Jquery

2020-12-20

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除