使用ASM实现动态代理
如果对我这段代码感兴趣,直接拷贝测试debug,要不然你不知道我写的是什么鬼,如果有什么问题,可以告诉我。
一、实现动态代理,首先得考虑有应该定义哪些类,根据JDK的动态代理思想,那么它就应该有一个生成代理的类
- package com.asm_core;
- import java.io.PrintWriter;
- import java.lang.reflect.Method;
- import java.util.List;
- import org.objectweb.asm.ClassWriter;
- import org.objectweb.asm.Opcodes;
- import org.objectweb.asm.tree.ClassNode;
- import org.objectweb.asm.tree.FieldInsnNode;
- import org.objectweb.asm.tree.FieldNode;
- import org.objectweb.asm.tree.InsnList;
- import org.objectweb.asm.tree.InsnNode;
- import org.objectweb.asm.tree.JumpInsnNode;
- import org.objectweb.asm.tree.LabelNode;
- import org.objectweb.asm.tree.LdcInsnNode;
- import org.objectweb.asm.tree.MethodInsnNode;
- import org.objectweb.asm.tree.MethodNode;
- import org.objectweb.asm.tree.TypeInsnNode;
- import org.objectweb.asm.tree.VarInsnNode;
- import org.objectweb.asm.util.TraceClassVisitor;
- import com.asm_core.logic.AddLogic;
- /**
- * 生成代理对象
- * @author may
- *
- */
- public class GenSubProxy {
- //逻辑接口
- private AddLogic logic = null;
- //被代理类的所有方法
- private Method[] methods = null;
- private String classNamePrefix = null;
- private String descInfoPrefix = null;
- private String logicPkg = null;
- public GenSubProxy(AddLogic logic) {
- String logicClassName = AddLogic.class.getName();
- this.logicPkg = logicClassName.substring(0, logicClassName.lastIndexOf(AddLogic.class.getSimpleName())).replace(".", "/");
- this.logic = logic;
- }
- public Object genSubProxy(Class<?> superClass) {
- //获得被代理类的方法集合
- methods = superClass.getDeclaredMethods();
- classNamePrefix = superClass.getName().substring(0, superClass.getName().lastIndexOf(superClass.getSimpleName()));
- descInfoPrefix = classNamePrefix.replace(".", "/");
- Object obj = null;
- try {
- PrintWriter pw = new PrintWriter(System.out, true);
- //生成ClassNode
- ClassNode cn = genClassNode(superClass);
- //ClassWriter.COMPUTE_FRAMES表示让asm自动生成栈图,虽然会慢上二倍。
- ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
- TraceClassVisitor tcv = new TraceClassVisitor(cw, pw);
- cn.accept(tcv);
- //cn.accept(cw);
- byte[] b = cw.toByteArray();
- MyClassLoader classLoader = new MyClassLoader(b);
- Class<?> proxy = classLoader.loadClass(classNamePrefix + "$Proxy");
- obj = proxy.newInstance();
- Method method = proxy.getDeclaredMethod("setLogic", AddLogic.class);
- method.invoke(obj, logic);
- Method meth = proxy.getDeclaredMethod("setMethods", Method[].class);
- meth.invoke(obj, new Object[] {methods});
- } catch (Exception e) {
- e.printStackTrace();
- }
- return obj;
- }
- public ClassNode genClassNode(Class<?> superClass) {
- String superName = superClass.getName().replace(".", "/");
- ClassNode cn = new ClassNode(Opcodes.ASM5);
- //定义代理类的类名
- cn.name = descInfoPrefix + "$Proxy";
- //超类为当前被代理类
- cn.superName = superName;
- cn.access = Opcodes.ACC_PUBLIC;
- cn.version = Opcodes.V1_8;
- cn = proxyMethod(cn);
- return cn;
- }
- @SuppressWarnings("all")
- public ClassNode proxyMethod(ClassNode cn) {
- List<MethodNode> list = cn.methods;
- List<FieldNode> fields = cn.fields;
- //这里赋初始值必须是Integer, Float, Long, Double 或者 String,null
- fields.add(new FieldNode(Opcodes.ACC_PUBLIC, "logic", "L" + logicPkg + "AddLogic;", null, null));
- //添加methods字段
- fields.add(new FieldNode(Opcodes.ACC_PUBLIC, "methods", "[Ljava/lang/reflect/Method;", null, null));
- //添加方法setLogic,用于设置Logic
- MethodNode mn = new MethodNode(Opcodes.ACC_PUBLIC, "setLogic", "(L" + logicPkg + "AddLogic;)V", null, null);
- //下面的指令相当于this.logic = logic;
- InsnList insnList = mn.instructions;
- insnList.add(new VarInsnNode(Opcodes.ALOAD, 0));
- insnList.add(new VarInsnNode(Opcodes.ALOAD, 1));
- insnList.add(new FieldInsnNode(Opcodes.PUTFIELD, descInfoPrefix + "$Proxy", "logic", "L" + logicPkg + "AddLogic;"));
- insnList.add(new InsnNode(Opcodes.RETURN));
- mn.maxLocals = 2;//定义最大的本地变量
- mn.maxStack = 2;//定义最大的操作数栈
- list.add(mn);
- //添加一个setMethods方法,用于设置methods字段
- MethodNode meth = new MethodNode(Opcodes.ACC_PUBLIC, "setMethods", "([Ljava/lang/reflect/Method;)V", null, null);
- //这段指令相当于this.methods = methods;
- InsnList methList = meth.instructions;
- methList.add(new VarInsnNode(Opcodes.ALOAD, 0));
- methList.add(new VarInsnNode(Opcodes.ALOAD, 1));
- methList.add(new FieldInsnNode(Opcodes.PUTFIELD, descInfoPrefix + "$Proxy", "methods", "[Ljava/lang/reflect/Method;"));
- methList.add(new InsnNode(Opcodes.RETURN));
- meth.maxLocals = 2;
- meth.maxStack = 2;
- list.add(meth);//
- //添加构造方法
- MethodNode init = new MethodNode(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
- //这里是调用父类构造器,相当于super();
- InsnList initList = init.instructions;
- initList.add(new VarInsnNode(Opcodes.ALOAD, 0));
- initList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, descInfoPrefix + "SayHello", "<init>", "()V", false));
- initList.add(new InsnNode(Opcodes.RETURN));
- init.maxLocals = 1;
- init.maxStack = 1;
- list.add(init);
- int count = 0;
- //循环创建需要代理的方法
- for (Method method : methods) {
- MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC, method.getName(), DescInfo.getDescInfo(method), null, null);
- // System.err.println(DescInfo.getDescInfo(method));
- InsnList il = methodNode.instructions;
- //获得参数的类型
- Class<?>[] clazz = method.getParameterTypes();
- //计算出参数会占用掉的本地变量表长度,long,double类型占用两个slot
- int len = LocalLen.len(clazz);
- //获得这个方法的参数个数,不包括this
- int size = clazz.length;
- //或的返回值类型
- Class<?> rtClazz = method.getReturnType();
- il.clear();
- /**
- * 下面的一大段指令的意思是
- * int index = count;
- * Method method = methods[index];//从methods中获得对应的方法对象
- * Object[] objs = new Object[]{arg0,arg1,arg2....};用来存方法传过来的参数值的
- * try {
- * logic.addLogic(method, objs);
- * } catch(Exception e) {
- * e.printStackTrace();
- * }
- */
- il.add(new LdcInsnNode(count));//
- il.add(new VarInsnNode(Opcodes.ISTORE, len + 1));
- il.add(new VarInsnNode(Opcodes.ALOAD, 0));
- il.add(new FieldInsnNode(Opcodes.GETFIELD, descInfoPrefix + "$Proxy", "methods", "[Ljava/lang/reflect/Method;"));
- il.add(new VarInsnNode(Opcodes.ILOAD, len + 1));
- il.add(new InsnNode(Opcodes.AALOAD));
- il.add(new VarInsnNode(Opcodes.ASTORE, len + 2));//将栈顶的method存到局部变量表中
- //将参数长度推到栈顶
- il.add(new LdcInsnNode(size));
- il.add(new TypeInsnNode(Opcodes.ANEWARRAY, "java/lang/Object"));//new 出一个Object的数组
- il.add(new VarInsnNode(Opcodes.ASTORE, len + 3));//将数组存到本地变量表中
- int index = 1;
- //将参数值全都存到数组中
- for(int i = 0; i < size; i++) {
- il.add(new VarInsnNode(Opcodes.ALOAD, len + 3));//将数组推到栈顶
- il.add(new LdcInsnNode(i));//下标
- int opcode = OpcodeMap.getOpcodes(clazz[i].getName());//获得当前是什么类型的参数,使用什么样类型的指令
- //如果是long,double类型的index加2
- if(opcode == 22 || opcode == 24) {
- il.add(new VarInsnNode(opcode,index));//将long或者double参数推到栈顶
- index += 2;
- } else {
- il.add(new VarInsnNode(opcode, index));//将参数推到栈顶
- index += 1;
- }
- if(AutoPKG.auto(clazz[i].getName()) != null) {
- il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, AutoPKG.auto(clazz[i].getName()), "valueOf", AutoPKG_valueOf.auto(clazz[i].getName()), false));
- }
- il.add(new InsnNode(Opcodes.AASTORE));//将数据存到数组中
- }
- il.add(new VarInsnNode(Opcodes.ALOAD, 0));//
- il.add(new FieldInsnNode(Opcodes.GETFIELD, descInfoPrefix + "$Proxy", "logic", "L" + logicPkg + "AddLogic;"));
- il.add(new VarInsnNode(Opcodes.ALOAD, len+2));
- il.add(new VarInsnNode(Opcodes.ALOAD, len + 3));
- il.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, "" + logicPkg + "AddLogic", "addLogic", "(Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true));
- il.add(new TypeInsnNode(Opcodes.CHECKCAST, rtClazz.getName().replace(".", "/")));
- LabelNode label = new LabelNode();
- il.add(new JumpInsnNode(Opcodes.GOTO, label));
- //由于对栈图还是不太明白是啥意思,如果有知道的麻烦告知我一声
- //il.add(new FrameNode(Opcodes.F_SAME, 0, null, 0, null));
- il.add(new VarInsnNode(Opcodes.ASTORE, len + 4));
- il.add(new VarInsnNode(Opcodes.ALOAD, len + 4));
- il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V", false));
- il.add(label);
- il.add(new InsnNode(OpcodeRt.getOpcodes(rtClazz.getName())));
- methodNode.maxLocals = 5+len;
- methodNode.maxStack = 5;
- list.add(methodNode);
- count ++;
- }
- return cn;
- }
- }
二、有了生成代理的类,那么就还应该有个处理逻辑的接口
- package com.asm_core.logic;
- import java.lang.reflect.Method;
- /**
- * 这个类用来增加方法逻辑,类似于JDK代理的InvocationHandler
- * @author may
- *
- */
- public interface AddLogic {
- /**
- *
- * @param method 被代理对象的方法对象
- * @param args 被代理方法的参数
- * @throws Exception
- */
- public Object addLogic(Method method, Object[] args) throws Exception ;
- }
三、如果方法参数中存在基本类型参数,要自动打包成Object[] args,写个基本类型对应包装类助手
- package com.asm_core;
- import java.util.HashMap;
- import java.util.Map;
- public class AutoPKG {
- public static final Map<String, String> map = new HashMap<>();
- static {
- map.put("int", "java/lang/Integer");
- map.put("byte", "java/lang/Byte");
- map.put("short", "java/lang/Short");
- map.put("long", "java/lang/Long");
- map.put("boolean", "java/lang/Boolean");
- map.put("char", "java/lang/Character");
- map.put("float", "java/lang/Float");
- map.put("double", "java/lang/Double");
- }
- public static String auto(String type) {
- if(map.containsKey(type)) {
- return map.get(type);
- } else {
- return null;
- }
- }
- }
四、基本类型对应包装类的valueOf方法的描述符
- package com.asm_core;
- import java.util.HashMap;
- import java.util.Map;
- public class AutoPKG_valueOf {
- public static final Map<String, String> map = new HashMap<>();
- static {
- map.put("int", "(I)Ljava/lang/Integer;");
- map.put("byte", "(B)Ljava/lang/Byte;");
- map.put("short", "(S)Ljava/lang/Short;");
- map.put("long", "(J)Ljava/lang/Long;");
- map.put("boolean", "(Z)Ljava/lang/Boolean;");
- map.put("char", "(C)Ljava/lang/Character;");
- map.put("float", "(F)Ljava/lang/Float;");
- map.put("double", "(D)Ljava/lang/Double;");
- }
- public static String auto(String type) {
- if(map.containsKey(type)) {
- return map.get(type);
- } else {
- return null;
- }
- }
- }
五、方法描述符生成助手
- package com.asm_core;
- import java.lang.reflect.Method;
- import java.util.HashMap;
- import java.util.Map;
- /**
- * 用于生成字节码描述符的工具类
- * @author may
- *
- */
- public class DescInfo {
- public static final Map<String, String> map = new HashMap<>();
- static {
- map.put("int", "I");
- map.put("byte", "B");
- map.put("short", "S");
- map.put("long", "J");
- map.put("boolean", "Z");
- map.put("char", "C");
- map.put("float", "F");
- map.put("double", "D");
- map.put("void", "V");
- }
- public static String getDescInfo(Method method) {
- Class<?>[] pt = method.getParameterTypes();
- Class<?> rt = method.getReturnType();
- String rtStr = "V";
- if(map.containsKey(rt.getName())) {
- rtStr = map.get(rt.getName());
- } else {
- /**
- * 如果不为空,那么就是数组
- */
- Class<?> clazz= rt.getComponentType();//如果原来的rt是int[][]
- Class<?> oldClazz = clazz;//int[]
- int count = 0;
- if(oldClazz != null) {
- rtStr = "";
- while(clazz != null) {
- count ++;//
- oldClazz = clazz;
- clazz= clazz.getComponentType();
- }
- for(int i = 0; i < count; i++) {
- rtStr += "[";
- }
- if(map.containsKey(oldClazz.getName())) {
- rtStr += map.get(oldClazz.getName());
- } else {
- rtStr += "L" + oldClazz.getName().replace(".", "/") + ";";
- }
- } else {
- rtStr = "L" + rt.getName().replace(".", "/") + ";";
- }
- }
- String descInfo = "(";
- for (Class<?> class1 : pt) {
- String name = class1.getName();
- if(map.containsKey(name)) {
- descInfo += map.get(name);
- } else {
- if(class1.getComponentType() != null) {
- descInfo += class1.getName().replace(".", "/");
- } else {
- descInfo += ("L" + name.replace(".", "/") + ";");
- }
- }
- }
- descInfo += ")" + rtStr;
- return descInfo;
- }
- }
六、用于计算局部变量表的slot长度
- package com.asm_core;
- /**
- * 计算本地变量表的长度,long,double类型会占用两个slot
- * @author may
- *
- */
- public class LocalLen {
- public static int len(Class<?>[] clzz) {
- int count = 0;
- for (Class<?> class1 : clzz) {
- String str = class1.getName();
- if(str.equals("long") || str.equals("double")) {
- count += 2;
- } else {
- count ++;
- }
- }
- return count;
- }
- }
七、根据不同类型使用不同字节码指令助手类
- package com.asm_core;
- import java.util.HashMap;
- import java.util.Map;
- import org.objectweb.asm.Opcodes;
- public class OpcodeMap {
- public static Map<String, Integer> map = new HashMap<>();
- static {
- map.put("int", Opcodes.ILOAD);
- map.put("byte", Opcodes.ILOAD);
- map.put("short", Opcodes.ILOAD);
- map.put("long", Opcodes.LLOAD);
- map.put("boolean", Opcodes.ILOAD);
- map.put("char", Opcodes.ILOAD);
- map.put("float", Opcodes.FLOAD);
- map.put("double", Opcodes.DLOAD);
- }
- public static int getOpcodes(String type) {
- if(map.containsKey(type)) {
- return map.get(type);
- } else {
- return Opcodes.ALOAD;
- }
- }
- }
八、根据不同的返回类型使用不同字节码指令的助手类
- package com.asm_core;
- import java.util.HashMap;
- import java.util.Map;
- import org.objectweb.asm.Opcodes;
- public class OpcodeRt {
- public static Map<String, Integer> map = new HashMap<>();
- static {
- map.put("int", Opcodes.IRETURN);
- map.put("byte", Opcodes.IRETURN);
- map.put("short", Opcodes.IRETURN);
- map.put("long", Opcodes.LRETURN);
- map.put("boolean", Opcodes.IRETURN);
- map.put("char", Opcodes.IRETURN);
- map.put("float", Opcodes.FRETURN);
- map.put("double", Opcodes.DRETURN);
- map.put("void", Opcodes.RETURN);
- }
- public static int getOpcodes(String type) {
- if(map.containsKey(type)) {
- return map.get(type);
- } else {
- return Opcodes.ARETURN;
- }
- }
- }
九、自定义类加载器
- package com.asm_core;
- public class MyClassLoader extends ClassLoader {
- private byte[] b = null;
- public MyClassLoader(byte[] b) {
- this.b = b;
- }
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- return this.defineClass(name, b, 0, b.length);
- }
- }
十、测试类
1】实现逻辑接口
- package com.asm_core.test;
- import java.lang.reflect.Method;
- import com.asm_core.logic.AddLogic;
- /**
- * 实现了方法逻辑的类
- * @author may
- *
- */
- public class AddLogicImpl implements AddLogic {
- private Object sayHello;
- public AddLogicImpl(Object sayHello) {
- this.sayHello = sayHello;
- }
- @Override
- public Object addLogic(Method method, Object[] args) throws Exception {
- System.out.println("Hello");
- Object obj = method.invoke(sayHello, args);//我们可以在调用目标方法的周围增加逻辑
- System.out.println("baby");
- return obj;
- }
- }
2】被代理类
- package com.asm_core.test;
- import java.util.Date;
- /**
- * 需要进行代理的类
- * @author Administrator
- *
- */
- public class SayHello {
- public void sayHello(String str_1, String str_2, int age, String[] args) {
- System.out.println(str_1 + " " + str_2 + "嘿嘿:" + age);
- }
- private String l() {
- System.out.println("private String l() {");
- return "";
- }
- public int[][] tt(int age, long l, double d) {
- System.out.println("public int[][] tt(int age) {");
- return new int[][]{};
- }
- public String[][] hh(long k, double d, Double dd) {
- System.out.println("public String[][] hh(long k, double d, Double dd) {");
- return null;
- }
- public String[][] hh(short age, byte[] arg, int a, float f, char c, long l, double d, int[][] ii, String str, String[][] ss, Date date) {
- System.out.println("public String[][] hh(short age, byte[] arg, int a, float f, char c, long l, double d, int[][] ii, String str, String[][] ss, Date date) {");
- return null;
- }
- /*public String[][] hh(Long l, Double d) {
- System.out.println("public String[][] hh(short age, byte[] arg, double d) {");
- return null;
- }*/
- }
3】Test
- package com.asm_core.test;
- import java.util.Date;
- import com.asm_core.GenSubProxy;
- import com.asm_core.logic.AddLogic;
- public class Test {
- public static void main(String[] args) {
- SayHello sayHello = new SayHello();
- AddLogic logic = new AddLogicImpl(sayHello);
- GenSubProxy genSubProxy = new GenSubProxy(logic);
- Object obj = genSubProxy.genSubProxy(SayHello.class);
- SayHello sh = (SayHello) obj;
- sh.hh((byte)1, new byte[]{}, 1, 1f, 's',1, 1, new int[][]{{12}}, "", new String[][]{{"sdf","s"}}, new Date());
- sh.sayHello("sg", "srt", 234, new String[]{});
- }
- }
十一、总结
使用ASM实现动态代理,需要先学懂JVM虚拟机的字节码指令。在自己写字节码指令的时候,如果你忘记了某些代码的指令的实现,别忘记使用JDK的javap -c -v -private **.class。通过javap我们可以解决好多我们曾经感到疑惑的地方,比如为什么匿名内部类使用局部变量时这个局部变量不能变?为什么在字节码层面上不能直接将基本类型复制给Object类型?synchronized字节码中如何表述的。。。。。。
使用ASM实现动态代理的更多相关文章
- JDK、CGLIB、Javassist和ASM的动态代理使用对比
动态代理是指在运行时,动态生成代理类.正如标题所示,能够提供动态代理的方式有很多.既然选择这么有很多,那么如何选择会更好呢? 带着这个疑问,我找到了Dubbo的作者--梁飞大神的一篇文章.文章中对四种 ...
- Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...
- Java 动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...
- ASM字节码框架学习之动态代理
ASM字节码操纵框架,可以直接以二进制的形式来来修改已经存在的类或者创建新的类.ASM封装了操作字节码的大部分细节,并提供了非常方便的接口来对字节码进行操作.ASM框架是全功能的,使用ASM字节码框架 ...
- Java动态代理机制详解(类加载,JDK 和CGLIB,Javassist,ASM)
class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...
- 设计模式7---Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...
- JVM插码之四:Java动态代理机制的对比(JDK 和CGLIB,Javassist,ASM)
一.class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件, ...
- java中动态代理的实现
动态代理的实现 使用的模式:代理模式. 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问.类似租房的中介. 两种动态代理: (1)jdk动态代理,jdk动态代理是由Java内部的反射机制 ...
- Java动态代理全面分析
代理模式 解说:给某一个对象提供一个代理,并由代理对象控制对原对象的引用: 代理模式需要以下几个角色: 1 主题:规定代理类和真实对象共同对外暴露的接口: 2 代理类:专门代理真实对象的类: 3 ...
随机推荐
- Spring5源码深度分析(二)之理解@Conditional,@Import注解
代码地址: 1.源码分析二主要分析的内容 1.使用@Condition多条件注册bean对象2.@Import注解快速注入第三方bean对象3.@EnableXXXX 开启原理4.基于ImportBe ...
- java编程思想之面向对象
面向对象和面向过程 面向对象(Object Oriented),简称OO,是软件开发方法的一种,我们都知道java是基于面向对象开发的,但是说到面向对象,我们不得不提一提面向过程开发,面向过程,又称结 ...
- 第三章: Expressions and Flow Control
第三章: Expressions and Flow Control一:局部变量和实例变量定义变量是指设定变量的数据类型和变量的名字,Java语言要求变量遵循先定义,再初始化,然后使用的规则.作用域:指 ...
- Django生成PDF显示在网页上以及解决中文显示乱码的问题
项目地址:https://github.com/PythonerKK/django-generate-pdf/tree/master 这个demo实现了通过用户输入自己的个人信息生成一份简历pdf,来 ...
- django-haystack+whoosh+jieba实现中文全文搜索
先上效果图 附上个人网站:https://liyuankun.cn 安装依赖库 注意:这里我们不安装django-haystack,因为要添加中文分词的功能很麻烦,所以我直接集成了一个中文的djang ...
- 不懂数据库索引的底层原理?那是因为你心里没点b树
本文在个人技术博客不同步发布,详情可用力戳 亦可扫描屏幕右侧二维码关注个人公众号,公众号内有个人联系方式,等你来撩... 前几天下班回到家后正在处理一个白天没解决的bug,厕所突然传来对象的声音: ...
- leadcode的Hot100系列--104. 二叉树的最大深度
依然使用递归思想. 思路: 1.树的深度 = max (左子树深度,右子树深度)+ 1 . ------> 这里的加1是表示自己节点深度为1. 2.如果当前节点为null,则说明它的左右子树深度 ...
- Linux部署项目常用命令
前言:一般项目都会使用阿里云等服务器作为云服务器.此时必不可免会使用到一系列常用的命令.这里我整合一下常用的命令 1.一般链接阿里云服务器常用的的是xshell跟xftp. 下载路径:https:// ...
- 基于SpringBoot的Web API快速开发基础框架
其实还是很因为懒,才会有这个案例项目的产生,每次开启一个终端的小服务都要整理一次框架,造成重复的.不必要的.缺乏创造性的劳动,SO,本着可以用.用着简单的原则上传代码到Github,希望有需要的朋友直 ...
- shell的用处到底大不大
我曾在智联招聘等网站上搜寻有关shell脚本员的职位,与C++.JAVA等热门语言相比,冷清很多.看上去似乎招shell程序员的公司比较少.是不是公司不重视或者是很少用到shell这个东东呢? ...