hbase-sdk

基于HBase Client的相关API开发而来的一款轻量级的HBase ORM框架。提供SQL查询功能,以类SQL的方式——HQL读写HBase数据。?

针对HBase 1.x和2.xAPI的不同之处,在其上做了一层统一的封装。

hbase-sdk分为spring-boot-starter-hbase和hbase-sdk-core两部分。

SpringBoot项目中引入spring-boot-starter-hbase,在普通的Java项目中则可以使用hbase-sdk-core。


hbase-sdk 是一款轻量级的ORM框架,封装了对HBase数据表的读写操作和对集群的运维管理,并针对HBase1.x的API和2.xAPI的差异,做了统一的定义, 提供更加方便的调用API。同时,HQL的功能也已上线,提供了类SQL读写数据的能力,这将大大降低HBase Client API的使用门槛。API文档地址:

https://weixiaotome.gitee.io/hbase-sdk/

如果觉得这个项目不错可以 star 支持或者打赏它。

功能特性

  • [x] 消除不同版本API的差异,重新定义接口规范
  • [x] 优良的ORM特性,数据查询结果集自动映射Java实体类
  • [x] HQL,以类SQL的形式读写HBase的表中数据
  • [x] 利用spring-boot-starter-hbase无缝与SpringBoot集成
  • [x] HBatis,类似于myBatis,提供配置文件管理HQL的功能(努力规划中)
  • [x] 熔断能力,提供API级别的主备集群自动切换功能,保障服务的高可用(努力规划中)
  • [x] JDK8+

快速开始

hbase-sdk 的每个版本经过测试完成之后,都会编译打包至各个模块,最后发布到maven中央仓库之中,只是最新版本的代码有一定的延迟。如果你想在第一时间体验该项目, 可以选择在Gitee或Github中克隆源码,在本地编译并运行测试用例。

hbase-sdk 基于java8开发,如果你想自己编译或体验,请确保已经安装了Java8和maven3+。此外,如果你想在本地进行开发调试,建议在本地存在一个可连通的HBase环境。如果你想快速搭建一个HBase的开发环境,请参考:

https://www.jielongping.com/archives/dockerhbasetest

hbase-sdk 开发所用的工具为IDEA,所以也极力推荐导入项目到idea中。

1. 普通项目

Maven 配置:

创建一个基础的 Maven 工程,HBase SDK API开发时基于的HBase版本主要是1.4.3和2.1.0。

所以,如果你的HBase版本是1.x,可以使用如下依赖。

<dependency>    <groupId>com.github.CCweixiaogroupId>    <artifactId>hbase-sdk-core_1.4artifactId>    <version>2.0.6version>dependency>

如果你的HBase版本是2.x,则可以使用如下依赖。

<dependency>    <groupId>com.github.CCweixiaogroupId>    <artifactId>hbase-sdk-core_2.1artifactId>    <version>1.0.5version>dependency>

hbase-sdk目前最新的版本是2.0.6。你可以在maven仓库中搜索CCweixiao来获取hbase-sdk相关jar包的最新版本。

https://mvnrepository.com/artifact/com.github.CCweixiao

当然,如果你想重新编译,以适配你自己的HBase版本,也可以选择下载源码,修改项目根pom.xml文件中的hbase.version,之后运行如下编译命令:

git clone https://github.com/CCweixiao/hbase-sdk.git  orgit clone https://gitee.com/weixiaotome/hbase-sdk.gitcd hbase-sdkmvn clean install -Dmaven.test.skip=true

2. 项目结构

project

API核心类继承结构示意图:

3. 在SpringBoot项目中使用

Maven 配置:

创建一个基于Maven的spring boot工程。

<dependency>    <groupId>com.github.CCweixiaogroupId>    <artifactId>spring-boot-starter-hbase_1.4artifactId>    <version>2.0.6version>dependency>

or

<dependency>    <groupId>com.github.CCweixiaogroupId>    <artifactId>spring-boot-starter-hbase_2.1artifactId>    <version>2.0.6version>dependency>

spring-boot-starter-hbase这个模块中已经包含了hbase-sdk-core。

4. 引入hbase-client的依赖

除了引入hbase-sdk的相关依赖之外,你还需要引入hbase-client的依赖,hbase-client的版本目前建议为1.2.x1.4.x2.1.x。hbase-client1.x和2.x的新旧API有所差异。未来,hbase-sdk会持续完善该依赖的版本兼容。

<dependency>    <groupId>org.apache.hbasegroupId>    <artifactId>hbase-clientartifactId>    <version>1.4.3version>dependency>        

or

<dependency>    <groupId>org.apache.hbasegroupId>    <artifactId>hbase-clientartifactId>    <version>2.1.0version>dependency>        

5. 配置HBase数据库连接

普通项目

// 数据读写APIHBaseTemplate hBaseTemplate = new HBaseTemplate("node1", "2181");// 管理员APIHBaseAdminTemplate hBaseAdminTemplate = new HBaseAdminTemplate("node1", "2181");// HQL操作APIHBaseSqlTemplate hBaseSqlTemplate = new HBaseSqlTemplate("localhost", "2181");

spring boot项目

application.yaml

spring:  data:    hbase:      quorum: node1,node2,node3      node-parent: /hbase      zk-client-port: 2181      root-dir: /hbase      client-properties: hbase.client.retries.number=3

@Autowired 依赖注入

@Servicepublic class UserService {    @Autowired    private HBaseTemplate hBaseTemplate;    @Autowired    private HBaseAdminTemplate hBaseAdminTemplate;}

集群管理

HBaseAdminTemplate封装了HBaseAdmin的常用操作,比如namespace的管理、表的管理、以及快照管理等等,后续这些API将会更加完善。

admin-api

创建namespace

    @Test    public void testCreateNamespace() {        String namespaceName = "LEO_NS";

        NamespaceDesc namespaceDesc = new NamespaceDesc();        namespaceDesc.setNamespaceName(namespaceName);        // 为namespace添加属性        namespaceDesc = namespaceDesc.addNamespaceProp("desc", "测试命名空间")                .addNamespaceProp("createBy", "leo").addNamespaceProp("updateBy", "admin");

        hBaseTemplate.createNamespace(namespaceDesc);    }

创建表

    @Test    public void testCreateTable() {        String tableName = "LEO_NS:USER";

        TableDesc tableDesc = new TableDesc();        tableDesc.setTableName(tableName);

        tableDesc = tableDesc.addProp("tag", "测试用户表").addProp("createUser", "leo");

        FamilyDesc familyDesc1 = new FamilyDesc.Builder()                .familyName("INFO")                .replicationScope(1)                .compressionType("NONE")                .timeToLive(2147483647)                .maxVersions(1).build();

        FamilyDesc familyDesc2 = new FamilyDesc.Builder()                .familyName("INFO2")                .replicationScope(0)                .compressionType("SNAPPY")                .timeToLive(864000)                .maxVersions(3).build();

        tableDesc = tableDesc.addFamilyDesc(familyDesc1).addFamilyDesc(familyDesc2);

        hBaseTemplate.createTable(tableDesc, false);    }

更多操作

可以参考相关API文档或测试用例

数据读写

类似于Hibernate,你也可以使用hbase-sdk框架所提供的ORM特性,来实现对HBase的数据读写操作。

api-data

创建数据模型类

public class ModelEntity {    private String createBy;    private Long createTime;

    public String getCreateBy() {        return createBy;    }

    public void setCreateBy(String createBy) {        this.createBy = createBy;    }

    public Long getCreateTime() {        return createTime;    }

    public void setCreateTime(Long createTime) {        this.createTime = createTime;    }}
@HBaseTable(schema = "TEST", name = "LEO_USER", uniqueFamily = "info1")public class UserEntity extends ModelEntity{    @HBaseRowKey    private String userId;

    private String username;    private int age;    private List addresses;private Map contactInfo;private Double pay;@HBaseColumn(name = "is_vip", family = "INFO2", toUpperCase = true)private boolean isVip;public String getUserId() {return userId;    }public void setUserId(String userId) {this.userId = userId;    }public String getUsername() {return username;    }public void setUsername(String username) {this.username = username;    }public int getAge() {return age;    }public void setAge(int age) {this.age = age;    }public boolean isVip() {return isVip;    }public void setVip(boolean vip) {        isVip = vip;    }public List getAddresses() {return addresses;    }public void setAddresses(List addresses) {this.addresses = addresses;    }public Map getContactInfo() {return contactInfo;    }public void setContactInfo(Map contactInfo) {this.contactInfo = contactInfo;    }public Double getPay() {return pay;    }public void setPay(Double pay) {this.pay = pay;    }@Overridepublic String toString() {return "UserEntity{" +"userId='" + userId + '\'' +", username='" + username + '\'' +", age=" + age +", addresses=" + addresses +", contactInfo=" + contactInfo +", pay=" + pay +", isVip=" + isVip +'}';    }}

@HBaseTable(schema = "TEST", name = "LEO_USER", uniqueFamily = "info1")

HBaseTable注解用于定义HBase的表信息,schema用于定义该表的命名空间,如果不指定,默认是default, name用于定义该表的表名,如果不指定,表名则为类名的组合单词拆分加'_'拼接,如:UserEntity对应的表名为:user_entity。uniqueFamily用于定义如果所有的字段不特配置列簇名,则使用此处配置的列簇名。

@HBaseRowKey private String userId;

该注解表示userId字段为rowKey字段。

@HBaseColumn(name = "is_vip", family = "INFO2", toUpperCase = true) private boolean isVip; 该注解用于定义一个字段信息,name用于定义字段名,如果不指定,则默认使用字段名的组合单词拆分加'_'拼接,如:isVip,对应的字段名是:is_vip. family用于定义该字段属于INFO2列簇,toUpperCase表示字段名是否转大写,默认false,不做操作。

保存数据

   @Test    public void testSaveUser() {        UserEntity userEntity = new UserEntity();        userEntity.setUserId("10001");        userEntity.setUsername("leo");        userEntity.setAge(18);        userEntity.setVip(true);        userEntity.setAddresses(Arrays.asList("北京", "上海"));        userEntity.setCreateBy("admin");        userEntity.setCreateTime(System.currentTimeMillis());

        Map contactInfo = new HashMap<>(2);        contactInfo.put("email", "2326130720@qq.com");        contactInfo.put("phone", "18739577988");        contactInfo.put("address", "浦东新区");        userEntity.setContactInfo(contactInfo);        userEntity.setPay(100000.0d);try {            hBaseTemplate.save(userEntity);            System.out.println("用户数据保存成功!");        } catch (Exception e) {            e.printStackTrace();        }    }

除此之外,保存数据时也可以不必构造数据模型类,而直接构造map数据模型。

    @Test    public void testToSave() {        Map data = new HashMap<>();        data.put("info1:addresses", Arrays.asList("广州", "深圳"));        data.put("info1:username", "leo");        data.put("info1:age", 18);        data.put("INFO2:IS_VIP", true);        data.put("info1:pay", 10000.1d);        data.put("info1:create_by", "tom");        data.put("info1:create_time", System.currentTimeMillis());        Map contactInfo = new HashMap<>(2);        contactInfo.put("email", "2326130720@qq.com");        contactInfo.put("phone", "18739577988");        contactInfo.put("address", "浦东新区");        data.put("info1:contact_info", contactInfo);        hBaseTemplate.save("TEST:LEO_USER", "10002", data);        System.out.println("用户数据保存成功!");    }

批量保存数据

    @Test    public void testToSaveBatch() {        Map> data = new HashMap<>();        Map data1 = new HashMap<>();        data1.put("info1:username", "kangkang");        data1.put("info1:age", 18);        data1.put("INFO2:IS_VIP", true);        Map data2 = new HashMap<>();        data2.put("info1:username", "jane");        data2.put("info1:age", 18);        data2.put("INFO2:IS_VIP", false);        data.put("12003", data1);        data.put("11004", data2);        hBaseTemplate.saveBatch("TEST:LEO_USER", data);        System.out.println("用户数据批量保存成功!");    }

根据RowKey查询

    @Test    public void testGet() {        UserEntity userEntity = hBaseTemplate.getByRowKey("10001", UserEntity.class);        final UserEntity userEntity1 = hBaseTemplate.getByRowKey("10002", UserEntity.class);        System.out.println("用户数据获取成功!");        System.out.println(userEntity);    }
    @Test    public void testGetToMap() {        Map userInfo = hBaseTemplate.getByRowKey("TEST:LEO_USER", "10001");        System.out.println(Boolean.valueOf(userInfo.get("INFO2:IS_VIP").toString()));        System.out.println(userInfo);    }

scan查询

    @Test    public void testFind() {        final List userEntities = hBaseTemplate.findAll(10, UserEntity.class);        System.out.println(userEntities);        System.out.println("用户数据批量查询");    }@Testpublic void testFindByPrefix() {final List userEntities = hBaseTemplate.findByPrefix("11", 10, UserEntity.class);        System.out.println("用户数据批量查询");    }

删除数据

    @Test    public void testDeleteData() {        hBaseTemplate.delete("TEST:LEO_USER", "12003");        hBaseTemplate.delete("TEST:LEO_USER", "11004", "INFO2");        hBaseTemplate.delete("TEST:LEO_USER", "10001", "info1", "addresses");        System.out.println("数据删除完成");    }
    @Test    public void testDeleteBatch() {        hBaseTemplate.deleteBatch("TEST:LEO_USER", Arrays.asList("10001", "10002"));        hBaseTemplate.deleteBatch("TEST:LEO_USER", Collections.singletonList("10003"), "INFO2");        hBaseTemplate.deleteBatch("TEST:LEO_USER", Collections.singletonList("10004"),                "info1", "age", "username");    }

HQL

hbase-sdk 从2.0.6版本开始,开始提供HQL功能,一种以类SQL的方式读写HBase集群的数据,降低API的使用复杂度。HQL的操作依赖HBaseSqlTemplate来完成, 因此使用之前,必须构造好HBaseSqlTemplate的对象实例。

hql

构造HBaseSqlTemplate的示例。

    private HBaseSqlTemplate hBaseSqlTemplate;

    @Before    public void testInitHBaseSqlTemplate() {        hBaseSqlTemplate = new HBaseSqlTemplate("localhost", "2181");

        List hBaseColumnSchemas = createHBaseColumnSchemaList();        HBaseTableSchema hBaseTableSchema = new HBaseTableSchema();        hBaseTableSchema.setTableName("LEO_USER");        hBaseTableSchema.setDefaultFamily("g");//hBaseTableSchema.setRowKeyHandlerName("string");        HBaseTableConfig hBaseTableConfig = new DefaultHBaseTableConfig(hBaseTableSchema, hBaseColumnSchemas);        hBaseSqlTemplate.setHBaseTableConfig(hBaseTableConfig);    }public List createHBaseColumnSchemaList() {            List hBaseColumnSchemas = new ArrayList<>();            HBaseColumnSchema hBaseColumnSchema1 = new HBaseColumnSchema();            hBaseColumnSchema1.setFamily("g");            hBaseColumnSchema1.setQualifier("id");            hBaseColumnSchema1.setTypeName("string");            HBaseColumnSchema hBaseColumnSchema2 = new HBaseColumnSchema();            hBaseColumnSchema2.setFamily("g");            hBaseColumnSchema2.setQualifier("name");            hBaseColumnSchema2.setTypeName("string");            HBaseColumnSchema hBaseColumnSchema3 = new HBaseColumnSchema();            hBaseColumnSchema3.setFamily("g");            hBaseColumnSchema3.setQualifier("age");            hBaseColumnSchema3.setTypeName("int");            HBaseColumnSchema hBaseColumnSchema4 = new HBaseColumnSchema();            hBaseColumnSchema4.setFamily("g");            hBaseColumnSchema4.setQualifier("address");            hBaseColumnSchema4.setTypeName("string");            hBaseColumnSchemas.add(hBaseColumnSchema1);            hBaseColumnSchemas.add(hBaseColumnSchema2);            hBaseColumnSchemas.add(hBaseColumnSchema3);            hBaseColumnSchemas.add(hBaseColumnSchema4);return hBaseColumnSchemas;        }

构造hBaseSqlTemplate示例需要先构造HBaseTableConfig,HBaseTableConfig的两个成员变量,

    protected HBaseTableSchema hBaseTableSchema;    protected  List hBaseColumnSchemaList;

分别用来表的Schema信息和HBase表对应列的元数据信息。

针对HBase表列的数据类型转换,目前内置的实现有:

Boolean、Byte、Char、Date、Double、Float、Hex、Int、Long、Short、String

通过实现LiteralInterpreter接口,你可以定义自己的列数据类型转换实现。

{ "tableName":"TEST:USER", "defaultFamily":"INFO", "columnSchema":[  {   "family":"INFO",   "qualifier":"name",   "typeName":"string"  },  {   "family":"INFO2",   "qualifier":"age",   "typeName":"int"  } ]}

通过实现相应的接口,你可以选择加载HBase表、列元数据信息的方式。如:类型myBatis在XML文件中加载。

HBaseSqlTemplate的实例准备好之后,就可以使用HQL来进行数据读写。

insert

insert into LEO_USER ( g:id , g:name , g:age , g:address ) values ( '10001', 'leo1' , '18', 'shanghai' ) where rowKey is stringkey ( 'a10002' ) ts is '1604160000000'

insert into LEO_USER ( g:id , g:name , g:age , g:address ) values ( '10002', 'leo2' , '17', 'beijing' ) where rowKey is stringkey ( 'a10002' )

    @Test    public void testInsertSql() {        String sql = "insert into LEO_USER ( g:id , g:name , g:age , g:address ) values ( '10001', 'leo' , '18', 'shanghai' ) where rowKey is stringkey ( 'a10002' ) ts is '1604160000000'";        hBaseSqlTemplate.insert(sql);        System.out.println("insert successfully!");    }

select

select ( g:id , g:name , g:age , g:address ) from LEO_USER where startKey is stringkey ( 'a10001' ) , endKey is stringkey ( 'a10002' ) ( ( name equal 'leo' and age less '12' ) or ( id greater '10000' ) )  maxversion is 2  startTS is '1604160000000' , endTS is '1604160000001' limit 1, 10 

select * from LEO_USER where startKey is stringkey ( 'a10001' ) , endKey is stringkey ( 'a10002' ) ( ( name equal 'leo1' and age less '20' ) or ( id greater '10000' ) )  maxversion is 2  startTS is '1604160000000' , endTS is '1604160000001' limit 10
    @Test    public void testSelectSql() {        String sql = "select ( g:id , g:name , g:age , g:address ) from LEO_USER where startKey is stringkey ( 'a10001' ) , endKey is stringkey ( 'a10002' ) ( ( name equal 'leo' and age less '12' ) or ( id greater '10000' ) )  maxversion is 2  startTS is '1604160000000' , endTS is '1604160000001' limit 10 ";        sql = "select * from LEO_USER where startKey is stringkey ( 'a10001' ) , endKey is stringkey ( 'a10002' ) ( ( name equal 'leo' and age less '12' ) or ( id greater '10000' ) )  maxversion is 2  startTS is '1604160000000' , endTS is '1604160000001' limit 10 ";

        final List> listList = hBaseSqlTemplate.select(sql);        listList.forEach(dataList -> {            dataList.forEach(data -> {                System.out.println(data.getRowKey());                System.out.println(data.getFamilyStr());                System.out.println(data.getQualifierStr());                System.out.println(data.getTsDate());                System.out.println("########################################");            });        });    }

delete

delete ( id , name ) from LEO_USER where startKey is stringkey ( 'a10001' ) , endKey is stringkey ( 'a10003' ) ( ( name equal 'leo' and age less '12' ) or ( id greater '10000' ) ) ts is '1604160000000'delete * from LEO_USER where startKey is stringkey ( 'a10001' ) , endKey is stringkey ( 'a10003' ) ( ( name equal 'leo' and age less '12' ) or ( id greater '10000' ) ) ts is '1604160000000'delete * from LEO_USER where rowKey is stringkey ( 'a10002' ) ( name equal 'leo2' or age less '21' ) ts is '1604160000000'
    @Test    public void testDeleteSql(){        String sql = "delete ( id , name ) from LEO_USER where startKey is stringkey ( 'a10001' ) , endKey is stringkey ( 'a10003' ) ( ( name equal 'leo' and age less '12' ) or ( id greater '10000' ) ) ts is '1604160000000'";        sql = "delete * from LEO_USER where startKey is stringkey ( 'a10001' ) , endKey is stringkey ( 'a10003' ) ( ( name equal 'leo' and age less '12' ) or ( id greater '10000' ) ) ts is '1604160000000'";        sql = "delete * from LEO_USER where rowKey is stringkey ( 'a10002' ) ( name equal 'leo' or age less '21' ) ts is '1604160000000'";

        hBaseSqlTemplate.delete(sql);    }

特别鸣谢

HQL的语法设计以及antlr4的语法解析,有参考alibaba的开源项目 simplehbase,在此特别感谢。simplehbase感觉是一个被遗弃的项目,针对的HBase版本是0。94, 已经有超过6年没有维护了。

hbase-sdk 在simplehbase的基础上进行重组和解耦,以兼容hbase-sdk原有的框架设计,并便于以后的扩展。

hbase-sdk 目前的不足

非HQL的数据读写API还不丰富,特别是数据过滤的查询API。

HQL的antlr4解析功能不太完善,比如,目前HQL对中文要求不太好,同时,HQL对语法的要求比较严格,多一个空格少一个空格貌似都会引起语法错误。后续会针对这些缺点一一优化。

未来计划

  • HBatis,类似于MyBatis的ORM框架,以XML管理SQL的方式维护集群数据的读写操作
  • 集成Hystrix熔断框架,实现API层面的主备集群自动切换功能
  • 还有更多

更新日志

v2.0.6 2020-11-29

  • HQL功能上线

v2.0.5 2020-11-14

  • 新增功能与代码优化

v2.0.3 2020-10-08

  • 大量重构和优化

v1.0.5 2020-09-07

  • 完善基础API的功能
  • 完成ORM特性
  • 模块拆分
  • ......

java连接hbase_HBase 工具 | hbasesdk 推出HQL功能相关推荐

  1. java 连接mysql工具类_java连接Mysql数据库的工具类

    一个封装好的链接Mysql数据库的工具类,可以方便的获取Connection对象关闭Statement.ResultSet.Statment对象等等 复制代码 代码如下: package myUtil ...

  2. java连接sftp工具类

    本工具类支持远程连接sftp,上传下载文件 需要用到是jar是jsch-0.1.29.jar import java.io.BufferedReader;import java.io.File;imp ...

  3. java连接ftp工具类

    这里使用了org.apache.commons.net.ftp这个类库,仅仅是对这个类库稍微封装了一下方便使用,这里写了一个工具类,大家可以参考一下. 介绍一个 ftp客户端工具:iis7服务器管理工 ...

  4. java 连接SSH工具操作服务器 (构建者模式+Util类) 分享

    需求 因为需要以java 远程操作服务器, 比如进行文件下载/上传操作, 或者执行一些服务器常用命令ls cat grep 等等. 调研发现比较好用的SSH 工具有: ganymed-ssh2 jsc ...

  5. java连接hbase_HBase实战 | 05405.15.0Spark2使用HBaseSpark访问HBase

    转载自微信公众号Hadoop实操 温馨提示:如果使用电脑查看图片不清晰,可以使用手机打开文章单击文中的图片放大查看高清原图. Fayson的github: https://github.com/fay ...

  6. java获取excle表格对象_Java使用excel工具类导出对象功能示例

    本文实例讲述了Java使用excel工具类导出对象功能.分享给大家供大家参考,具体如下: package com.gcloud.common; import org.apache.poi.ss.use ...

  7. Java连接MySql数据库实现增删改查功能

    一   不用工具类,简单实现连接数据库并查询 package com.liu;import java.sql.*;public class JDBCTest {public static void m ...

  8. Java短连接生成工具-思路

    Java短连接生成工具-思路-这里只是模拟一下 package com.csrs.trans.shorturl;import java.util.HashMap; import java.util.M ...

  9. IDEA2019 Java连接PostgreSQL数据库实现基础功能增删改查

    IDEA2019 Java连接PostgreSQL数据库实现基础功能增删改查 注意: 每个方法对应单个java类,可以自行进行整理汇总到一个类中 一.Java通过JDBC连接到PostgreSQL数据 ...

最新文章

  1. java多线程机制_Java的多线程机制
  2. 网络营销专员浅析如何判断网络营销中网站优化效果几何?
  3. python代码案例详解-Python编程:案例详解输出函数print
  4. Laravel 实践之路: 数据库迁移与数据填充
  5. 1152 Google Recruitment (20 分)【难度: 简单 / 知识点: 模拟】
  6. 请写php合并数组,合并PHP数组
  7. 1965 - 2019 年最流行的编程语言变化
  8. vb 6.0 获取重定向的url_接口测试:A07_HttpRunner重定向_04_解决方案
  9. 常见的6种JavaScript设计模式
  10. 异步与并行~ReaderWriterLockSlim实现的共享锁和互斥锁
  11. git wechat.class.php,wechat-php-sdk/qywechat.class.php at master · gitye/wechat-php-sdk · GitHub
  12. Vue使用Element-ui按需引入大坑
  13. TeamCity : .NET Core 插件
  14. 用js来实现那些数据结构01(数组篇01-数组的增删)
  15. python列表推导式生成随机数_Python:列表推导式/生成器推导式
  16. C++ 日期 时间
  17. 用QLabel实现抽奖
  18. 英文分词的算法和原理
  19. Java使用jfreechart画饼图_JFreeChart饼图
  20. 微信小程序如何跳转到tabbar页面-陆大湿

热门文章

  1. 2.2 逻辑回归-机器学习笔记-斯坦福吴恩达教授
  2. STM32 基础系列教程 42 - SDMMC+Fatfs
  3. 01、DFT-全面了解如何测试一颗芯片
  4. 【PC工具】开源绿色windows右键管理工具软件
  5. 手机拍照功能的简单实现
  6. 微软重新开源 MS-DOS 1.25/2.0:已诞生 36 年
  7. 3.25 for循环
  8. sublime3配置pythonIDE
  9. 操作系统基本分段存储管理方式
  10. 三阶魔方花样玩法,公式汇总