手写Spring系列:IOC
IOC 我们学Java的基本上都会使用Spring进行开发,而Spring中最为核心的又是IOC和AOP,接下来的内容是在学习手写Spring渐进式源码实践这本书后的学习总结,看是否我们能开发出一个mini-Spring。因为后期代码会很多,而且基本上都是在前一版的基础上进行扩展。这里我只声明每一章的目标扩展点是啥,具体从Github上获取源码:https://github.com/fuzhengwei/book-small-spring
第一章:实现一个简单的Spring Bean容器 先不深究Spring源码,我就看自己平时使用Spring时的体会,使用Spring时,通过XML配置文件或者通过注解,声明哪些类是需要注入到容器中的,到自己使用时,可以从容器中获取该类对象。那这不就是我们基础中学的Collection或者Map就能实现的操作嘛,因为我需要频繁的从容器中获取指定类对象,所以查询返回的效率需要非常高,那就我们就用Map来实现,先不要想那么多。
public class BeanFactory {
// 用Map来存储Bean
priv ...
从单体到分布式
初级阶段(本地调用) 在我们学习java初期,我们都会通过创建一个类对象,用该对象调用自身方法,得到需要的内容。整个过程都在本地当前JVM中进行的。这样的程序是最简单,方便,快速的。后面我们又学习了面向接口编程,用接口去规范子类的行为,也就是说调用者不需要知道具体方法的实现,只调用接口即可。
再后来我们又学习了分布式调用这些高端名词,什么Dubbo,什么RPC框架,什么Zookeeper注册中心,说实话我学完还是挺懵逼的,会有种漂浮的感觉,知道它大概是个什么东西后,又说这玩意儿落伍了,让我去看看SpringCloud,Nacos之类的玩意儿,说学这个没问题。现在回想起来有种知识断层的感觉,这种感觉从普通java程序进阶为Spring框架时有,从SpringBoot到SpringCloud时也有。废话不多说了 ,我们开始理理思路吧。
import lombok.AllArgsConstructor;
import lombok.Data;
public interface Factory {
public User askEntity();
...
从 JDBC 到 ORM(例:Mybatis)
从 JDBC 到 ORM(例:Mybatis)的演化过程 下面我将介绍Java操作Mysql数据的方式的演化过程,从最基本的JDBC到ORM框架的实现,每一次演化都是为了解决现有存在的问题。
JDBC这里需要加入Mysql驱动包或者依赖.
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
@Test
public void test1() throws SQLException {
// 注册驱动
Driver driver = new com.mysql.jdbc.Driver();
// 设置配置
Properties properties = new Properties();
properties.setProperty("user","root");
...
手写线程池
手写线程池
我们从初级阶段自己创建线程去异步执行任务,到后期使用线程池不断执行任务,原理书上都有,但都是字面意思,无法深入理解,所以打算自己进行实现。
阶段一:
new Thread(()-> System.out.println("异步执行")).start();
阶段二:
// 抽离出一个统一接口,具体实现由子类完成
interface Executor {
public void execute(Runnable r);
}
// 定义各自的子类去实现execute方法
@Slf4j
class myExecutor implements Executor {
@Override
public void execute(Runnable r) {
// 底层还是执行的 new Thread(r),虽然现在看起来是有点儿麻烦,但更方便我们进行扩展了
new Thread(r).start();
}
}
阶段三:上面我们执行一次方法就创 ...
第二章:链表问题
第二章:链表问题打印两个有序链表的公共部分/**
* 给定两个有序链表的头指针head1和head2,打印两个链表的公共部分
* @param head1
* @param head2
*/
public static void printCommonPart(Node head1,Node head2) {
// 有序 则说明,我们可以用双指针思想,找到相同的点
while (head1 != null && head2 != null) {
if (head1.value > head2.value) {
head1 = head1.next;
} else if (head1.value < head2.value) {
head2 = head2.next;
} else {
// 如果相等,说明到达了公共节点
Syste ...
搭建博客,利用Webhook自动更新
Hexo d 推送至服务器 前段时间利用Hexo搭建博客,并配合GitHub page进行页面显示,一切都很美妙,但有一个问题就是访问速度太慢,后面我希望将博客迁移到腾讯云中,之后就从服务器访问了。现在关于Hexo搭建基本博客的帖子已经很详细了,所以我会放一些我参考的链接,主要介绍如何从本地将文件同步到自己的服务器中。
前置知识Hexo博客搭建三水同学的笔记:https://sanshui.vip/2022/08/10/indexday1/ (这里操作完,基本上就是博客雏形和Github page的显示)
Butterfly主题有了基本的博客雏形,但样式比较单一,这时候就可以利用现有的Hexo主题,这里只列举了Butterfly,还有些其他主题,可以自行查找。
推荐教程:https://www.fomal.cc/posts/4aa2d85f.html (这里操作完,样子就会好看许多,剩下的就是页面的修改,按照自己的意愿)
页面音乐播放我当时觉得博客加点儿音乐很有感觉,就捣鼓了些。
推荐教程:https://blog.csdn.net/qq_41467882/article/ ...
第一章:栈与队列
第一章:栈与队列设计一个有getMin功能的栈
要求:pop,push,getMin的时间复杂度为:O(1)
public class stackTemplate {
// 记录插入值
private Stack<Integer> stack = new Stack<>();
// 记录插入值的最小值
private Stack<Integer> minStack = new Stack<>();
public void push(int value) {
stack.push(value);
// 如果最小值栈的栈顶元素 大于 当前元素,则说明有新的最小值了,
if (minStack.isEmpty() || minStack.peek() >= value) {
minStack.push(value);
}
}
public int pop() { ...
Redis 底层结构
BigKey是什么 ?BigKey通常以Key的大小和Key中成员的数量来综合判定,例如:
Key本身的数据量过大:一个String类型的Key,它的值为5 MB。
Key中的成员数过多:一个ZSET类型的Key,它的成员数量为10,000个。
Key中成员的数据量过大:一个Hash类型的Key,它的成员数量虽然只有1,000个但这些成员的Value(值)总大小为100 MB。
推荐值:
单个key的value小于10KB
对于集合类型的key,建议元素数量小于1000
有什么危害 ?
网络阻塞
对BigKey执行读请求时,少量的QPS就可能导致带宽使用率被占满,导致Redis实例,乃至所在物理机变慢
数据倾斜
BigKey所在的Redis实例内存使用率远超其他实例,无法使数据分片的内存资源达到均衡
Redis阻塞
对元素较多的hash、list、zset等做运算会耗时较旧,使主线程被阻塞
CPU压力
对BigKey的数据序列化和反序列化会导致CPU的使用率飙升,影响Redis实例和本机其它应用
怎么识别 ?
redis-cli –bigkeys
利用re ...
内存划分与溢出
概述 对于java与C&C++开发的一大区别就在内存管理方面。Java是通过虚拟机管理内存,但如果不熟悉虚拟机怎么使用管理内存,出现内存泄露和内存溢出问题,修正就会很艰难。
运行时数据区域 Java虚拟机在执行Java程序的过程中会把所管理的内存划分为一个个小部分,有的部分随着进程的启动而一直存在,有的部分随着用户线程的启动和结束而创建和销毁。其管理的内存分为以下几个运行时数据区域。
程序计数器 是一块儿较小的内存空间,他可以看做是当前线程所执行的字节码的行号指示器,通俗的来讲就是通过改变计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支,循环,跳转,异常处理,线程恢复等基础功能都是依赖计数器完成的。
在java多线程中,是利用线程轮流切换,分配处理器执行时间的方式来实现的,也就是说我执行了一会儿A线程,时间到了,我要去执行B线程了,那B线程执行时间到,我要再切换会A线程,那我怎么知道刚才任务完成到哪了?所以每个线程都私有一个程序计数器来保证线程切换后能回到正确的位置。
...
垃圾回收与内存分配
垃圾收集器与内存分配策略概述我们先提出三个问题:
哪部分内存需要回收?
什么时候进行回收?
如何进行回收?
生存还是死亡?where 我们都知道在Java中,栈,本地方法区,程序计数器都是线程私有的,随着线程的创建和结束,内存也会自动的分配和销毁,执行的方法也随着栈帧的插入和弹出而创建和销毁。所以这部分区域我们不必担心。
而方法区(元空间)和堆空间,是线程共享的,这部分区域的内存就是我们需要去进行垃圾回收的区域。
when 什么时候进行垃圾回收,就需要我们判断这个对象是否仍被引用,如果没有一个指针指向它,那我们就可以放心的进行垃圾回收,如果仍被引用,那就不行(你杀根本干嘛,这人我正用着呢),关于对象引用判断分为两种:
引用计数法 就是说一个人引用,就对一个引用计数器加一,如果这个人不引用了,我们就减一,当引用计数器等于0的时候,我们就可以判定,可以被垃圾清理了。
该方法易理解,且实现简单。但一个缺点就是遇到循环依赖,不好处理。在主流的java虚拟机中都没有使用该方法进行判断。下面用代码进行说明。
class referenceCountingG ...

