0%

冒泡算法

每次遍历进行i与i+1的值进行比较,把大的值都往数组后面丢,保证了数组末尾的元素为最大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static int[] test1(int[] arr) {
int temp;
for(int i=arr.length-1;i>0;i--) {
for(int j=0;j<i;j++) {
// 前者如果小于后者,就调换
if (arr[j] > arr[j+1]) {
temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
System.out.println(change(arr));
return arr;
}
阅读全文 »

运行一个 Java 应用程序,必须要先安装 JDK 或者 JRE 包。因为 Java 应用在编译后会变成字节码,通过字节码运行在 JVM 中,而 JVM 是 JRE 的核心组成部分。JVM 不仅承担了 Java 字节码的分析和执行,同时也内置了自动内存分配管理机制。这个机制可以大大降低手动分配回收机制可能带来的内存泄露和内存溢出风险,使 Java 开发人员不需要关注每个对象的内存分配以及回收,从而更专注于业务本身。
在 Java 中,JVM 内存模型主要分为堆、方法区、程序计数器、虚拟机栈和本地方法栈。其中,堆和方法区被所有线程共享,虚拟机栈、本地方法栈、程序计数器是线程私有的。

阅读全文 »

一些Java基础流程图/架构图

TCP三次握手,四次挥手

Alt

三次握手

  • 第一次握手(SYN=1, seq=x),发送完毕后,客户端进入 SYN_SEND 状态
  • 第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1), 发送完毕后,服务器端进入 SYN_RCVD 状态。
  • 第三次握手(ACK=1,ACKnum=y+1),发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手,即可以开始数据传输。

四次挥手

  • 第一次挥手(FIN=1,seq=a),发送完毕后,客户端进入 FIN_WAIT_1 状态
  • 第二次挥手(ACK=1,ACKnum=a+1),发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态
  • 第三次挥手(FIN=1,seq=b),发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。
  • 第四次挥手(ACK=1,ACKnum=b+1),客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。

JVM内存结构

Alt

程序计数器(PC 寄存器)

程序计数器是一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器。在虚拟机的模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、异常处理、线程恢复等基础功能都需要依赖计数器完成。

Java虚拟机栈

  • 与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期与线程相同
  • 每个方法被执行的时候都会创建一个”栈帧”,用于存储局部变量表(包括参数)、操作数栈、动态链接、方法出口等信息。每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
  • 局部变量表存放各种基本数据类型boolean、byte、char、short等

本地方法栈

与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。

Java堆

  • GC堆是java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,在JVM启动时创建。
    其大小通过-Xms(最小值)和-Xmx(最大值)参数设置,-Xms为JVM启动时申请的最小内存,-Xmx为JVM可申请的最大内存。
    由于现在收集器都是采用分代收集算法,堆被划分为新生代和老年代。新生代由S0和S1构成,可通过-Xmn参数来指定新生代的大小。
    所有对象实例以及数组都在堆上分配。
  • Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中。

方法区

  • 也称”永久代” ,它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。
    运行时常量池:是方法区的一部分,其中的主要内容来自于JVM对Class的加载。
  • Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中。

Java内存模型

Alt

  • Java的多线程之间是通过共享内存进行通信的,在通信过程中会存在一系列如可见性、原子性、顺序性等问题,而JMM就是围绕着多线程通信以及与其相关的一系列特性而建立的模型。JMM定义了一些语法集,这些语法集映射到Java语言中就是volatile、synchronized等关键字。有兴趣可以看看我的另外一篇笔记:www.jianshu.com/p/3c1691aed…
  • Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。

spring的生命周期

Alt

  • 首先容器启动后,对bean进行初始化
  • 按照bean的定义,注入属性
  • 检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean,如BeanNameAware等
  • 以上步骤,bean对象已正确构造,通过实现BeanPostProcessor接口,可以再进行一些自定义方法处理。如:postProcessBeforeInitialzation。
  • BeanPostProcessor的前置处理完成后,可以实现postConstruct,afterPropertiesSet,init-method等方法, 增加我们自定义的逻辑,
  • 通过实现BeanPostProcessor接口,进行postProcessAfterInitialzation后置处理
  • 接着Bean准备好被使用啦。
  • 容器关闭后,如果Bean实现了DisposableBean接口,则会回调该接口的destroy()方法
  • 通过给destroy-method指定函数,就可以在bean销毁前执行指定的逻

springMVC执行流程图

Alt

  • User向服务器发送request,前端控制Servelt DispatcherServlet捕获;
  • DispatcherServlet对请求URL进行解析,调用HandlerMapping获得该Handler配置的所有相关的对象,最后以HandlerExecutionChain对象的形式返回.
  • DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter.
  • 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)
  • Handler执行完成后,返回一个ModelAndView对象到DispatcherServlet
  • 根据返回的ModelAndView,选择一个适合的ViewResolver
  • ViewResolver 结合Model和View,来渲染视图
  • 将渲染结果返回给客户端。

spring cloud组件架构

Spring Cloud是一个基于Spring Boot实现的云原生应用开发工具,它为基于JVM的云原生应用开发中涉及的配置管理、服务发现、熔断器、智能路由、微代理、控制总线、分布式会话和集群状态管理等操作提供了一种简单的开发方式。

Alt

  • Eureka 负责服务的注册与发现。
  • Hystrix 负责监控服务之间的调用情况,起到熔断,降级作用。
  • Spring Cloud Config 提供了统一的配置中心服务。
  • 所有对外的请求和服务,我们都通过Zuul来进行转发,起到 API 网关的作用
  • 最后我们使用 Sleuth+Zipkin 将所有的请求数据记录下来,方便我们进行后续分析。
  • Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。它是一个基于HTTP和TCP的客户端负载均衡器。
  • Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。

dubbo 调用

Dubbo是一个分布式服务框架,致力于提供高性能和透明化的远程服务调用方案,这容易和负载均衡弄混,负载均衡是对外提供一个公共地址,请求过来时通过轮询、随机等,路由到不同server。

Alt

  • Provider: 暴露服务的服务提供方。
  • Consumer: 调用远程服务的服务消费方。
  • Registry: 服务注册与发现的注册中心。
  • Monitor: 统计服务的调用次调和调用时间的监控中心。
  • Container: 服务运行容器。

dockerfile的属性

FROM

    基础镜像,该配置是基于某些镜像的基础上实现的,比如jdk镜像

MAINTAINER

    维护者信息

ENV

    设置环境变量

ADD

    文件放在当前目录下,拷过去会自动解压

RUN

    执行命令。RUN命令是创建Docker镜像(image)的步骤,RUN命令对Docker容器造成的改变是会被反映到创建的Docker镜像上的。一个Dockerfile中可以有许多个RUN命令。

WORKDIR

    指定容器的一个目录, 容器启动时执行的命令会在该目录下执行

EXPOSE

    映射端口

CMD

    镜像最终运行的命令。CMD命令是当Docker镜像被启动后Docker容器将会默认执行的命令。一个Dockerfile中只能有一个CMD命令。通过执行docker run xxx启动镜像可以重载CMD命令。

阅读全文 »

下载与安装

    拉取4.4.8版本的镜像,看仓库的描述是一百多兆的,没想到要400多。
docker pull mongo:4.4.8

运行容器

    mongodb默认是没有用户验证的,所以加个–auth开启,并把数据持久化到/data/mongo-data中
docker run --name mongo -v /data/mongo-data:/data/db -p 27017:27017 -d mongo:4.4.8 --auth
    进入admin数据库
docker exec -it mongo mongo admin

阅读全文 »

在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。

成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/**
* 线程池的控制状态,是AtomicInteger类型的,里面包含两部分,workcount---线程的数量,
* runState---线程池的运行状态。这里限制了最大线程数是2^29-1,大约500百万个线程,
* 这也是个问题,所以ctl也可以变成AtomicLong类型的
*/
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
/**
* 线程数量所占位数
*/
private static final int COUNT_BITS = Integer.SIZE - 3;
/**
* 理论上的最大活跃线程数
*/
private static final int CAPACITY = (1 << COUNT_BITS) - 1;

/**
* RUNNING - 接受新任务并且继续处理阻塞队列中的任务
* SHUTDOWN - 不接受新任务但是会继续处理阻塞队列中的任务
* STOP - 不接受新任务,不在执行阻塞队列中的任务,中断正在执行的任务
* TIDYING - 所有任务都已经完成,线程数都被回收,线程会转到TIDYING状态会继续执行钩子方法
* TERMINATED - 钩子方法执行完毕
*/
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;


/**
* 存放任务的队列,只有当线程数>核心线程数,才会把其他的任务放入queue,
* 一般常用的是queue就是ArrayBlockingQueue,LinkedBlockingQueue,
* SynchronousQueue, ConcurrentLinkedQueue。
*
   * 1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
*
   * 2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
   * 3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
   * 4) ConcurrentLinkedQueue: 无界线程安全队列
*/
private final BlockingQueue<Runnable> workQueue;

/**
* 包含池中所有工作线程的集合。仅当保持主锁.
*/
private final HashSet<Worker> workers = new HashSet<Worker>();

/**
* 支持等待终止的等待条件
*/
private final Condition termination = mainLock.newCondition();

/**
* 跟踪获得的最大池大小。仅在主锁下访问。
*/
private int largestPoolSize;

/**
* 已完成任务的计数器。仅在工作线程终止时更新。仅在主锁下访问
*/
private long completedTaskCount;

/**
* 创建线程的工厂类
*/
private volatile ThreadFactory threadFactory;

/**
* 在执行中饱和或关闭时调用的处理程序。拒绝策略;当任务太多来不及处理时,如何拒绝任务
*/
private volatile RejectedExecutionHandler handler;

/**
* 当线程池中创建的线程超过了核心线程数的时候,这些多余的空闲线程在结束之前等待新的 * 创建线程的工厂类任务最大的存活时间。
*/
private volatile long keepAliveTime;

/**
* 允许核心线程被回收
*/
private volatile boolean allowCoreThreadTimeOut;

/**
* 线程池中的核心线程数,空闲的线程也不会回收,除非把allowCoreThreadTimeOut设置为true, * 这时核心线程才会被回收
*/
private volatile int corePoolSize;

/**
* 线程池中可以创建的最大线程数,限定为2^29-1,大约500百万个线程。
* 需要注意的是,当使用无界的阻塞队列的时候,maximumPoolSize就起不到作用了。
*/
private volatile int maximumPoolSize;

/**
* 默认被拒绝的执行处理程序
*/
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();

/**
* 终止的权限
*/
private static final RuntimePermission shutdownPerm =
new RuntimePermission("modifyThread");

/**
*
*/
private final AccessControlContext acc;
阅读全文 »

拉取redis镜像(版本5.0.5)

docker pull redis:5.0.5

创建并运行三个redis容器

  • redis-node1 6379
  • redis-node1 6380
  • redis-node1 6381
阅读全文 »

成员变量

1
2
3
4
5
6
7
8
9
/**
* 底层由数组存数据
*/
private final char value[];

/**
* hash值
*/
private int hash;

方法

阅读全文 »

特征

  • 节点都有颜色
  • 在插入和删除的过程中,要遵循保持这些颜色的不同排列规则

规则

  • 每个节点不是红色就是黑色的
  • 根节点总是黑色的
  • 如果节点是红色的,则它的子节点必须是黑色的(反之不一定),(也就是从每个叶子到根的所有路径上不能有两个连续的红色节点)
  • 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

    注意:新插入的节点颜色总是红色的,这是因为插入一个红色节点比插入一个黑色节点违背红-黑规则的可能性更小,原因是插入黑色节点总会改变黑色高度(违背规则4),但是插入红色节点只有一半的机会会违背规则3(因为父节点是黑色的没事,父节点是红色的就违背规则3)。

修正

插入新节点可能会破坏规则,有2种修正方法,变色跟旋转,旋转分左旋转跟右旋转

左旋转

  • 子右节点Y上升为父节点
  • 父节点X降级为子左节点
  • 把旧子右节点Y的子左节点搞过来当子新左节点子X的子右节点
1
2
3
4
5
  X                                             Y
/ \ 以X为轴旋转 / \
a Y ------------->> X c
/ \ / \
b c a b

右旋转

  • 子左节点Y上升为父节点
  • 父节点X降级为子右节点
  • 把旧子左节点Y的子右节点搞过来当新子右节点X的子左节点
1
2
3
4
5
    X                                             Y
/ \ 以X为轴旋转 / \
Y c ------------->> a X
/ \ / \
a b b c

多看几次就懂了,明白旋转规则即可

成员变量

1
2
3
4
5
6
7
8
9
/**
* 从后面的构造方法可以看出,这里new一个value为null的Optional
*/
private static final Optional<?> EMPTY = new Optional<>();

/**
* 值
*/
private final T value;

构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* value = null
*/
private Optional() {
this.value = null;
}

/**
* 自定义value值,value为null会抛异常
*/
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}

public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
阅读全文 »