博客内容来源于 刘欣老师的课程,刘欣老师的公众号 码农翻身

博客内容来源于 Java虚拟机规范(JavaSE7)

博客内容的源码 https://gitee.com/zumengjie/litejvm

阅读此博客请配合源码食用。

ClassLoader

ClassLoader就是根据类名去classpath路径上找到Class文件然后解析Class文件形成Class类对象。

package com.datang.litejvm.loader;

import com.datang.litejvm.clz.Class;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils; import java.io.*;
import java.util.ArrayList;
import java.util.List; /**
* @author: 顶风少年
* @Description: 类加载器
* @date: 13:54 2022/6/10
**/
public class ClassLoader { /**
* @author: 顶风少年
* @Description: 存放环境变量
* @date: 21:58 2022/6/8
**/
private List<String> clzPaths = new ArrayList<String>(); /**
* @author: 顶风少年
* @Description: 添加环境变量 classPath
* @date: 21:53 2022/6/8
**/
public void addClassPath(String path) {
if (this.clzPaths.contains(path)) {
return;
}
this.clzPaths.add(path);
} /**
* @author: 顶风少年
* @Description: 返回classPath, 多个中间使用 ; 拼接
* @date: 21:53 2022/6/8
**/
public String getClassPath() {
return StringUtils.join(this.clzPaths, ";");
} /**
* @author: 顶风少年
* @Description: 将classpath和className拼接获取类路径
* @date: 21:53 2022/6/8
**/
public byte[] readBinaryCode(String className) {
className = className.replace('.', File.separatorChar) + ".class";
for (String path : this.clzPaths) {
String clzFileName = path + File.separatorChar + className;
byte[] codes = loadClassFile(clzFileName);
if (codes != null) {
return codes;
}
}
return null;
} /**
* @author: 顶风少年
* @Description: 根据类路径读取class文件
* @date: 21:54 2022/6/8
**/
private byte[] loadClassFile(String clzFileName) {
File f = new File(clzFileName);
try {
return IOUtils.toByteArray(new FileInputStream(f));
} catch (IOException e) {
e.printStackTrace();
return null;
}
} /**
* @author: 顶风少年
* @Description: 解析class文件形成Class对象,在这里是形成ClassFile对象
* @date: 13:55 2022/6/10
**/
public Class loadClass(String className) {
byte[] codes = this.readBinaryCode(className);
ClassParser parser = new ClassParser();
return parser.parse(codes);
}
}

Class

首先我们对一个已经编译完成的class文件进行解析,解析的过程就是读取每一个字节,然后了解其结构以及含义,将其解析为一个Class类。使用javap -v xxx.class命令可以查看该class的结构。

Class字节码文件的内容十分紧凑,首先是魔数,小版本号,大版本号,常量池,类访问权限,类,父类,父接口,字段列表,方法列表。常量池占了大部分的字节码文件,其余的部分很多都会引用常量池项。

解析Class文件就是读取固定长度的字节,魔数为4个字节,小版本号2个字节,大版本号两个字节。接下来的常量池稍微复杂,两个字节标记了常量池项的总个数,其中每一个常量池项都有对应的数据结构。需要先读取1个字节判断它的结构,例如tag是1则它是一个CONSTANT_Utf8_info,这是一个最简单的结构,2个字节的长度表示后续字符串的长度,然后再读取对应长度的字节数。常量池项是可以引用常量池项的,例如第7项是一个CONSTANT_Class_info这个结构包含了2个字节的index它指向常量池的第40项目,是Class对应的类名。解析常量池时需要根据tag判断常量池项是什么结构,然后再根据具体的结构读取字节。

接下来是读取2个字节类的访问权限,无论是类,字段,方法都有访问权限,注意访问权限可能有多个,例如类可以是 <public> <final>

接下来是2个字节的类名,2个字节的父类名,2个字节的接口个数,每个接口名也是2个字节。

之后是类成员变量也就是字段,2个字节的成员个数,每个字段里包含2个字节的访问权限,2个字节的字段名,2个字节的字段类型,两个字节的的属性个数,这个属性也是有多种结构的,方法里也有,放到方法里说。

字节码文件的最后一部分是方法,2个字节的方法个数,每个方法包含2个字节的访问权限,2个字节的方法名,2个字节的方法签名(入参和返回值)。2个字节的属性个数,每个属性中包含,2个字节的属性名称。在方法中只有一个属性就是Code。

jvm定义的属性有以下结构,字段的属性也在其中。

首先是2个字节的属性名称,根据属性名判断属性。每个属性都有4个字节的属性长度,它标记了接下来的属性内容的字节数。Code属性中包含2个字节的栈深度,2个字节的局部变量表,4个字节的字节码长度,字节码长度为N则表示有N个字节的字节码指令,每个指令1个字节。对于字节码指令需要展开说,每个字节码指令根据其含义的不同可能带有不同的参数,例如bb含义为new对象,bb的后边有2个字节是它的参数,这两个参数指向了常量池中的常量项是需要new的对象类名。

在字节码指令后是2个字节的异常长度,异常的相关的内容,我本次没有解析,只是手动跳过了异常。

Code属性中还包含了2个其他属性。LineNumberTable和LocalVariableTable其一是行号相关,其二是参数列表。

2个字节区分属性的名称,4个字节表示属性内容的长度。LineNumberTable有2个字节的行数,每一行中包含2个字节的起始位,2个字节的行号。

LocalVariableTable有2个字节的参数个数,每个参数有2个字节的起始位,2个字节的长度,2个字节的属性名,2个字节的下标。

以上是Class文件中可以解析出来的内容,当然根据Class的复杂程度,解析出来的内容不同,我这里是最基本的Class属性,将其解析完毕后,形成一个Class类。

package com.datang.litejvm.clz;

import com.datang.litejvm.constant.ConstantClassInfo;
import com.datang.litejvm.constant.ConstantPool;
import com.datang.litejvm.constant.ConstantUTF8Info; import java.util.Iterator;
import java.util.List; /**
* @author: 顶风少年
* @Description: Class类
* @date: 13:55 2022/6/10
**/
public class Class { //魔数
private String magic; //小版本号
private int minorVersion; //大版本号
private int majorVersion; //常量池
private ConstantPool pool; //访问标记
private ClassAccessFlag classAccessFlag; //类
private int thisClassIndex; //父类
private int superClassIndex; private String superClassName; //接口引用
private List<Integer> interfaceIndexList; //属性列表
private List<Field> fieldList; //方法
private List<Method> methodList; //------------------------------------------------------- /**
* @author: 顶风少年
* @Description: 魔数
* @date: 11:39 2022/6/10
**/
public String getMagic() {
return magic;
} /**
* @author: 顶风少年
* @Description: 魔数
* @date: 11:39 2022/6/10
**/
public void setMagic(String magic) {
this.magic = magic;
} /**
* @author: 顶风少年
* @Description: 小版本号
* @date: 11:28 2022/6/10
**/
public int getMinorVersion() {
return minorVersion;
} /**
* @author: 顶风少年
* @Description: 小版本号
* @date: 11:28 2022/6/10
**/
public void setMinorVersion(int minorVersion) {
this.minorVersion = minorVersion;
} /**
* @author: 顶风少年
* @Description: 大版本号
* @date: 11:28 2022/6/10
**/
public int getMajorVersion() {
return majorVersion;
} /**
* @author: 顶风少年
* @Description: 大版本号
* @date: 11:28 2022/6/10
**/
public void setMajorVersion(int majorVersion) {
this.majorVersion = majorVersion;
} /**
* @author: 顶风少年
* @Description: 常量池
* @date: 11:28 2022/6/10
**/
public ConstantPool getConstantPool() {
return pool;
} /**
* @author: 顶风少年
* @Description: 常量池
* @date: 11:28 2022/6/10
**/
public void setConstPool(ConstantPool pool) {
this.pool = pool;
} /**
* @author: 顶风少年
* @Description: 访问标记
* @date: 17:18 2022/6/10
**/
public ClassAccessFlag getAccessFlag() {
return classAccessFlag;
} /**
* @author: 顶风少年
* @Description: 访问标记
* @date: 17:18 2022/6/10
**/
public void setAccessFlag(ClassAccessFlag classAccessFlag) {
this.classAccessFlag = classAccessFlag;
} /**
* @author: 顶风少年
* @Description: 类
* @date: 17:18 2022/6/10
**/
public int getThisClassIndex() {
return thisClassIndex;
} public void setThisClassIndex(int thisClassIndex) {
this.thisClassIndex = thisClassIndex;
} /**
* @author: 顶风少年
* @Description: 父类
* @date: 17:18 2022/6/10
**/
public int getSuperClassIndex() {
return superClassIndex;
} public void setSuperClassIndex(int superClassIndex) {
this.superClassIndex = superClassIndex;
ConstantClassInfo constantClassInfo = (ConstantClassInfo) pool.getConstantInfo(superClassIndex);
this.superClassName = constantClassInfo.getClassName();
} public String getSuperClassName() {
return superClassName;
} /**
* @author: 顶风少年
* @Description: 接口
* @date: 17:18 2022/6/10
**/
public List<Integer> getInterfaceIndexList() {
return interfaceIndexList;
} public void setInterfaceIndexList(List<Integer> interfaceIndexList) {
this.interfaceIndexList = interfaceIndexList;
} /**
* @author: 顶风少年
* @Description: 属性列表
* @date: 11:22 2022/6/12
**/
public List<Field> getFieldList() {
return fieldList;
} /**
* @author: 顶风少年
* @Description: 属性列表
* @date: 11:22 2022/6/12
**/
public void setFieldList(List<Field> fieldList) {
this.fieldList = fieldList;
} /**
* @author: 顶风少年
* @Description: 方法
* @date: 18:31 2022/6/12
**/
public List<Method> getMethodList() {
return methodList;
} /**
* @author: 顶风少年
* @Description: 方法
* @date: 18:31 2022/6/12
**/
public void setMethodList(List<Method> methodList) {
this.methodList = methodList;
} /**
* @author: 顶风少年
* @Description: 根据方法名和方法签名查询方法
* @date: 10:47 2022/6/16
**/
public Method getMethod(String methodName, String paramAndResultType) {
Method rMethod = null;
Iterator<Method> iter = methodList.iterator();
while (iter.hasNext()) {
Method method = iter.next();
int nameIndex = method.getNameIndex();
int descriptorIndex = method.getDescriptorIndex(); ConstantUTF8Info nameInfo = (ConstantUTF8Info) pool.getConstantInfo(nameIndex);
ConstantUTF8Info descriptorInfo = (ConstantUTF8Info) pool.getConstantInfo(descriptorIndex); if (nameInfo.getBytes().equals(methodName) && descriptorInfo.getBytes().equals(paramAndResultType)) {
rMethod = method;
}
}
return rMethod;
} /**
* @author: 顶风少年
* @Description: 查询main方法
* @date: 10:36 2022/6/16
**/
public Method getMainMethod() {
return getMethod("main", "([Ljava/lang/String;)V");
}
}

MethodArea

方法区中有个Map它的key是class类名,value是Class对象。使用ClassLoader解析后的Class对象都存在这里。

package com.datang.litejvm.engin;

import com.datang.litejvm.clz.Field;
import com.datang.litejvm.clz.Method;
import com.datang.litejvm.constant.ConstantFieldRefInfo;
import com.datang.litejvm.constant.ConstantMethodRefInfo;
import com.datang.litejvm.loader.ClassLoader;
import com.datang.litejvm.clz.Class;
import java.util.HashMap;
import java.util.Map; /**
* @author: 顶风少年
* @Description: 方法区
* @date: 10:49 2022/6/16
**/
public class MethodArea { public static final MethodArea instance = new MethodArea(); /**
* 注意:我们做了极大的简化, ClassLoader 只有一个, 实际JVM中的ClassLoader,是一个双亲委托的模型
*/ private ClassLoader classLoader = null; /**
* @author: 顶风少年
* @Description: 存放所有的Class
* @date: 10:39 2022/6/16
**/
Map<String, Class> map = new HashMap<String, Class>(); private MethodArea(){
} /**
* @author: 顶风少年
* @Description: 单例,获取常量池
* @date: 10:39 2022/6/16
**/
public static MethodArea getInstance(){
return instance;
} public void setClassFileLoader(ClassLoader clzLoader){
this.classLoader = clzLoader;
} /**
* @author: 顶风少年
* @Description: 获取main方法
* @date: 10:39 2022/6/16
**/
public Method getMainMethod(String className){
Class clz = this.findClass(className);
return clz.getMainMethod();
} /**
* @author: 顶风少年
* @Description: 从指定class中 根据名称获取方法
* @date: 22:23 2022/6/16
**/
public Method getMethod(ConstantMethodRefInfo constantMethodRefInfo){
Class aClass = findClass(constantMethodRefInfo.getClassName());
Method method = aClass.getMethod(constantMethodRefInfo.getMethodName(), constantMethodRefInfo.getParamAndResult());
return method;
} /**
* @author: 顶风少年
* @Description: 创建Class
* @date: 10:38 2022/6/16
**/
public Class findClass(String className){
if(map.get(className) != null){
return map.get(className);
}
// 看来该class 文件还没有load过
Class aClass = this.classLoader.loadClass(className);
map.put(className, aClass);
return aClass;
}
}

ExecutorEngine

执行引擎中主要有一个栈结构,栈中的每个元素是StackFrame栈帧。执行引擎执行第一步将main方法压入栈中,然后就是执行栈帧。每个栈帧其实就是一个method当方法执行结束后返回ExecutionResult里边封装了该方法是暂存运行其他method还是方法运行结束出栈。如果是运行其他的method则将跳转方法压入栈中,并且传递参数,如果是方法执行结束则将当前方法出栈。执行引擎就是在不断的判断栈内是否还有元素,如果栈为空则表示当前程序执行结束。

package com.datang.litejvm.engin;

import com.datang.litejvm.clz.Method;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack; /**
* @author: 顶风少年
* @Description: 执行引擎
* @date: 15:19 2022/6/16
**/
public class ExecutorEngine { /**
* @author: 顶风少年
* @Description: 栈
* @date: 15:41 2022/6/16
**/
private Stack<StackFrame> stack = new Stack<StackFrame>(); public void execute(Method mainMethod) {
//创建main栈帧
StackFrame stackFrame = StackFrame.create(mainMethod);
//入栈
stack.push(stackFrame);
//接下来就是对栈做操作了,入栈,出栈
while (!stack.isEmpty()) {
//拿到栈顶栈帧
StackFrame frame = stack.peek();
//执行栈帧
ExecutionResult result = frame.execute();
//暂停,并运行新的栈帧.有函数调用了
if (result.isPauseAndRunNewFrame()) {
//下一个method
Method nextMethod = result.getNextMethod();
//形成新的栈帧
StackFrame nextFrame = StackFrame.create(nextMethod);
nextFrame.setCallerFrame(frame);
setupFunctionCallParams(frame, nextFrame);
//将新的栈帧也入栈
stack.push(nextFrame);
} else {
//出栈
stack.pop();
}
}
} /**
* @author: 顶风少年
* @Description: 给下个调用方法设置参数
* @date: 16:07 2022/6/16
**/
private void setupFunctionCallParams(StackFrame currentFrame, StackFrame nextFrame) {
Method nextMethod = nextFrame.getMethod();
//获取参数列表
List<String> parameterList = nextMethod.getParameterList();
List<JavaObject> values = new ArrayList<>();
//要添加 this
int paramNum = parameterList.size() + 1; while (paramNum > 0) {
values.add(currentFrame.getOperandStack().pop());
paramNum--;
} List<JavaObject> params = new ArrayList<>();
for (int i = values.size() - 1; i >= 0; i--) {
params.add(values.get(i));
}
//设置局部变量表
nextFrame.setLocalVariableTable(params);
} }

StackFrame

每个方法入栈都会形成一个StackFrame,栈帧中包含两个数据结构。局部变量表和操作数栈,局部变量表是用来存放方法的入参,方法内的计算结果,操作数栈则是真正运算的地方,我们经常说的 1 + 2 = 3 的操作其实是在操作数栈进行的,先将 1 和 2 压入操作数栈,将 1 和 2 出栈进行运算最后得出的 3 将其再次压入操作数栈。StackFrame执行时就是从method总取出Code属性中的操作指令,一条一条的执行。每条指令执行结束后会对ExecutionResult进行设置。

package com.datang.litejvm.engin;

import com.datang.litejvm.clz.Method;
import com.datang.litejvm.cmd.ByteCodeCommand; import java.util.ArrayList;
import java.util.List;
import java.util.Stack; /**
* @author: 顶风少年
* @Description: 函数栈帧
* @date: 15:41 2022/6/16
**/
public class StackFrame {
//局部变量表
private List<JavaObject> localVariableTable = new ArrayList<JavaObject>();
//操作数栈
private Stack<JavaObject> operandStack = new Stack<JavaObject>(); //字节码指令偏移量,指向下一个操作指令
int index = 0; //当前方法
private Method m = null; //上一个函数栈帧
private StackFrame callerFrame = null; private StackFrame(Method m) {
this.m = m;
} //创建函数栈帧
public static StackFrame create(Method m) {
StackFrame frame = new StackFrame(m);
return frame;
} /**
* @author: 顶风少年
* @Description: 上一个函数栈帧
* @date: 16:44 2022/6/16
**/
public StackFrame getCallerFrame() {
return callerFrame;
} public void setCallerFrame(StackFrame callerFrame) {
this.callerFrame = callerFrame;
} /**
* @author: 顶风少年
* @Description: 栈帧所属方法
* @date: 16:45 2022/6/16
**/
public Method getMethod() {
return m;
} /**
* @author: 顶风少年
* @Description: 设置局部变量表
* @date: 16:40 2022/6/16
**/
public void setLocalVariableTable(List<JavaObject> values) {
this.localVariableTable = values;
} /**
* @author: 顶风少年
* @Description: 获取局部变量表中的某个变量
* @date: 10:22 2022/6/17
**/
public JavaObject getLocalVariableValue(int index) {
return this.localVariableTable.get(index);
} /**
* @author: 顶风少年
* @Description: 向局部变量表设置值
* @date: 10:38 2022/6/17
**/
public void setLocalVariableValue(int index, JavaObject jo) {
//问题: 为什么要这么做??
if (this.localVariableTable.size() - 1 < index) {
for (int i = this.localVariableTable.size(); i <= index; i++) {
this.localVariableTable.add(null);
}
}
this.localVariableTable.set(index, jo);
} /**
* @author: 顶风少年
* @Description: 执行方法, 就是执行字节码指令
* @date: 17:11 2022/6/16
**/
public ExecutionResult execute() {
List<ByteCodeCommand> cmds = m.getCodeAttr().getCmds();
while (index < cmds.size()) {
//执行结果
ExecutionResult result = new ExecutionResult();
//下一条字节码指令
ByteCodeCommand cmd = cmds.get(index);
System.out.println(cmd.toString());
//执行
cmd.execute(this, result);
//运行下一条
if (result.isRunNextCmd()) {
index++;
} else if (result.isExitCurrentFrame()) {
//退出当前栈帧,return 剩余的栈帧不执行了
return result;
} else if (result.isPauseAndRunNewFrame()) {
//暂停当前栈帧,执行新的函数栈帧
index++;
return result;
} else if (result.isJump()) {
//跳转指令,跳转到下一个字节码指令
int offset = result.getNextCmdOffset();
//设置下一个指令的偏移量
this.index = getNextCommandIndex(offset);
} else {
index++;
}
}
//如果循环走完了,说明没有任何的跳转,停止,表示当前StackFrame的指令全部执行完毕,可以退出了
ExecutionResult result = new ExecutionResult();
result.setNextAction(ExecutionResult.EXIT_CURRENT_FRAME);
return result;
} /**
* @author: 顶风少年
* @Description: 根据偏移量查询字节码指令
* @date: 17:48 2022/6/16
**/
public int getNextCommandIndex(int offset) {
List<ByteCodeCommand> cmds = m.getCodeAttr().getCmds();
for (int i = 0; i < cmds.size(); i++) {
if (cmds.get(i).getOffset() == offset) {
return i;
}
}
throw new RuntimeException("Can't find next command");
} /**
* @author: 顶风少年
* @Description: 获取操作数栈
* @date: 19:04 2022/6/16
**/
public Stack<JavaObject> getOperandStack() {
return this.operandStack;
} }

ExecutionResult

操作指令执行结束后会设置ExecutionResult,这个类标记了当前字节码执行后的行为,默认的是RUN_NEXT_CMD继续执行下一条字节码指令,也可能是JUMP跳转到指定的指令号,EXIT_CURRENT_FRAME停止执行,PAUSE_AND_RUN_NEW_FRAME暂存当前栈帧,执行新的栈帧。如果是JUMP或RUN_NEXT_CMD则当前栈帧继续执行。如果是EXIT_CURRENT_FRAME和PAUSE_AND_RUN_NEW_FRAME就要返回到执行引擎层。

package com.datang.litejvm.engin;

import com.datang.litejvm.clz.Method;

/**
* @author: 顶风少年
* @Description: 执行结果
* @date: 17:29 2022/6/16
**/
public class ExecutionResult {
//默认值 执行下一条指令
public static final int RUN_NEXT_CMD = 1;
//跳转指令
public static final int JUMP = 2;
//退出当前栈帧,剩余指令不执行了
public static final int EXIT_CURRENT_FRAME = 3;
//暂停当前栈帧,执行新的函数栈帧
public static final int PAUSE_AND_RUN_NEW_FRAME = 4; //下一次的行为
private int nextAction = RUN_NEXT_CMD; //如果是跳转指令 JUMP ,则会记录下一个指令偏移量
private int nextCmdOffset = 0; //如果是执行新的栈帧 EXIT_CURRENT_FRAME 则需要设置栈帧对应的方法
private Method nextMethod; /**
* @author: 顶风少年
* @Description: 获取下一个执行的方法
* @date: 17:46 2022/6/16
**/
public Method getNextMethod() {
return nextMethod;
} public void setNextMethod(Method nextMethod) {
this.nextMethod = nextMethod;
} /**
* @author: 顶风少年
* @Description: 设置字节码执行后的行为, 默认为 RUN_NEXT_CMD 执行下一条指令
* @date: 17:31 2022/6/16
**/
public void setNextAction(int action) {
this.nextAction = action;
} /**
* @author: 顶风少年
* @Description: 下一个字节码指令偏移量
* @date: 17:43 2022/6/16
**/
public int getNextCmdOffset() {
return nextCmdOffset;
} public void setNextCmdOffset(int nextCmdOffset) {
this.nextCmdOffset = nextCmdOffset;
} /**
* @author: 顶风少年
* @Description: 执行结果
* @date: 17:44 2022/6/16
**/
public boolean isPauseAndRunNewFrame() {
return this.nextAction == PAUSE_AND_RUN_NEW_FRAME;
} public boolean isExitCurrentFrame() {
return this.nextAction == EXIT_CURRENT_FRAME;
} public boolean isRunNextCmd() {
return this.nextAction == RUN_NEXT_CMD;
} public boolean isJump() {
return this.nextAction == JUMP;
}
}

Heap

堆这个对象用来模拟创建不同类型的对象,当前可以创建String,int,float,Object。如果创建的Object则还可以给Object设置属性。

package com.datang.litejvm.engin;

/**
* @author: 顶风少年
* @Description: 堆,可以有多种类型,对象,字符串,int float
* @date: 21:02 2022/6/16
**/
public class Heap { /**
* 没有实现垃圾回收, 所以对于下面新创建的对象, 并没有记录到一个数据结构当中
*/ private static Heap instance = new Heap();
private Heap() {
}
public static Heap getInstance(){
return instance;
} public JavaObject newObject(String clzName){
JavaObject jo = new JavaObject(JavaObject.OBJECT);
jo.setClassName(clzName);
return jo;
} public JavaObject newString(String value){
JavaObject jo = new JavaObject(JavaObject.STRING);
jo.setStringValue(value);
return jo;
} public JavaObject newFloat(float value){
JavaObject jo = new JavaObject(JavaObject.FLOAT);
jo.setFloatValue(value);
return jo;
}
public JavaObject newInt(int value){
JavaObject jo = new JavaObject(JavaObject.INT);
jo.setIntValue(value);
return jo;
} }

jvm造轮子的更多相关文章

  1. 别在重复造轮子了,几个值得应用到项目中的 Java 开源库送给你

    我是风筝,公众号「古时的风筝」.文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面.公众号回复『666』获取高清大图. 风筝我作为一个野路子开发者,直到 ...

  2. 避免重复造轮子的UI自动化测试框架开发

    一懒起来就好久没更新文章了,其实懒也还是因为忙,今年上半年的加班赶上了去年一年的加班,加班不息啊,好了吐槽完就写写一直打算继续的自动化开发 目前各种UI测试框架层出不穷,但是万变不离其宗,驱动PC浏览 ...

  3. 【疯狂造轮子-iOS】JSON转Model系列之二

    [疯狂造轮子-iOS]JSON转Model系列之二 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇<[疯狂造轮子-iOS]JSON转Model系列之一> ...

  4. 【疯狂造轮子-iOS】JSON转Model系列之一

    [疯狂造轮子-iOS]JSON转Model系列之一 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 之前一直看别人的源码,虽然对自己提升比较大,但毕竟不是自己写的,很容易遗 ...

  5. h5engine造轮子

    基于学习的造轮子,这是一个最简单,最基础的一个canvas渲染引擎,通过这个引擎架构,可以很快的学习canvas渲染模式! 地址:https://github.com/RichLiu1023/h5en ...

  6. 我为什么还要造轮子?欠踹?Monk.UI表单美化插件诞生记!

    背景 目前市场上有很多表单美化的UI,做的都挺不错,但是他们都有一个共同点,那就是90%以上都是前端工程师开发的,导致我们引入这些UI的时候,很难和程序绑定.所以作为程序员的我,下了一个决定!我要自己 ...

  7. 「iOS造轮子」之UIButton 用Block响应事件

    俗语说 一个不懒的程序员不是好程序员 造轮子,也只是为了以后更好的coding. coding,简易明了的代码更是所有程序员都希望看到的 无论是看自己的代码,还是接手别人的代码 都希望一看都知道这代码 ...

  8. 重复造轮子感悟 – XLinq性能提升心得

    曾经的两座大山 1.EF 刚接触linq那段时间,感觉这家伙好神奇,语法好优美,好厉害.后来经历了EF一些不如意的地方,就想去弥补,既然想弥补,就必须去了解原理.最开始甚至很长一段时间都搞不懂IQue ...

  9. GitHub Android 最火开源项目Top20 GitHub 上的开源项目不胜枚举,越来越多的开源项目正在迁移到GitHub平台上。基于不要重复造轮子的原则,了解当下比较流行的Android与iOS开源项目很是必要。利用这些项目,有时能够让你达到事半功倍的效果。

    1. ActionBarSherlock(推荐) ActionBarSherlock应该算得上是GitHub上最火的Android开源项目了,它是一个独立的库,通过一个API和主题,开发者就可以很方便 ...

  10. 第27篇 重复造轮子---模拟IIS服务器

    在写程序的时候,重复造轮子是程序员的一个大忌,很多人对重复造轮子持有反对的态度,但是我觉得这个造轮子的过程,是对于现有的知识的一个深入的探索的过程,虽然我们不可能把轮子造的那么的完善,对于现在有的东西 ...

随机推荐

  1. zeppelin-0.6.0安装配置

    从http://zeppelin.apache.org/download.html 下载 zeppelin-0.6.0-bin-all.tgz 解压 修改zeppelin-site.xml,配置端口 ...

  2. 如何闪开安装VS2013必须要有安装IE10的限制

    把下面这一段文字,储存成.bat档案,然后右击以管理员角色去执行它.@ECHO OFF :IE10HACK REG ADD "HKLM\SOFTWARE\Wow6432Node\Micros ...

  3. [转]vim常用命令

    [转]vim常用命令 http://www.cnblogs.com/sunyubo/archive/2010/01/06/2282198.html http://blog.csdn.net/wooin ...

  4. (转)Mysql数据库读写分离配置

    环境模拟 实现读写分离 减轻数据库的负荷 主服务器 master 10.0.0.12从服务器 slave 10.0.0.66 ------------------------------------- ...

  5. poj1256(全排列stl)

    #include<stdio.h>#include<string.h>#include<algorithm>using namespace std;bool cmp ...

  6. 关于Android中为什么主线程不会因为Looper.loop()里的死循环卡死?引发的思考,事实可能不是一个 epoll 那么 简单。

    ( 转载请务必标明出处:http://www.cnblogs.com/linguanh/, 本文出自:[林冠宏(指尖下的幽灵)的博客]) 前序 本文将会把一下三个问题阐述清楚以及一个网上的普遍观点的补 ...

  7. .NET Core 源码导航(按程序集链接)

    System.*.dll/dotnetfx mscorlib.dll/dotnetclr Microsoft.AspNetCore.dll Microsoft.EntityFrameworkCore. ...

  8. Java网络编程的基本网络概念

    前言 自己网络这方面的知识很是薄弱,每次面试被问到这部分都会卡壳,所以很尴尬,然后最近也是有些时间了,就赶紧把自己的不足补充一下.虽然最近也在看设计模式,但是总看设计模式也容易烦,所以就并行学习,看看 ...

  9. 基于tensorflow的逻辑分类

    #!/usr/local/bin/python3 ##ljj [2] ##logic classify model import tensorflow as tf import matplotlib. ...

  10. react 全局公共组件-----动态弹窗 (dialog)

    react 的时候,总是会用到弹窗,并且各种各样的,一般来说,组件层级嵌套之后,就会出现 z-index层级覆盖的问题 这个时候,就需要一个公共的弹出层,然后我们将需要展示的组件,放到弹出层里面 下面 ...