快排优化
写lc的时候写到了这个 912. 排序数组 - 力扣(LeetCode) , 我点进去一看
给你一个整数数组 nums,请你将该数组升序排列。
很快啊 , 我啪的一下就是一个快排
123456789101112131415161718192021222324class Solution { public int[] sortArray(int[] nums) { qs(nums,0,nums.length-1); return nums; } void qs(int[]nums,int i,int j){ if(i>j) return ; int l=i,r=j; int pivot=nums[i]; while(l<r){ while(l<r && nums[r]>=pivot) r--; while(l<r && nums[l]<=pivot) l++; if(l<r){ int tmp = nums[l]; nums[l]=nums[r]; nums[r]=tmp; } } nums[i]=nums[l]; nums[l]=pivot; qs(nums,i,l-1); qs(nums,l+1,j); }}
结果反手收到一个TLE , , , ,
题目有一个样例是 五万个2 的数组 , , , ,
那么对于上面的这个样例,实际在执行上面的代码的时候对于每一个基准数字的复杂度都是O(N) ,也就是快排的最差的情况,此时的快速排序也就退化成了冒泡排序,
算法的运行时间的递归式可表达为:T(n) = T(0) + T(n - 1) + O(n) = T(n - 1) + O(n),T(n)的解是O(N2)。
那么对于我们想的最理想的快排的情况,应该是每次切分之后两个数组的大小n / 2时,
这时一个的数组的大小为[n / 2 ...
深入理解redis持久化(1):RDB持久化[redis]
众所周知 , redis之所以这么快, 很大一部分原因是因为redis中存储的数据都在内存中, 但是如果我们的服务器在运行的过程中宕机或者重启了, 内存中的数据就会直接丢失 , 因此我们在使用redis 的时候必须要将数据持久化到磁盘中 , 以备不时之需。
数据持久化就是将内存中的数据模型转换为存储模型, 以及将存储模型转换为内存中的数据模型的统称.
数据模型可以是任何数据结构或对象模型,存储模型可以是关系模型、XML、二进制流等。
cmp和Hibernate只是对象模型到关系模型之间转换的不同实现。
比如我们常用的mybatis就是一个持久层框架
事实上Redis 有两种持久化方案,分别是 RDB (Redis DataBase) 和 AOF (Append Only File)。
本篇涉及到redis源码 , 可以先了解redis的目录结构(37条消息) redis之源码目录结构_happytree001的博客-CSDN博客_redis源码目录
aof.c
rdb.c、rdb.h
server.c
RDB
RDB文件的创建与加载
创建
首先 , 有两个Redis命令可以用于生成RDB文件,一个是SAVE,另一个是BGSAVE。
SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求
*BGSAVE ( Background saving )*命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求
也就是说 BGSAVE和SAVE命令直接阻塞服务器进程的做法不同 , 不会阻塞服务器进程
创建RDB文件的实际工作由rdb.c/rdbSave 函数完成,SAVE命令和BGSAVE命令会以不同的方式调用这个函数
SAVE
123def SAVE(): # 创建RDB文件 rdbSave()
BGSAVE
1234567891011121314def BGSAVE(): # 创建子进程 pid = fork() if pid == 0: # 子进程负责创建RDB文件 rdbSave() # 完成之后向父进程发送信号 signal_parent() ...
数据结构作业
作业中编程相关题目均使用Java实现
关于用到的类的具体实现
1234567891011121314151617181920public class ListNode { int val; ListNode next; ListNode() {} ListNode(int val) { this.val = val; } ListNode(int val, ListNode next) { this.val = val; this.next = next; }}public class TreeNode { int val; TreeNode left; TreeNode right; TreeNode() {} TreeNode(int val) { this.val = val; } TreeNode(int val, TreeNode left, TreeNode right) { this.val = val; this.left = left; this.right = right; }}
练习1
2
树形结构, 其中a没有前驱结点, 为根节点, b e i g 没有后继节点, 为叶子结点
8
(1)
1234567public long sumArray(int[]nums){ long res=0; for(int i=0;i<nums.length;i++){ res+=nums[i]; } return res;}
(2)
123456789101112131415public void printNumsByASC(long a,long b,long c){ if(a>b) { if (b > c) { System.out.println("" + c + " " + b + " " + a); } else { System.out.println("" + b + " " + c + " " + a); } }else{ if (a > c) ...
使用java实现简易LRU
146. LRU 缓存 - 力扣(LeetCode)
算法就像搭乐高:带你手撸 LRU 算法 :: labuladong的算法小抄
LRU , Least Recently Used 按照访问数据的访问频率来进行操作, 在缓存写满的时候,会根据所有数据的访问记录,淘汰掉未来被访问几率最低的数据 , 在os中的页面置换算法以及MySQL 的innodb存储引擎 ( MySQL中的具体参数有所改变 , 不过算法思想是相通的 )中都有使用 , 因此熟悉LRU的具体操作还是十分必要的 。
LRU算法认为,最近被访问过的数据,在将来被访问的几率最大。
算法实现
HashMap + 双向链表
1transient Node<K,V>[] table;
关于java 中transient 关键字 , 首先我们知 道如果一个类实现了Serilizable 接口并且声明了 Serializable, 那么这个类就可以被序列化 , 那么 transient 的作用就是用于标记类中的属性, ==表示这个属性不会被序列化==
我们知道java中的HashMap 类的底层实现是使用数组实现的 , 如果出现了哈希冲突 , 在java8以前会使用链表扩容, java8以及以后使用的是红黑树进行扩容 , 但是如果在当前的场景中, 由于HashMap是无序的, 因此我们无法获取到哪个是 最久没有使用的数据 , 这个时候就需要借助于链表 , 我们维持一个序列 , 把每次使用的数据以及添加的数据添加到序列的尾部 , 如果在添加的时候发现 序列达到了最大的容量 , 就删除最前面的数据 , 这样也就达到了 Least Recently Used 的要求, 由于时间复杂度方面的要求 , 需要使用双向链表实现 , 但是由于链表获取数据的时间复杂度为 O(N) , 因此需要通过hash来快速的进行查找
自然而然, Map的映射关系为 index: Node , 对应 java中的数据类型也就是 <Integer, Node> , 下面为具体的代码实现
手写双向链表 , 然后通过HashMap以及 DoubleList 来实现 LRUCache
LRUCache(int capacity) 以 正整数 作为容量 capaci ...
AOP+注解实现登录验证
之前的项目遇到的用户登录验证都是使用拦截器(MvcConfig) , 现在遇到的情况是 需要在springcloud gateway中进行登录验证 , 但是gateway 是基于webflux实现的, 本身与 springMVC会产生冲突, 因此考虑使用 AOP 面向切面编程 以及注解 来实现对用户的登录验证功能
前置知识
[Spring AOP-面向切面编程 | dhx_'blog](http://39.105.61.0/2022/07/18/SSM/Spring/Spring day03 AOP/)
AOP
只做增强 , 不做修改
Spring 框架一般都是基于 AspectJ 实现 AOP 操作,AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把AspectJ 和 Spirng 框架一起使 用,进行 AOP 操作
AOP底层采用的是动态代理, 主要有两种情况
有接口情况,使用 JDK 动态代理 ;创建接口实现类代理对象,增强类的方法
没有接口情况,使用 CGLIB 动态代理;创建子类的代理对象,增强类的方法
还有就是AOP的一些操作术语
连接点:类里面哪些方法可以被增强,这些方法称为连接点
切 入点:实际被真正增强的方法称为切入点
通知(增强):实际增强的逻辑部分称为通知,且分为以下五种类型:
前置通知:在我们执行目标方法之前运行(@Before)
后置通知:在我们目标方法运行结束之后,不管有没有异常(@After)
返回通知:在我们的目标方法正常返回值后运行(@AfterReturning)
异常通知:在我们的目标方法出现异常后运行(@AfterThrowing)
环绕通知:目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,joinPoint.procced()就是执行目标方法的代码 。环绕通知可以控制返回对象(@Around)
切面:把通知应用到切入点过程
@After 与@AfterReturning 的区别 : @After 即便抛出了异常 依然后执行 ,@AfterReturning如果运行过程中抛出了异常就不会执行
如果运行过程中 出现了异常, @Around 的 第二次环绕不会执行
AOP切入点表达式
切入点表示我们需要在哪个地方做增强
...
OSS简单总结(涉及vue)
最近在给项目写后台管理系统, 使用的是人人开源的模板 , 然后结合代码生成器生成了基本的CRUD代码 ( 真的爽~)
不过主要的代码还是需要自己写的, 这里总结一下vue遇到的一些基本的问题以及一些简单的知识点
贴一个地址 人人开源 (renren.io)
一般情况下会把 vue 代码写到<script> 标签里面 , vue其实就是一个对象( 结合vue的生命周期理解 ) , 基本的结构如下
12345678910111213141516171819202122232425262728<template> <button @click="increment">Count is: {{ count }}</button></template><script> export default {// data() 返回的属性将会成为响应式的状态// 并且暴露在 `this` 上data() { return { count: 0 }}, // methods 是一些用来更改状态与触发更新的函数 // 它们可以在模板中作为事件监听器绑定 methods: { increment() { this.count++ } }, // 生命周期钩子会在组件生命周期的各个不同阶段被调用 // 例如这个函数就会在组件挂载完成后被调用 mounted() { console.log(`The initial count is ${this.count}.`) }} </script>
OSS服务端签名直传
这里传输图片的方式是前端向后端发送请求然后获取凭证直接向oss传输图片
这样做的好处就是可以减轻服务器的压力, 否则在高峰期传输图片很占用服务器资源
同时相较于吧代码都放到前端会更加安全
具体细节参考官方文档Java (aliyun.com) 服务端签名后直传 (aliyun.com)
配置服务器 java
配置客户端 Vue
在阿里云控制台设置跨域 CORS
Demo测试
获取凭 ...
docker常用容器部署命令总结
总结一下Docker常用镜像 , 容器以及部署命令
docker常用命令
拉取镜像
docker pull ${image}:${version} 如果不写version默认为latest
查看日志
docker logs ${container}
docker logs -f ${container} 持续查看容器日志
进入容器内部
docker exec -it ${container} /bin/bash
设置docker自动启动
systemctl enable docker.service
设置容器自动启动
docker update ${container} --restart=always
docker常用容器部署命令
注意 , 如果使用-v 参数进行主机与容器的数据挂载 , 需要在主机里面提前创建好对应的目录 , 否则创建容器失败
举例
这里主机中并没有 /mydata目录, 因此创建容器失败
12345678[root@192 usr]# docker run --restart=always -d \> -v /mydata/mysql/conf.d/my.cnf:/etc/mysql/my.cnf \> -v/mydata/mysql/logs:/logs \> -v /mydata/mysql/data/mysql:/var/lib/mysql \> -p 3306:3306 --name mysqltest \> -e MYSQL_ROOT_PASSWORD=root mysql:5.7a0c1120e96b2d8b124e8c1b325f3518c21d240aca4a75c1739f0c7ffb5092847docker: Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: error mounting "/mydata/mysql/conf.d/my.cnf" to rootfs at "/etc/mysq ...
go语言基础入门[0]
学习书籍 : Go程序设计语言
命名
命名与java c/c++等语言命名基本一一致,
区分大小写
名称的开头只能是字母(Unicode中的字符即可)或下划线
比如heapSort和Heapsort是不同的名称。
Go的关键字
关键字不能作为变量名
break
default
func
interface
select
case
defer
go
map
struct
chan
else
goto
package
switch
const
fallthrough
if
range
type
continue
for
import
return
var
Go还有内置的预生命的 常量 , 类型以及函数
常量
true false iota nil
nil与 java 中的null意义相同
类型
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64
uintptr
float32 float64
complex128 complex64
bool
byte
rune
string
error
这些名称不是预留的,可以在声明中使用它们。我们将在很多地方看到对其中的名称进行重声明,但是要知道这有冲突的风险。
如果一个实体在函数中声明,它只在函数局部有效。如果声明在函数外,它将对包里面的所有源文件可见。
==实体第一个字母的大小写决定其可见性是否跨包。==如果名称以==大写字母==的开头,它是==导出==的,意味着它对包外是可见和可访问的,可以被自己包之外的其他程序所引用,像fmt包中的Printf.。
包名本身总是由小写字母组成。
Go的命名风格上通常使用 驼峰式 的风格,
这一点就跟java差不多
不过需要注意的是 像ASCIⅡ和HTML这样的首字母缩写词通常使用相同的大小写,
所以一个函数可以叫作htmlEscape、HTMLEscape或escapeHTML,但不会是escapeHtmlo
声明
Go有4个主要的声明:变量( var)、常量(const)、类型(type)和函数(func)。
下面的程序声明一个常量、一个函数和一对变量:
12345678910package mainimport "fmt"cosnt bopilingF = 212.0func main( ...
后端如何优雅地返回数据
在我们日常后端开发中,在处理数据之后,给客户端返回一个统一的、优雅的、规范的结果,能够让客户端很容易判断数据交给服务端处理的结果。
首先 , 我们需要知道的是我们通常会返回什么样的数据给前端?
code : 状态码 , 除去一些常见的状态码 , 其他的一般由自己添加
比如
200 - 请求成功
301 - 资源(网页等)被永久转移到其它URL
404 - 请求的资源(网页等)不存在
500 - 内部服务器错误
data : 结果数据
message : 信息
description : 描述返回的信息 , description的定义似乎与message有一点冲突 , 不过可以根据情况选择是否携带
对象+工具类+枚举类
首先我们可以定义一个对象 , 简单的取个名字 , BaseResponse
在前面我们提到了常见的错误信息 , 这里可以吧常见的错误封装成枚举类, 在出现异常或者代码执行出错的时候可以直接利用枚举类来做出处理
错误枚举类ErrorCode
这里可以看到我们一般在message属性中简单的描述错误的原因 , 具体的描述可以封装到description
举个简单的例子 , 用户在注册的时候传入了用户名, 而恰好此时的用户名不符合我们定义的规范(一般情况我们会使用正则来校验用户传入的数据的合法性)
这个时候我们可以直接抛出异常, 同时通过ErrorCode来快速的返回错误内容,
同时在description属性中封装 用户名不符合规范 ! 等类似的提示信息 , 提升用户体验
12345678910111213141516171819202122232425262728293031public enum ErrorCode { SUCCESS(0,"ok",""), //HTTP状态码本身就是500,为什么500,因为你的业务里面抛异常 , 但是不应该让前端出现500,因为我们刚刚自己定义了一个业务异常,它应该返回40000 PARAMS_ERROR(40000,"请求参数错误",""), NULL_ERROR(40001,"请求数据为空",""), NOT_LOGIN(40100,"未登录",""), NO_AUTH(40101,"无权限",""), ...
记录一个ik分词器安装bug
以后就专门准备一个bug分类来记录遇到的bug
安装ik分词器的时候, 明明已经安装成功了, 但是却没有生效
那么来一步步的排查问题
docker exec -it elasticsearch /bin/bash进入容器内部
cd /bin进入bin目录
elasticsearch-plugin list显示插件列表
这个时候发现突然就报错了
1234567891011121314151617181920212223242526272829java.lang.IllegalStateException: Could not load plugin descriptor for plugin directory [elasticsearch-analysis-ik-7.6.2]Likely root cause: java.nio.file.NoSuchFileException: /usr/share/elasticsearch/plugins/elasticsearch-analysis-ik-7.6.2/plugin-descriptor.properties at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:92) at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111) at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:116) at java.base/sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:219) at java.base/java.nio.file.Files.newByteChannel(Files.java:374) at java.base/java.nio.file.Files.newByteChannel(Files.java:425) at java.base/java.nio.file.spi ...


![深入理解redis持久化(1):RDB持久化[redis]](https://i.loli.net/2020/05/01/gkihqEjXxJ5UZ1C.jpg)




![go语言基础入门[0]](https://dhx-blog.oss-cn-beijing.aliyuncs.com/dhx/149a8bdf31m2m1u.jpg)




