一、前言

框架代码其实也没那么难,大家不要看着源码就害怕,现在去看 Tomcat 3.0的代码,保证还是看得懂一半,照着撸一遍基本上很多问题都能搞定了。这次我们就模拟 Tomcat 中的 Digester(xml解析工具)来仿写一个相当简易的版本。上一篇说了如何利用 sax 模型来解析 xml,但是,该程序还有相当多的优化空间。这一篇,我们一起将程序进行一些优化。之前的版本有什么问题呢?请看:

     @Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
System.out.println("startElement: " + qName + " It's the " + eventOrderCounter.getAndIncrement() + " one"); if ("Coder".equals(qName)) {
6
7 Coder coder = new Coder(); setProperties(attributes,coder); stack.push(coder);
} else if ("Girl".equals(qName)) { Girl girl = new Girl();
setProperties(attributes, girl); Coder coder = (Coder) stack.peek();
coder.setGirl(girl);
}
}

上图为当前xml handler的代码,注意第5-6行,我们这里写死了,当元素为 Coder 的时候,就生成 Coder类的对象。那要是,我现在不想生成 Coder 类了,就得修改这里的程序。这样还是太不灵活了,所以我们考虑将 这个提取到 xml 中来。

 <?xml version='1.0' encoding='utf-8'?>

 <Coder name="xiaoming" sex="man" love="girl" class="com.coder.Coder">
<Girl class = "com.coder.Girl" name="Catalina" height="170" breast="C++" legLength="150" isPregnant="true" />
</Coder>

如上图所示,我们将其类型的信息提取到了 元素的class 属性中,以后假设想换个类型,那就很简单了,只要修改 class 属性即可。

二、大体思路与具体实现

1、Tomcat源码中的实现思路

我们先截取了 Tomcat 中的 server.xml 一句:

<Server port="8005" shutdown="SHUTDOWN">

Tomcat 源码中负责定义如何解析上面这句的代码如下:

          //source:org.apache.catalina.startup.Catalina#createStartDigester

          digester.addObjectCreate("Server",
4 "org.apache.catalina.core.StandardServer",
5 "className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");

我简单解释下,这里没有进行真实解析,只是定义解析规则,真实的解析发生在Digester.parse()方法中,彼时会回调这里定义的规则。 第三行表示,新增一条"Server"元素的规则,类型为ObjectCreate,这条规则,在遇到 "Server" 元素时,获取 className 属性的值,如果有的话,即创建指定类型的对象,否则默认创建 org.apache.catalina. core.StandardServer 类型的对象,并保存到 digester 对象的内部栈中; 第6行表示,新增一条 "Server" 元素的规则,类型为 SetAllPropertiesRule,这条规则,会从 digester 当前的栈中,取出栈顶对象,并利用反射,来将 xml 元素中的 attribute 设置到该对象中。

2、仿写开始:自定义 rule 接口及实现

package com.coder.rule;

import org.xml.sax.Attributes;

public interface ParseRule {
/**
* 遇到xml元素的开始标记时,调用该方法。
* @param attributes 元素中的属性
*/
void startElement(Attributes attributes); void body(String body); void endElement();
}

我们先定义了一个解析规则,规则中有三个方法,分别在遇到 xml元素 的开始标记、内容、结束标记时调用。接下来,我们再定义一个规则:

 package com.coder.rule;

 import com.coder.GirlFriendHandler;
import com.coder.GirlFriendHandlerVersion2;
import org.xml.sax.Attributes; /**
* desc:
*
* @author : caokunliang
* creat_date: 2019/7/1 0001
* creat_time: 11:20
**/
public class CreateObjectParseRule implements ParseRule {
private String attributeNameForObjectType; private ClassLoader loader; private GirlFriendHandlerVersion2 girlFriendHandler; public CreateObjectParseRule(String attributeNameForObjectType, GirlFriendHandlerVersion2 girlFriendHandler) {
this.attributeNameForObjectType = attributeNameForObjectType;
this.girlFriendHandler = girlFriendHandler;
//默认使用当前线程类加载器
loader = Thread.currentThread().getContextClassLoader();
} @Override
public void startElement(Attributes attributes) {
String clazzStr = attributes.getValue(attributeNameForObjectType);
if (clazzStr == null) {
throw new RuntimeException("element must has attribute :" + attributeNameForObjectType);
} Class<?> clazz;
try {
clazz = loader.loadClass(clazzStr);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("class not found:" + clazzStr);
} Object o;
try {
o = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException("new instance failed.");
} girlFriendHandler.push(o);
} @Override
public void body(String body) { } @Override
public void endElement() { }
}

重点关注两个方法,一个是构造器,构造器两个参数,一个 attributeNameForObjectType 意思是要从xml元素的那个 属性中获取 对象类型,一个 girlFriendHandler 其实就是我们的解析器handler。

然后要关注的方法是,startElement。32行,根据构造器中的attributeNameForObjectType 获取对应的对象类型,然后利用类加载器来加载该类,获取到class后,利用反射生成对象,并压入 handler的栈中。

接下来,我们介绍另一个 rule:

 package com.coder.rule;

 import com.coder.GirlFriendHandlerVersion2;
import com.coder.TwoTuple;
import org.xml.sax.Attributes; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects; public class SetPropertiesParseRule implements ParseRule {
private GirlFriendHandlerVersion2 girlFriendHandler; public SetPropertiesParseRule(GirlFriendHandlerVersion2 girlFriendHandler) {
this.girlFriendHandler = girlFriendHandler;
} @Override
public void startElement(Attributes attributes) {
23 Object object = girlFriendHandler.peek();
24
25 setProperties(attributes,object);
} @Override
public void body(String body) { } @Override
public void endElement() { } private void setProperties(Attributes attributes, Object object) {
Method[] methods = object.getClass().getMethods();
ArrayList<Method> list = new ArrayList<>();
list.addAll(Arrays.asList(methods));
list.removeIf(o -> o.getParameterCount() != 1); for (int i = 0; i < attributes.getLength(); i++) {
// 获取属性名
String attributesQName = attributes.getQName(i);
String setterMethod = "set" + attributesQName.substring(0, 1).toUpperCase() + attributesQName.substring(1); String value = attributes.getValue(i);
TwoTuple<Method, Object[]> tuple = getSuitableMethod(list, setterMethod, value);
// 没有找到合适的方法
if (tuple == null) {
continue;
} Method method = tuple.first;
Object[] params = tuple.second;
try {
method.invoke(object,params);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
} private TwoTuple<Method, Object[]> getSuitableMethod(List<Method> list, String setterMethod, String value) { for (Method method : list) { if (!Objects.equals(method.getName(), setterMethod)) {
continue;
} Object[] params = new Object[1]; /**
* 1;如果参数类型就是String,那么就是要找的
*/
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?> parameterType = parameterTypes[0];
if (parameterType.equals(String.class)) {
params[0] = value;
return new TwoTuple<>(method,params);
} Boolean ok = true; // 看看int是否可以转换
String name = parameterType.getName();
if (name.equals("java.lang.Integer")
|| name.equals("int")){
try {
params[0] = Integer.valueOf(value);
}catch (NumberFormatException e){
ok = false;
e.printStackTrace();
}
// 看看 long 是否可以转换
}else if (name.equals("java.lang.Long")
|| name.equals("long")){
try {
params[0] = Long.valueOf(value);
}catch (NumberFormatException e){
ok = false;
e.printStackTrace();
}
// 如果int 和 long 不行,那就只有尝试boolean了
}else if (name.equals("java.lang.Boolean") ||
name.equals("boolean")){
params[0] = Boolean.valueOf(value);
} if (ok){
return new TwoTuple<Method,Object[]>(method,params);
}
}
return null;
}
}

该 rule,重点代码为23-25行,主要是设置对象的属性。对象从哪来,从handler中获取栈顶元素即可。设置属性这部分,主要是利用反射来解决的。

3、元素与规则列表的对应关系

规则定义好了,我们再看看,针对具体某个xml元素,需要应用哪些规则呢? 这部分是需要我们预定义的。

 package com.coder;

 import com.coder.rule.CreateObjectParseRule;
import com.coder.rule.ParseRule;
import com.coder.rule.SetPropertiesParseRule;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; /**
* desc:
* @author: caokunliang
* creat_date: 2019/6/29 0029
* creat_time: 11:06
**/
public class GirlFriendHandlerVersion2 extends DefaultHandler {
private LinkedList<Object> stack = new LinkedList<>(); /**
* 规则定义。每个元素可以有多条规则,所以value是一个list。解析时,会按顺序调用各个规则
*/
33 private ConcurrentHashMap<String, List<ParseRule>> ruleMap = new ConcurrentHashMap<>(); {
ArrayList<ParseRule> rules = new ArrayList<>();
rules.add(new CreateObjectParseRule("class",this));
rules.add(new SetPropertiesParseRule(this)); ruleMap.put("Coder",rules); rules = new ArrayList<>();
rules.add(new CreateObjectParseRule("class",this));
rules.add(new SetPropertiesParseRule(this)); ruleMap.put("Girl",rules);
} }

为了存储该关系,我们利用了 concurrenthashmap,key即为xml元素的名字,value为需要应用的规则列表。具体的规则定义,见第 36-46行。

4、startElement 实现

 @Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
System.out.println("startElement: " + qName + " It's the " + eventOrderCounter.getAndIncrement() + " one"); List<ParseRule> rules = ruleMap.get(qName);
for (ParseRule rule : rules) {
rule.startElement(attributes);
} }

该方法会在解析到 xml 元素开始时,被sax 解析模型调用。 第三个参数qName,即为xml元素的值。我们这里,根据qName获取到规则,然后依次应用这些规则。

5、endElement实现

    @Override
public void endElement(String uri, String localName, String qName) throws SAXException {
System.out.println("endElement: " + qName + " It's the " + eventOrderCounter.getAndIncrement() + " one"); if ("Coder".equals(qName)){
Object o = stack.pop();
System.out.println(o);
}else if ("Girl".equals(qName)){
//弹出来的应该是girl
Object o = stack.pop(); //接下来获取到coder
Coder coder = (Coder) stack.peek();
coder.setGirl((Girl) o); }
}

该方法,会在解析到 xml元素的结束标记时被调用,我们这里,主要关注 橙色行,这里从栈中弹出第一个元素,应该是我们在 startElement 中 压入栈内的 girl;然后继续取栈顶元素,则应该取到 Coder 对象。然后我们这里,手动将 girl 设置到 Coder里面去。

这里将在下一个版本的 handler 中进行优化。

6、执行测试代码

类的完整代码如下,执行main即可:

 package com.coder;

 import com.coder.rule.CreateObjectParseRule;
import com.coder.rule.ParseRule;
import com.coder.rule.SetPropertiesParseRule;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; /**
* desc:
* @author: caokunliang
* creat_date: 2019/6/29 0029
* creat_time: 11:06
**/
public class GirlFriendHandlerVersion2 extends DefaultHandler {
private LinkedList<Object> stack = new LinkedList<>(); /**
* 规则定义。每个元素可以有多条规则,所以value是一个list。解析时,会按顺序调用各个规则
*/
private ConcurrentHashMap<String, List<ParseRule>> ruleMap = new ConcurrentHashMap<>(); {
ArrayList<ParseRule> rules = new ArrayList<>();
rules.add(new CreateObjectParseRule("class",this));
rules.add(new SetPropertiesParseRule(this)); ruleMap.put("Coder",rules); rules = new ArrayList<>();
rules.add(new CreateObjectParseRule("class",this));
rules.add(new SetPropertiesParseRule(this)); ruleMap.put("Girl",rules);
} private AtomicInteger eventOrderCounter = new AtomicInteger(0); @Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
System.out.println("startElement: " + qName + " It's the " + eventOrderCounter.getAndIncrement() + " one"); List<ParseRule> rules = ruleMap.get(qName);
for (ParseRule rule : rules) {
rule.startElement(attributes);
} } @Override
public void endElement(String uri, String localName, String qName) throws SAXException {
System.out.println("endElement: " + qName + " It's the " + eventOrderCounter.getAndIncrement() + " one"); if ("Coder".equals(qName)){
Object o = stack.pop();
System.out.println(o);
}else if ("Girl".equals(qName)){
//弹出来的应该是girl
Object o = stack.pop(); //接下来获取到coder
Coder coder = (Coder) stack.peek();
coder.setGirl((Girl) o); }
} public static void main(String[] args) {
GirlFriendHandlerVersion2 handler = new GirlFriendHandlerVersion2(); SAXParserFactory spf = SAXParserFactory.newInstance();
try {
SAXParser parser = spf.newSAXParser();
InputStream inputStream = ClassLoader.getSystemClassLoader()
.getResourceAsStream("girlfriend.xml"); parser.parse(inputStream, handler);
} catch (ParserConfigurationException | SAXException | IOException e) {
e.printStackTrace();
}
} /**
* 栈内弹出栈顶对象
* @return
*/
public Object pop(){
return stack.pop();
} /**
* 栈顶push元素
* @param object
*/
public void push(Object object){
stack.push(object);
} /**
* 返回栈顶元素,但不弹出
*/
public Object peek(){
return stack.peek();
}
}

执行结果如下:

三、优化

这次的优化目标就是,去掉上面endElement里面的硬编码。我们给 girl 元素加一条rule,该rule 会在endElement时被调用,该rule的逻辑是,从栈中弹出 girl 元素,再从栈中取出栈顶元素(此时由于girl被弹出,此时栈顶为 coder)。

然后直接反射调用 coder 的 setGirl 方法,即可将girl 设置进去。

1、定义ParentChildRule

 package com.coder.rule;

 import com.coder.GirlFriendHandlerVersion2;
import org.xml.sax.Attributes; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; public class ParentChildRule implements ParseRule{
/**
* 父对象的方法名,通过该方法将子对象设置进去
*/
private String parentObjectSetter; private GirlFriendHandlerVersion2 girlFriendHandler; public ParentChildRule(String parentObjectSetter, GirlFriendHandlerVersion2 girlFriendHandler) {
this.parentObjectSetter = parentObjectSetter;
this.girlFriendHandler = girlFriendHandler;
} @Override
public void startElement(Attributes attributes) { } @Override
public void body(String body) { } @Override
public void endElement() {
// 获取到栈顶对象child,该对象将作为child,被设置到parent中
Object child = girlFriendHandler.pop();
//栈顶的child被弹出后,继续调用peek,将获取到parent
Object parent = girlFriendHandler.peek(); try {
Method method = parent.getClass().getMethod(parentObjectSetter, new Class[]{child.getClass()});
method.invoke(parent,child);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}

2、给 girl 新增规则

         rules = new ArrayList<>();
rules.add(new CreateObjectParseRule("class",this));
rules.add(new SetPropertiesParseRule(this));
4 rules.add(new ParentChildRule("setGirl", this)); ruleMap.put("Girl",rules);

3、修改endElement

    @Override
public void endElement(String uri, String localName, String qName) throws SAXException {
System.out.println("endElement: " + qName + " It's the " + eventOrderCounter.getAndIncrement() + " one"); List<ParseRule> rules = ruleMap.get(qName);
if (rules != null) {
for (ParseRule rule : rules) {
rule.endElement();
}
} }

这里的逻辑不再硬编码,根据元素获取 rule 列表,然后按顺序调用 rule 的 endElement 即可。这里,就会调用 ParentChildRule ,将 girl 设置到 coder里面去。

4、完整实现

 package com.coder;

 import com.coder.rule.CreateObjectParseRule;
import com.coder.rule.ParentChildRule;
import com.coder.rule.ParseRule;
import com.coder.rule.SetPropertiesParseRule;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; /**
* desc:
* @author: caokunliang
* creat_date: 2019/6/29 0029
* creat_time: 11:06
**/
public class GirlFriendHandlerVersion2 extends DefaultHandler {
private LinkedList<Object> stack = new LinkedList<>(); /**
* 规则定义。每个元素可以有多条规则,所以value是一个list。解析时,会按顺序调用各个规则
*/
private ConcurrentHashMap<String, List<ParseRule>> ruleMap = new ConcurrentHashMap<>(); {
ArrayList<ParseRule> rules = new ArrayList<>();
rules.add(new CreateObjectParseRule("class",this));
rules.add(new SetPropertiesParseRule(this)); ruleMap.put("Coder",rules); rules = new ArrayList<>();
rules.add(new CreateObjectParseRule("class",this));
rules.add(new SetPropertiesParseRule(this));
rules.add(new ParentChildRule("setGirl", this)); ruleMap.put("Girl",rules);
} private AtomicInteger eventOrderCounter = new AtomicInteger(0); @Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
System.out.println("startElement: " + qName + " It's the " + eventOrderCounter.getAndIncrement() + " one"); List<ParseRule> rules = ruleMap.get(qName);
for (ParseRule rule : rules) {
rule.startElement(attributes);
} } @Override
public void endElement(String uri, String localName, String qName) throws SAXException {
System.out.println("endElement: " + qName + " It's the " + eventOrderCounter.getAndIncrement() + " one"); List<ParseRule> rules = ruleMap.get(qName);
if (rules != null) {
for (ParseRule rule : rules) {
rule.endElement();
}
} } public static void main(String[] args) {
GirlFriendHandlerVersion2 handler = new GirlFriendHandlerVersion2(); SAXParserFactory spf = SAXParserFactory.newInstance();
try {
SAXParser parser = spf.newSAXParser();
InputStream inputStream = ClassLoader.getSystemClassLoader()
.getResourceAsStream("girlfriend.xml"); parser.parse(inputStream, handler);
Object o = handler.stack.pop();
System.out.println(o);
} catch (ParserConfigurationException | SAXException | IOException e) {
e.printStackTrace();
}
} /**
* 栈内弹出栈顶对象
* @return
*/
public Object pop(){
return stack.pop();
} /**
* 栈顶push元素
* @param object
*/
public void push(Object object){
stack.push(object);
} /**
* 返回栈顶元素,但不弹出
*/
public Object peek(){
return stack.peek();
}
}

四、源码与总结

以上部分的源码在:

https://github.com/cctvckl/tomcat-saxtest

下篇将会正式进入 Tomcat 的 Digester 机制。

曹工说Tomcat2:自己撸一个简易Tomcat Digester的更多相关文章

  1. 基于 getter 和 setter 撸一个简易的MVVM

    Angular 和 Vue 在对Angular的学习中,了解到AngularJS 的两个主要缺点: 对于每一次界面时间,Ajax 或者 timeout,都会进行一个脏检查,而每一次脏检查又会在内部循环 ...

  2. 手把手教你撸一个简易的 webpack

    背景 随着前端复杂度的不断提升,诞生出很多打包工具,比如最先的grunt,gulp.到后来的webpack和Parcel.但是目前很多脚手架工具,比如vue-cli已经帮我们集成了一些构建工具的使用. ...

  3. 半天撸一个简易版mybatis

    为什么需要持久层框架? 首先我们先看看使用原生jdbc存在的问题? public static void main(String[] args) { Connection connection = n ...

  4. 曹工说Tomcat3:深入理解 Tomcat Digester

    一.前言 我写博客主要靠自己实战,理论知识不是很强,要全面介绍Tomcat Digester,还是需要一定的理论功底.翻阅了一些介绍 Digester 的书籍.博客,发现不是很系统,最后发现还是官方文 ...

  5. 曹工说Tomcat4:利用 Digester 手撸一个轻量的 Spring IOC容器

    一.前言 一共8个类,撸一个IOC容器.当然,我们是很轻量级的,但能够满足基本需求.想想典型的 Spring 项目,是不是就是各种Service/DAO/Controller,大家互相注入,就组装成了 ...

  6. C#基于Mongo的官方驱动手撸一个Super简易版MongoDB-ORM框架

    C#基于Mongo的官方驱动手撸一个简易版MongoDB-ORM框架 如题,在GitHub上找了一圈想找一个MongoDB的的ORM框架,未偿所愿,就去翻了翻官网(https://docs.mongo ...

  7. 撸了一个简易的配置中心,顺带整合到了SpringCloud

    大家好,我是三友~~ 最近突然心血来潮(就是闲的)就想着撸一个简单的配置中心,顺便也照葫芦画瓢给整合到SpringCloud. 本文大纲 配置中心的概述 随着历史的车轮不断的前进,技术不断的进步,单体 ...

  8. 手撸一个springsecurity,了解一下security原理

    手撸一个springsecurity,了解一下security原理 转载自:www.javaman.cn 手撸一个springsecurity,了解一下security原理 今天手撸一个简易版本的sp ...

  9. 自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)

    本文由作者FreddyChen原创分享,为了更好的体现文章价值,引用时有少许改动,感谢原作者. 1.写在前面 一直想写一篇关于im即时通讯分享的文章,无奈工作太忙,很难抽出时间.今天终于从公司离职了, ...

随机推荐

  1. WPF的逻辑树与视觉树(2)Visual容器

    原文:WPF的逻辑树与视觉树(2)Visual容器   一.摘要 虽然我们平时几乎不会从该类派生,但要想了解视觉树就必须要了解Visual,Visual是一个基本抽象类,继承自DependencyOb ...

  2. Entity Framework 6 编译出错的问题(VS2012)

    更新:其实这个问题是由于VS2012的EF代码生成模板是EF 5.x的,自然会与EF6 的runtime不兼容.起初我按照更新前的方式解决了,后来却发现会出现不止这一处命名空间发生改动而导致的问题. ...

  3. ZOJ 2334 HDU 1512 Monkey King

    题意: 猴子们打架  认识的猴子不会打架  两仅仅猴子打完以后就认识了  A认识B B认识C A也认识C  每次打架由两伙猴子进行  分别选出自己的最高战斗力  在战斗之后两仅仅猴子战斗力减半  给出 ...

  4. WPF ListView控件设置奇偶行背景色交替变换以及ListViewItem鼠标悬停动画

    原文:WPF ListView控件设置奇偶行背景色交替变换以及ListViewItem鼠标悬停动画 利用WPF的ListView控件实现类似于Winform中DataGrid行背景色交替变换的效果,同 ...

  5. 【C#】获取任意文件的缩略图

    原文:[C#]获取任意文件的缩略图 因为用shell取缩略图时,对于损坏的文件,读出来的图有黑边,所以就诞生了以下方法,不过这个效率要比用shell取的低3-4倍. 1.添加类WindowsThumb ...

  6. ES6中的Promise详解

    Promise 在 JavaScript 中很早就有各种的开源实现,ES6 将其纳入了官方标准,提供了原生 api 支持,使用更加便捷. 定义 Promise 是一个对象,它用来标识 JavaScri ...

  7. Java泛型和类型安全的容器

    示例: public class Apple { private static long counter; private final long id = counter++; public long ...

  8. 管道通信实例(A程序作为服务器,不断从B程序接收数据,并发送到C程序中)

    A程序作为服务器,不断从B程序接收数据,并发送到C程序中:#include <stdio.h>#include <conio.h> #include <tchar.h&g ...

  9. MIPS开发板的“不二”选择——Creator Ci20单板计算机评测(芯片是君正JZ4780 ,也就是MIPS R3000,系统推荐Debian或深度,官网就有,其它语言有FreePascal和Go和Java和Python)

    在MIPS架构的CPU上开发软件,当然需要使用MIPS专用的工具链来编译代码.不过一般的LINUX发行版内都有相应的配套工具链供用户使用.Ci20出厂时的LINUX发行版为DEBIAN 7.5,相应的 ...

  10. SQLDirect 6.5 Source (Delphi 5-10.1 Berlin)

    Description:SQLDirect Component Library is a light-weight Borland Database Engine replacement for Bo ...