0%

文章字数:19,阅读全文大约需要1分钟

1
2
3
4
5
6
7
8
9
10
Inter i = new Inter(){//多态
public void show(){
System.out.println("show");
}
public void show2(){
System.out.println("show2");
}
};
i.show();//调用类方法
i.show2();

文章字数:495,阅读全文大约需要1分钟

机器数和真值

  • 真值: 即数字本身的值,例如-3
  • 机器数: 数字在计算机中存储的值,第一位为符号位1代表负数,0代表正数。后面的二进制代表绝对值,例如-3的机器数就是10000011

原码

  • 原码即机器数,是计算机对于数值的二进制表示方式
  • 除了第一位的符号位,其余便是绝对值。因此可以表示的范围为[1111 1111, 0111 1111][-127, 127]

反码

  • 正数不变
  • 负数在原码的基础上,除了符号位,其余取反
  • 为了解决数值减法的问题,通过反码可以让符号位也参与运算,从而将减法也转换成加法(2-1 = 2 + (-1))

补码

  • 正数不变
  • 负数在反码的基础上,+1
  • 为了解决反码存在两个值表示0的情况(因为存在符号位,所以有一个+0和-0)
  • 使用补码之后负数能比正数多表示一个值(正数需要表示0,负数部分不需要)
  • 所以补码表示范围就成了[-2^8, (2^8)-1][-127, +127]

计算机加减法

  • 补码将减法也变成了加法,计算机上数值都是以补码形式存储的,所以直接二进制相加即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int add(int a, int b) {
if(a==0) {
return b;
}
if(b==0) {
return a;
}
// 两个都是1的需要进位
int c = a & b;
// 进位
c = c << 1;
// 按位相加,0+1=1,1+0=1,0+0=0,1+1=0(进位了,所以当前位为0),所以直接异或即可。
int d = a ^ b;
return add(c, d);
}
}

文章字数:473,阅读全文大约需要1分钟

Atom不可分割的意思,CAS是一种替换操作,不是锁。

一、原子操作

1.1sync的缺点

  1. 基于阻塞机制
  2. 被阻塞的线程优先级很高
  3. 拿到锁的线程不释放
  4. 大量竞争消耗cpu带来死锁等问题

1.2 CAS原理

  1. 基于指令级别保证是原子操作
  2. 基本思路:如果内存地址的值和期望值相等,就把新的值赋给此内存地址,否则不进行操作
  3. 在循环中不断进行CAS操作,直到成功(自旋)。

1.3 ABA问题

  • A被改成了B又被改回了A。对于一个线程来说,检测到值为A就以为没有改动过,其实有可能是再次被改回来了。
  • 解决方法是加上版本号。

1.4 性能问题

  • 如果长期不成功,cpu会不断循环

1.5 常用工具

类型 jdk类 备注
基本类型 AtomicBoolean AtomicInteger AtomicLong 常用getAndIncrement()先获取后累加,incrementAndGet()先累加再获取之类的方法。
数组 AtomicIntegerArray AtomicLongArray AtomicReferenceArray 可以理解为数组的引用类型
引用类型 AtomicReference AtomicMarkableReference AtomicStampedReference AtomicReference包装一个类,然后调用compareAndSet进行原子操作替换成另一个类。可以进行整个类的CAS原子操作。另外两个主要关系ABA问题,mark关心是否被动过,stamped关心动过几次
原子更新字段类 AtomicReferenceFieldUpdater AtomicIntegerFieldUpdater AtomicLongFieldUpdater 比较麻烦,可以用上一个替代

文章字数:115,阅读全文大约需要1分钟

怀疑人生,去掉输出程序执行错误(时好时坏)

1
2
3
4
//把密码加密再提交表单
var md5Pwd=hex_md5(pwd);
$("#password").val(md5Pwd);
$('#form')[0].submit();

原来提交之前输出md5字符串的值,之后去掉了。结果提交的密码就成了明文???

可能的问题

可能加密或者val()存在异步操作,程序直接进入下一步。

暂时解决办法

1
2
3
4
5
var md5Pwd=hex_md5(pwd);
$("#password").val(md5Pwd);
setTimeout(function () {
$('#forgotPwdForm')[0].submit();
})

文章字数:711,阅读全文大约需要2分钟

整理自原文

反射效率低的原因

  1. Method#invoke方法会对参数做封装和解封操作
    invoke方法参数是Object[]类型,如果方法的参数是简单类型,那么需要先包装成Object类型。例如long需要装换成Long。此时产生了多余的Long类型的Objec。生成动态字节码,并加载到jvm的方法MethodAccessorGenerator#emitInvoke会将参数恢复到之前的类型,同时做参数校验。
    反射调用的时候可能会将参数进行多余的封装和解封,产生不必要的内存浪费。调用次数过多甚至会导致GC(感觉有点夸张)

  2. 需要检测方法可见性
    反射每次调用都必须检查方法的可见性(Method.invoke)

  3. 需要校验参数
    反射必须检查每个实际参数和形式参数的类型匹配(在NativeMethodAccessorImpl.invoke0 里或者生成的 Java 版 MethodAccessor.invoke 里)

  4. 方法难以内联
    Method.invoke()自身难以被内联到方法调用。

  5. JIT无法优化
    反射涉及到动态加载的类型,无法优化。

解决

文章没给,但是自己能想到几个

  1. 方法参数使用包装类型(减少了包装和拆包的步骤)
  2. 将反射获取的Method等对象缓存下来,这样在下次调用的时候可以减少生成的开销。

总结

  1. 获取方式的方法
  • getMethod / getDeclaredMethod
    调用反射时现需要创建Class对象(Class.forName),然后获取Method对象(getMethod / getDeclaredMethod),最后才是invoke调用。
    相同:getMethodgetDeclaredMethod的内部结构都是检查方法权限获取方法返回方法的拷贝
    不同:getMethod检查和获取的方法可以是自身的也可以是父类的,getDeclaredMethod获取的方法只能是自身的。

  • checkMemberAccess
    第一步,检查方法权限。getMethod的传入值之一是Member.PUBLICgetDeclaredMethodMember.DECLARED。一个检查父类和自身,另一个只有自身方法。

  • getMethod0
    getMethod方法检查完方法权限后调用getMethod0获取方法

  • getMechodsRecursive
    递归查找父类方法

  • privateGetDeclaredMethods
    获取自身方法

  • Method#copy

  1. 调用方式的方法
  • checkAccess
  • acquireMethodAccessor
  • MethodAccessor#invoke
  1. 方式效率低的原因
  • Method#invoke 方法会对参数做封装和解封操作
  • 需要检查方法的可见性
  • 需要校验参数
  • 反射方法难以内联
  • JIT无法优化

文章字数:174,阅读全文大约需要1分钟
原文

全局数据响应(返回数据统一)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RestControllerAdvice(basePackages = {"com.rudecrab.demo.controller"}) // 注意哦,这里要加上需要扫描的包
public class ResponseControllerAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> aClass) {
// 如果接口返回的类型本身就是ResultVO那就没有必要进行额外的操作,返回false
return !returnType.getGenericParameterType().equals(ResultVO.class);
}

@Override
public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) {
// String类型不能直接包装,所以要进行些特别的处理
if (returnType.getGenericParameterType().equals(String.class)) {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 将数据包装在ResultVO里后,再转换为json字符串响应给前端
return objectMapper.writeValueAsString(new ResultVO<>(data));
} catch (JsonProcessingException e) {
throw new APIException("返回String类型错误");
}
}
// 将原本的数据包装在ResultVO里
return new ResultVO<>(data);
}
}

全局异常返回

1
2
3
4
5
6
7
8
9
10
11
@RestControllerAdvice
public class ExceptionControllerAdvice {

@ExceptionHandler(MethodArgumentNotValidException.class)
public String MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
// 从异常对象中拿到ObjectError对象
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
// 然后提取错误提示信息进行返回
return objectError.getDefaultMessage();
}
}

文章字数:204,阅读全文大约需要1分钟

响应式编程有三个组成部分 变化传递propagation of change、 数据流data stream、 申明式declarative

变化传递(propagation of change)

在响应式编程中,一个模块数据产生变动,相应模块的数据也会变化。(vue的数据绑定)
生产者生成发送 数据/事件,消费者监听并负责处理数据变化传递的方式。

数据流(data stream)

响应式编程中的数据是以数据流的形式发出的,相应的方法监听数据流,对于数据流的元素一次处理。

声明式(declarative)

数据流中说到的监听数据流的方法中对于数据流应该如何处理的定义就是声明。预先定义好将如何对数据流进行处理就是声明式。


文章字数:359,阅读全文大约需要1分钟

回文子串指的是从前向后和从后向前读是一样的字符串,比如abba或者abcba

暴力获取法

以下是我自己写的,思路如下:

  1. 获取可能是回文子串的字符串
  2. 判断是否是回文子串

实现是:

  1. 用一个循环遍历所有字符
  2. 再嵌套一个循环,从反方向循环,找到一样的值
    (左右相同就可能是回文子串)
  3. 判断字符串是否是回文子串,用个循环遍历其中的内容。
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
class Solution {
public String longestPalindrome(String s) {

if (s == null || s.trim().equals("")) {
return "";
}

int longsLeft = 0;
int longsRight = 0;

int maxLength = 0;

char[] args = s.toCharArray();

for (int i = 0; i < s.length() - maxLength; i++) {
for (int j = i + 1; j < s.length(); j++) {
if (args[i] == args[j]) {
if (isPalindrome(s.substring(i, j + 1))) {
int longs = j - i + 1;
if (longs > maxLength) {
longsLeft = i;
longsRight = j;
maxLength = longs;
}
}
}
}
}

return s.substring(longsLeft, longsRight + 1);
}

boolean isPalindrome(String arg) {
char[] args = arg.toCharArray();
for (int i = 0, j = args.length - 1; i < j; i++, j--) {
if (args[i] != args[j]) {
return false;
}
}
return true;
}
}

时间复杂度O(n^3)
为了直观,现将String转成char[]再操作,多占了内存。将char[]的操作改成直接操作String可以达到空间复杂度O(1)

中心扩散法

leetcode官方讲解中看到的一个解决方法,时间复杂度O(n^2),空间复杂度O(1)

思路:
每个回文子串都有个中心,abba的中心就是bbabcba的中心是c
通过中心向外扩散,即可得到整个回文子串。

实现

  1. 循环遍历每个元素(每两个)
  2. 以每个元素为中心,向两边遍历,直到不相同即为回文子串长度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public String longestPalindrome(String s) {
if (s == null || s.length() < 1) return "";
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = expandAroundCenter(s, i, i);
int len2 = expandAroundCenter(s, i, i + 1);
int len = Math.max(len1, len2);
if (len > end - start) {
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);
}

private int expandAroundCenter(String s, int left, int right) {
int L = left, R = right;
while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) {
L--;
R++;
}
return R - L - 1;
}

文章字数:265,阅读全文大约需要1分钟

基数排序适用于整形排序,将数据按照位数切割成不同的数字,按照权重依次桶排序。

解析

第一次排序,位数最大的在后面,小的在前面。
第二次排序十位上的大的向后
第三次百位大的向后

越在后面进行的排序作用就越大,因为可以推翻之前的排序结果。位数越靠前代表的值也越大,所以从个位开始到最高位。

本质上是桶排序的增强版,不需要那么多的桶。

代码

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
public static void sort(int[] arr){
int length = arr.length;

//最大值
int max = arr[0];
for(int i = 0;i < length;i++){
if(arr[i] > max){
max = arr[i];
}
}
//当前排序位置
int location = 1;

//桶列表
ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>();

//长度为10 装入余数0-9的数据
for(int i = 0; i < 10; i++){
bucketList.add(new ArrayList());
}

while(true)
{
//判断是否排完
int dd = (int)Math.pow(10,(location - 1));
if(max < dd){
break;
}

//数据入桶
for(int i = 0; i < length; i++)
{
//计算余数 放入相应的桶
int number = ((arr[i] / dd) % 10);
bucketList.get(number).add(arr[i]);
}

//写回数组
int nn = 0;
for (int i=0;i<10;i++){
int size = bucketList.get(i).size();
for(int ii = 0;ii < size;ii ++){
arr[nn++] = bucketList.get(i).get(ii);
}
bucketList.get(i).clear();
}
location++;
}
}

文章字数:511,阅读全文大约需要2分钟

文件转Base64

1
2
3
4
5
6
7
8
9
10
11
12
public String encryptToBase64(String filePath) {
if (filePath == null) {
return null;
}
try {
byte[] b = Files.readAllBytes(Paths.get(filePath));
return Base64.getEncoder().encodeToString(b);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

Files、Paths类是JDK7里加入的,读取文件不再需要调用IO包里的FileInputStream,简单便捷。字符串参数filePath是文件的路径。

首先是将文件读成二进制码,然后通过Base64.getEncoder().encodeToString()方法将二进制码转换为Base64值。

Base64转文件

1
2
3
4
5
6
7
8
9
10
11
public String decryptByBase64(String base64, String filePath) {
if (base64 == null && filePath == null) {
return "生成文件失败,请给出相应的数据。";
}
try {
Files.write(Paths.get(filePath), Base64.getDecoder().decode(base64),StandardOpenOption.CREATE);
} catch (IOException e) {
e.printStackTrace();
}
return "指定路径下生成文件成功!";
}

Files.write()方法轻松将文件写入指定位置

Path类

1.7之后引入了java.nio.file包取代原来基于java.io.File的文件IO操作
位置:

  • java.nio.file.Files
  • java.nio.file.Path
  1. 获取path

    1
    2
    3
    4
    5
    6
    7
    Path path;
    //1.绝对路径
    path = Paths.get("c:\\test.txt");
    //2.相对路径
    path = Paths.get("/demo/test.txt");
    //3.通过FileSystems
    path = FileSystems.getDefault().getPath("c:\\test.txt");
  2. FilePath转换

    1
    2
    3
    4
    5
    6
    7
    File file = new file("c:\\xxx.txt");
    //文件转Path
    Path path = file.toPath();
    //Path转File
    path.toFile();
    //File转uri
    file.toURI();
  3. Path信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Path path = Paths.get("D:\\demo\text.txt");
    System.out.println("文件名:" + path.getFileName());
    System.out.println("名称元素的数量:" + path.getNameCount());
    System.out.println("父路径:" + path.getParent());
    System.out.println("根路径:" + path.getRoot());
    System.out.println("是否是绝对路径:" + path.isAbsolute());
    //startsWith()方法的参数既可以是字符串也可以是Path对象
    System.out.println("是否是以为给定的路径D:开始:" + path.startsWith("D:\\") );
    System.out.println("该路径的字符串形式:" + path.toString());

File类

  1. 文件是否存在

    1
    2
    3
    4
    Path path = Paths.get("D:\\demo.txt");
    boolean pathExists = Files.exists(path,new LinkOption[]{
    LinkOption.NOFOLLOW_LINKS
    });//数组内的NOFOLLOW_LINKS代表不包含符合链接文件
  2. 创建文件/文件夹

    1
    2
    3
    4
    5
    6
    7
    Path target2 = Paths.get("C:\\demo.txt");
    try{
    if(!Files.exists(target2))
    Files.createFile(target2);
    }catch(IOException e){
    e.printStackTrace();
    }

    Files.createDirectory()创建文件夹,上级目录不存在报错
    Files.createDirectorys()创建文件夹,上级目录不存在则创建上级目录

  3. 删除文件或目录

    1
    2
    3
    4
    5
    6
    Path path = Paths.get("data/subdir/logging-moved.properties");
    try {
    Files.delete(path);
    } catch (IOException e) {
    e.printStackTrace();
    }
  4. 复制文件到另一个位置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Path sourcePath = Paths.get("data/logging.properties");
    Path destinationPath = Paths.get("data/logging-copy.properties");
    try {
    Files.copy(sourcePath, destinationPath);
    } catch(FileAlreadyExistsException e) {
    //文件已经存在
    } catch (IOException e) {
    e.printStackTrace();
    }

    还可以直接覆盖目标文件

    1
    2
    Files.copy(sourcePath, destinationPath,
    StandardCopyOption.REPLACE_EXISTING);
  5. 获取文件属性

    1
    2
    3
    4
    5
    6
    Path path = Paths.get("D:\\XMind\\bcl-java.txt");
    System.out.println(Files.getLastModifiedTime(path));
    System.out.println(Files.size(path));
    System.out.println(Files.isSymbolicLink(path));
    System.out.println(Files.isDirectory(path));
    System.out.println(Files.readAttributes(path, "*"));
  6. 遍历一个文件夹

    1
    2
    3
    4
    5
    6
    7
    8
    Path dir = Paths.get("D:\\Java");
    try(DirectoryStream<Path> stream = Files.newDirectoryStream(dir)){
    for(Path e : stream){
    System.out.println(e.getFileName());
    }
    }catch(IOException e){
    //...
    }
  7. 遍历整个文件目录
    walkFileTree接受一个path和FileVisitor,path是遍历的目录,FileVistor则是一个接口,每次遍历都会被调用,需要自己实现。
    SimpleFileVisitor是默认实现类,将接口所有方法都做了空实现。

    1
    2
    3
    4
    //地址
    Path startingDir = Paths.get("D:\\demo");
    //调用
    Files.walkFileTree(startingDir, new FindJavaVisitor());

    FindJavaVisitor.java

    1
    2
    3
    4
    5
    6
    7
    public class FindJavaVisitor extends SimpleFileVisitor<Path>{
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs){
    //do...
    return FileVisitResult.CONTINUE;
    }
    }