Quantcast
Channel: CSDN博客推荐文章
Viewing all 35570 articles
Browse latest View live

BZOJ1303(CQOI2009)[中位数图]--奇技淫巧

$
0
0

【链接】
bzoj1303

【解题报告】

因为题目说了是1~n的排列且长度是奇数。所以只需要统计大于中位数的数等于小于中位数的数且包含中位数的区间个数。开个数组统计一下就行了。

#include<cstdio>
#include<cstring>
#define LL long long
using namespace std;
const int maxn=200005,tt=100000;
int n,m,where,num[maxn],sum[maxn];
LL ans;
inline char nc()
{
    static char buf[100000],*l,*r;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    if (l==r) return EOF; return *l++;
}
inline int Read()
{
    int res=0,f=1; char ch=nc(),cc=ch;
    while (ch<'0'||ch>'9') cc=ch,ch=nc();
    if (cc=='-') f=-1;
    while (ch>='0'&&ch<='9') res=res*10+ch-48,ch=nc();
    return res*f;
}
int main()
{
    freopen("1303.in","r",stdin);
    freopen("1303.out","w",stdout);
    n=Read(); m=Read(); sum[0]=where=ans=0;
    memset(num,0,sizeof(num)); num[tt]++;
    for (int i=1; i<=n; i++) 
    {
        int x=Read(),p=0;
        if (x>m) p=1;
        if (x<m) p=-1;
        if (x==m) where=i;
        sum[i]=sum[i-1]+p;
        if (!where) num[sum[i]+tt]++;
    }
    for (int i=where; i<=n; i++) ans+=num[sum[i]+tt];
    printf("%lld",ans);
    return 0;
}
作者:CHNWJD 发表于2017/11/27 21:14:41 原文链接
阅读:19 评论:0 查看评论

刷题笔记:牛客字符串专项练习3

$
0
0

题目:
一个包含 n 个节点的四叉树,每个节点都有四个指向孩子节点的指针,这 4n 个指针中有多少个空指针?

A.2n+1
B.3n-1
C.3n
D.3n+1

答案:D
知识点:四叉树

解析:
对于一个n个节点的树,应该有n-1条边,即n-1个非空指针。则对于4n个指针,空指针数为4n-(n-1)=3n+1。

作者:lin453701006 发表于2017/11/27 21:15:09 原文链接
阅读:22 评论:0 查看评论

(头条)Cordova+React+OnsenUI+Redux新闻App开发实战教程

$
0
0

这里写图片描述

前言

伴随着HTML5技术的普及力度与日俱增, 混合应用开发已经备受关注, 百家争鸣的技术框架,如何做好技术选型,搭建最稳健的架构,快速的持续集成,是一个跨平台App开发的关键所在,所以本套视频教程凭借实战App开发做实验讲解,带您共同学习基于Cordova平台的混合应用开发。

Cordova集成

Cordova作为最跨平台开发框架,已经变得越来越普及,已然成为各个平台开发的首选框架,Cordova独居一下特色:

  • 开源的框架,免费集成
  • 丰富的底层插件以及第三方支持
  • 快速集成的终端命令
  • 多平台支持(ios android windows…)
  • 支持自定义接口

React集成

React是facebook开发的一款HTML5框架,现如今已经成为主流的框架,虽然定位只是MVC中的一个view, 但是独居特色的虚拟DOM, 组件化的思想,能够整合Redux框架做数据管理,使其迅速普及开来,同时React也衍生处理驱动原生的技术框架React-Native, 火热程度已经不言而喻。

那么我们本套教程,将会结合React的技术框架做整理业务逻辑编写。

OnsenUI集成

如果一个技术框架,想提高用户体验,一个好的UX设计也是至关重要的,所以本套教程,将会推荐并讲解OnsenUI的在React中的集成,逐步搭建一个新闻App。

OnsenUI有如下特点:

  • 支持多个框架平台比如:Angular 1.x , Angular 2.x React, VUE
  • 丰富的组件支持
  • 支持ES6的集成语法
  • 轻量级的UX框架

Redux集成

做为Flux单向数据流的一个精简实现,Redux框架成为了React(View)的首选数据管理框架。单向的数据流动,大数据量的管理, 丰富的中间件集成,让React的开发更加灵活便捷。

总结

总体来讲,本套课程,会以实战的方式,讲解混合应用开发的整个流程,包括架构设计,框架集成,模块分解,产品发布;技术框架包括NodeJS, Webpack, npm, yarn, cordova, react, onsenui, redux系列中间价。

教程中会以Nodejs作为基础,搭建集成化的脚本文件,简化Cordova App发布的流程,使其更加方便快捷。

如果您想了解混合应用开发,那么本套教程一定是一个不二之选。

视频教程地址

http://edu.csdn.net/course/detail/6540

作者:jiangbo_phd 发表于2017/11/27 21:20:24 原文链接
阅读:10 评论:0 查看评论

Unity Shader 学习笔记(29) 表面着色器(Surface Shader)基本结构

$
0
0

Unity Shader 学习笔记(29) 表面着色器(Surface Shader)基本结构

参考书籍:《Unity Shader 入门精要》
官网API:Writing Surface Shaders
Unity Shader 学习笔记(3)Unity Shader模板、结构、形式
【浅墨Unity3D Shader编程】之六 暗黑城堡篇: 表面着色器(Surface Shader)的写法(一)
【浅墨Unity3D Shader编程】之七 静谧之秋篇: 表面着色器的写法(二)—— 自定义光照模式


结构

对顶点着色器和片元着色器的进一层封装。主要部分为两个结构体(Input、SurfaceOutput)和编译指令(#pragma surface)。

官网样例:

Shader "Example/Diffuse Bump" {
    Properties {
          _MainTex ("Texture", 2D) = "white" {}
          _BumpMap ("Bumpmap", 2D) = "bump" {}
    }
    SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert

        struct Input {
            float2 uv_MainTex;
            float2 uv_BumpMap;
        };

        sampler2D _MainTex;
        sampler2D _BumpMap;

        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
            o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
        }
      ENDCG
    } 
    Fallback "Diffuse"
}

编译指令

指明表面着色器使用的表面函数和光照函数,并设置可选参数。格式如下:
#pragma surface surfaceFunction lightModel [optionalparams]

  • 表面函数(surfaceFunction)
    包含属性有反射率、光滑度、透明度等。使用输入结构体Input来设置各种表面属性。
  • 光照函数
    使用表面函数的属性来模拟光照效果。Unity内置基于物理的光照模型Standard和StandardSpecular(在UnityPBSLighting.cginc中定义),非物理光照模型函数Lambert和BlinnPhong(在Lighting.cginc中定义)。

其他可选参数:
- 自定义修改函数:
- vertex:VertexFunction:顶点修改,实现一些顶点动画等。
- finalcolor:ColorFunction:最终颜色修改,实现雾效等。
- finalgbuffer:ColorFunction:延迟渲染修改,实现边缘检测等。
- finalprepass:ColorFunction:prepass base路径修改?
- 阴影:
- addshadow:添加一个阴影投射Pass。一般FallBack通用的光照模式中可以找到,对于顶点动画、透明度等需要特殊处理。
- fullforwardshadows:在前向渲染路径中支持所有光源类型的阴影。默认是只有平行光,添加这个参数就可以让点光或聚光灯有阴影渲染。
- noshadow:取消所有阴影。
- 透明度混合和测试:
- alpha、alpha:auto:透明测试。
- alpha:blend:透明混合。
- alphatest:VariableName:VariableName用来剔除不满足的片元。
- 光照:
- noambient:不应用任何环境光照或光照探针(light probe)。
- novertexlights:不应用逐顶点光照。
- noforwardadd:去掉所有前向渲染的额外Pass,即支持逐像素平行光,其他光源用逐顶点或SH计算。
- 控制代码生成:
- exclude_path:deferred, exclude_path:forward, exclude_path:prepass :不需要为特定渲染路径生成代码。


两个结构体

Input结构体(数据来源)

包含主纹理和法线纹理的采样坐标uv_MainTexuv_BumpMap。采样纹理必须以“uv”作为前缀(或用”uv2”前缀表明为次纹理坐标集合)。

Unity会背后准备好数据,可以直接使用这些数据。

SurfaceOutput(表面属性)

存储表面属性的结构体,作为光照函数输入来计算光照。

// Lighting.cginc中定义
struct SurfaceOutput
{
    fixed3 Albedo;  // diffuse color
    fixed3 Normal;  // tangent space normal, if written
    fixed3 Emission;
    half Specular;  // specular power in 0..1 range,高光反射指数部分。
    fixed Gloss;    // specular intensity,高光反射强度系数。
    fixed Alpha;    // alpha for transparencies
};

// UnityPBSLighing.cginc中定义
struct SurfaceOutputStandard
{
    fixed3 Albedo;      // base (diffuse or specular) color
    fixed3 Normal;      // tangent space normal, if written
    half3 Emission;
    half Metallic;      // 0=non-metal, 1=metal
    half Smoothness;    // 0=rough, 1=smooth
    half Occlusion;     // occlusion (default 1)
    fixed Alpha;        // alpha for transparencies
};
struct SurfaceOutputStandardSpecular
{
    fixed3 Albedo;      // diffuse color
    fixed3 Specular;    // specular color
    fixed3 Normal;      // tangent space normal, if written
    half3 Emission;
    half Smoothness;    // 0=rough, 1=smooth
    half Occlusion;     // occlusion (default 1)
    fixed Alpha;        // alpha for transparencies
};
作者:l773575310 发表于2017/11/27 22:12:19 原文链接
阅读:33 评论:0 查看评论

Redis 持久化之RDB和AOF

$
0
0

Redis 持久化之RDB和AOF

Redis 有两种持久化方案,RDB (Redis DataBase)和 AOF (Append Only File)。如果你想快速了解和使用RDB和AOF,可以直接跳到文章底部看总结。本章节通过配置文件,触发快照的方式,恢复数据的操作,命令操作演示,优缺点来学习 Redis 的重点知识持久化

RDB 详解

RDB 是 Redis 默认的持久化方案。在指定的时间间隔内,执行指定次数的写操作,则会将内存中的数据写入到磁盘中。即在指定目录下生成一个dump.rdb文件。Redis 重启会通过加载dump.rdb文件恢复数据。

从配置文件了解RDB

打开 redis.conf 文件,找到 SNAPSHOTTING 对应内容
1 RDB核心规则配置(重点)

save <seconds> <changes>
# save ""
save 900 1
save 300 10
save 60 10000

解说:save <指定时间间隔> <执行指定次数更新操作>,满足条件就将内存中的数据同步到硬盘中。官方出厂配置默认是 900秒内有1个更改,300秒内有10个更改以及60秒内有10000个更改,则将内存中的数据快照写入磁盘。
若不想用RDB方案,可以把 save “” 的注释打开,下面三个注释。

2 指定本地数据库文件名,一般采用默认的 dump.rdb

dbfilename dump.rdb

3 指定本地数据库存放目录,一般也用默认配置

dir ./

4 默认开启数据压缩

rdbcompression yes

解说:配置存储至本地数据库时是否压缩数据,默认为yes。Redis采用LZF压缩方式,但占用了一点CPU的时间。若关闭该选项,但会导致数据库文件变的巨大。建议开启。

触发RDB快照

1 在指定的时间间隔内,执行指定次数的写操作
2 执行save(阻塞, 只管保存快照,其他的等待) 或者是bgsave (异步)命令
3 执行flushall 命令,清空数据库所有数据,意义不大。
4 执行shutdown 命令,保证服务器正常关闭且不丢失任何数据,意义…也不大。

通过RDB文件恢复数据

将dump.rdb 文件拷贝到redis的安装目录的bin目录下,重启redis服务即可。在实际开发中,一般会考虑到物理机硬盘损坏情况,选择备份dump.rdb 。可以从下面的操作演示中可以体会到。

RDB 的优缺点

优点:
1 适合大规模的数据恢复。
2 如果业务对数据完整性和一致性要求不高,RDB是很好的选择。

缺点:
1 数据的完整性和一致性不高,因为RDB可能在最后一次备份时宕机了。
2 备份时占用内存,因为Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件(此时内存中的数据是原来的两倍哦),最后再将临时文件替换之前的备份文件。
所以Redis 的持久化和数据的恢复要选择在夜深人静的时候执行是比较合理的。

操作演示

[root@itdragon bin]# vim redis.conf
save 900 1
save 120 5
save 60 10000
[root@itdragon bin]# ./redis-server redis.conf
[root@itdragon bin]# ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> set key2 value2
OK
127.0.0.1:6379> set key3 value3
OK
127.0.0.1:6379> set key4 value4
OK
127.0.0.1:6379> set key5 value5
OK
127.0.0.1:6379> set key6 value6
OK
127.0.0.1:6379> SHUTDOWN
not connected> QUIT
[root@itdragon bin]# cp dump.rdb dump_bk.rdb
[root@itdragon bin]# ./redis-server redis.conf
[root@itdragon bin]# ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> FLUSHALL 
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> SHUTDOWN
not connected> QUIT
[root@itdragon bin]# cp dump_bk.rdb  dump.rdb
cp: overwrite `dump.rdb'? y
[root@itdragon bin]# ./redis-server redis.conf
[root@itdragon bin]# ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> keys *
1) "key5"
2) "key1"
3) "key3"
4) "key4"
5) "key6"
6) "key2"

第一步:vim 修改持久化配置时间,120秒内修改5次则持久化一次。
第二步:重启服务使配置生效。
第三步:分别set 5个key,过两分钟后,在bin的当前目录下会自动生产一个dump.rdb文件。(set key6 是为了验证shutdown有触发RDB快照的作用)
第四步:将当前的dump.rdb 备份一份(模拟线上工作)。
第五步:执行FLUSHALL命令清空数据库数据(模拟数据丢失)。
第六步:重启Redis服务,恢复数据…..咦????( ′◔ ‸◔`)。数据是空的????这是因为FLUSHALL也有触发RDB快照的功能。
第七步:将备份的 dump_bk.rdb 替换 dump.rdb 然后重新Redis。

注意点:SHUTDOWN 和 FLUSHALL 命令都会触发RDB快照,这是一个坑,请大家注意。

其他命令:

  • keys * 匹配数据库中所有 key
  • save 阻塞触发RDB快照,使其备份数据
  • FLUSHALL 清空整个 Redis 服务器的数据(几乎不用)
  • SHUTDOWN 关机走人(很少用)

AOF 详解

AOF :Redis 默认不开启。它的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

从配置文件了解AOF

打开 redis.conf 文件,找到 APPEND ONLY MODE 对应内容
1 redis 默认关闭,开启需要手动把no改为yes

appendonly yes

2 指定本地数据库文件名,默认值为 appendonly.aof

appendfilename "appendonly.aof"

3 指定更新日志条件

# appendfsync always
appendfsync everysec
# appendfsync no

解说:
always:同步持久化,每次发生数据变化会立刻写入到磁盘中。性能较差当数据完整性比较好(慢,安全)
everysec:出厂默认推荐,每秒异步记录一次(默认值)
no:不同步

4 配置重写触发机制

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

解说:当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发。一般都设置为3G,64M太小了。

触发AOF快照

根据配置文件触发,可以是每次执行触发,可以是每秒触发,可以不同步。

根据AOF文件恢复数据

正常情况下,将appendonly.aof 文件拷贝到redis的安装目录的bin目录下,重启redis服务即可。但在实际开发中,可能因为某些原因导致appendonly.aof 文件格式异常,从而导致数据还原失败,可以通过命令redis-check-aof –fix appendonly.aof 进行修复 。从下面的操作演示中体会。

AOF的重写机制

前面也说到了,AOF的工作原理是将写操作追加到文件中,文件的冗余内容会越来越多。所以聪明的 Redis 新增了重写机制。当AOF文件的大小超过所设定的阈值时,Redis就会对AOF文件的内容压缩。

重写的原理:Redis 会fork出一条新进程,读取内存中的数据,并重新写到一个临时文件中。并没有读取旧文件(你都那么大了,我还去读你??? o(゚Д゚)っ傻啊!)。最后替换旧的aof文件。

触发机制:当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发。这里的“一倍”和“64M” 可以通过配置文件修改。

AOF 的优缺点

优点:数据的完整性和一致性更高
缺点:因为AOF记录的内容多,文件会越来越大,数据恢复也会越来越慢。

操作演示

[root@itdragon bin]# vim appendonly.aof
appendonly yes
[root@itdragon bin]# ./redis-server redis.conf
[root@itdragon bin]# ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set keyAOf valueAof
OK
127.0.0.1:6379> FLUSHALL 
OK
127.0.0.1:6379> SHUTDOWN
not connected> QUIT
[root@itdragon bin]# ./redis-server redis.conf
[root@itdragon bin]# ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> keys *
1) "keyAOf"
127.0.0.1:6379> SHUTDOWN
not connected> QUIT
[root@itdragon bin]# vim appendonly.aof
fjewofjwojfoewifjowejfwf
[root@itdragon bin]# ./redis-server redis.conf
[root@itdragon bin]# ./redis-cli -h 127.0.0.1 -p 6379
Could not connect to Redis at 127.0.0.1:6379: Connection refused
not connected> QUIT
[root@itdragon bin]# redis-check-aof --fix appendonly.aof 
'x              3e: Expected prefix '*', got: '
AOF analyzed: size=92, ok_up_to=62, diff=30
This will shrink the AOF from 92 bytes, with 30 bytes, to 62 bytes
Continue? [y/N]: y
Successfully truncated AOF
[root@itdragon bin]# ./redis-server redis.conf
[root@itdragon bin]# ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> keys *
1) "keyAOf"

第一步:修改配置文件,开启AOF持久化配置。
第二步:重启Redis服务,并进入Redis 自带的客户端中。
第三步:保存值,然后模拟数据丢失,关闭Redis服务。
第四步:重启服务,发现数据恢复了。(额外提一点:有教程显示FLUSHALL 命令会被写入AOF文件中,导致数据恢复失败。我安装的是redis-4.0.2没有遇到这个问题)。
第五步:修改appendonly.aof,模拟文件异常情况。
第六步:重启 Redis 服务失败。这同时也说明了,RDB和AOF可以同时存在,且优先加载AOF文件。
第七步:校验appendonly.aof 文件。重启Redis 服务后正常。

补充点:aof 的校验是通过 redis-check-aof 文件,那么rdb 的校验是不是可以通过 redis-check-rdb 文件呢???

总结

  1. Redis 默认开启RDB持久化方式,在指定的时间间隔内,执行指定次数的写操作,则将内存中的数据写入到磁盘中。
  2. RDB 持久化适合大规模的数据恢复但它的数据一致性和完整性较差。
  3. Redis 需要手动开启AOF持久化方式,默认是每秒将写操作日志追加到AOF文件中。
  4. AOF 的数据完整性比RDB高,但记录内容多了,会影响数据恢复的效率。
  5. Redis 针对 AOF文件大的问题,提供重写的瘦身机制。
  6. 若只打算用Redis 做缓存,可以关闭持久化。
  7. 若打算使用Redis 的持久化。建议RDB和AOF都开启。其实RDB更适合做数据的备份,留一后手。AOF出问题了,还有RDB。

到这里Redis 的持久化就介绍完了,有什么不对的地方可以指出。
Redis 快速入门:http://blog.csdn.net/qq_19558705/article/details/78624428

作者:qq_19558705 发表于2017/11/27 22:29:00 原文链接
阅读:1 评论:0 查看评论

Java并发编程札记-(三)JUC原子类-02单个变量的原子类

$
0
0

今天学习AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference,这几个类的共同特点是都提供单个变量的原子方式访问和更新功能。下面以AtomicLong为代表,对这些类进行介绍。

AtomicLong可以看做是用原子方式更新的long值,实例提供long类型单个变量的原子方式访问和更新功能。

API

//构造方法摘要
        AtomicLong() 
          //创建具有初始值0的新AtomicLong。
        AtomicLong(long initialValue) 
          //创建具有给定初始值的新AtomicLong。
//方法摘要
long    addAndGet(long delta)
         //以原子方式将给定值添加到当前值。
boolean compareAndSet(long expect, long update)
         //如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
long    decrementAndGet()
         //以原子方式将当前值减 1。
double  doubleValue()
         //以 double 形式返回指定的数值。
float   floatValue()
         //以 float 形式返回指定的数值。
long    get()
         //获取当前值。
long    getAndAdd(long delta)
         //以原子方式将给定值添加到当前值。
long    getAndDecrement()
         //以原子方式将当前值减 1。
long    getAndIncrement()
         //以原子方式将当前值加 1。
long    getAndSet(long newValue)
         //以原子方式设置为给定值,并返回旧值。
long    incrementAndGet()
         //以原子方式将当前值加 1。
int     intValue()
         //以 int 形式返回指定的数值。
void    lazySet(long newValue)
         //最后设置为给定值。
long    longValue()
         //以 long 形式返回指定的数值。
void    set(long newValue)
         //设置为给定值。
String  toString()
         //返回当前值的字符串表示形式。
boolean weakCompareAndSet(long expect, long update)
         //如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
long    getAndUpdate(LongUnaryOperator updateFunction)
         //将当前值以原子方式更新为updateFunction方法的结果,并返回更新前的值
long    updateAndGet(LongUnaryOperator updateFunction)
         //将当前值以原子方式更新为updateFunction方法的结果,并返回更新后的值
long    getAndAccumulate(long x,LongBinaryOperator accumulatorFunction)
         //将当前值以原子方式更新为updateFunction方法的结果(方法参数为x和当前值),并返回更新前的值
long    accumulateAndGet(long x,LongBinaryOperator accumulatorFunction)
         //将当前值以原子方式更新为updateFunction方法的结果(方法参数为x和当前值),并返回更新后的值

例1:long型变量的原子访问和更新

通常情况下,在Java中的++i或者–i不是线程安全的。一般情况下,只能加锁才能保证上述操作的原子性。有了AtomicLong后,使用AtomicLong就可以保证上述操作的原子性。

Counter是一个计数器类。

class Counter extends Thread {
    private static long counter = 0;

    public static long addOne() {
        return ++counter;
    }
}

在多线程环境下测试其是否可用。

public class AtomicLongDemo {

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread() {
                public void run() {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (Counter.addOne() == 100) {
                        System.out.println("计数器值最终值为100");
                    }
                }
            };
            thread.start();
        }
    }
}

测试程序在连续运行100次++counter后,判断计数器值是否为100,如果为100就打印计数器值最终值为100,否则就什么都不打印。
数次运行程序后,发现大多数结果是什么都没有打印,说明次计数器在多线程环境下不可用。
修改计数器类。

import java.util.concurrent.atomic.AtomicLong;

class Counter {
    private static AtomicLong counter = new AtomicLong(0);

    public static long addOne() {
        return counter.incrementAndGet();
    }
}

数次运行程序后,发现结果全部为计数器值最终值为100

AtomicBoolean、AtomicInteger、AtomicReference与AtomicLong很相似,就不多做介绍了。

本文就讲到这里,想了解Java并发编程更多内容请参考:

END.

作者:panweiwei1994 发表于2017/11/27 22:44:28 原文链接
阅读:35 评论:0 查看评论

poj2823 单调队列 从入门到放弃

$
0
0
Sliding Window
Time Limit: 12000MS   Memory Limit: 65536K
Total Submissions: 64041   Accepted: 18262
Case Time Limit: 5000MS

Description

An array of size n ≤ 106 is given to you. There is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves rightwards by one position. Following is an example: 
The array is [1 3 -1 -3 5 3 6 7], and k is 3.
Window position Minimum value Maximum value
[1  3  -1] -3  5  3  6  7  -1 3
 1 [3  -1  -3] 5  3  6  7  -3 3
 1  3 [-1  -3  5] 3  6  7  -3 5
 1  3  -1 [-3  5  3] 6  7  -3 5
 1  3  -1  -3 [5  3  6] 7  3 6
 1  3  -1  -3  5 [3  6  7] 3 7

Your task is to determine the maximum and minimum values in the sliding window at each position. 

Input

The input consists of two lines. The first line contains two integers n and k which are the lengths of the array and the sliding window. There are n integers in the second line. 

Output

There are two lines in the output. The first line gives the minimum values in the window at each position, from left to right, respectively. The second line gives the maximum values. 

Sample Input

8 3
1 3 -1 -3 5 3 6 7

Sample Output

-1 -3 -3 -3 3 3
3 3 5 5 6 7

题意:给n个数,长度为k的窗口以此右移,求每个窗口中的最大值和最小值。

分析:单调队列,也是醉了,之前根本没听说过。从网上浏览众大神的分析后,学会了一点皮毛。在这里记录下自己的理解。

单调队列思考:
对于一个单调递增队列,每次从队尾插值,从队头取值。//这题不仅数值单调递增,时间(下标)也单调递增。
队尾维护当前状态最大值,所以插值的时候把队列中较大的值踢掉,这些值已经过时,随便删没问题。
队头维护当前状态最小值,所以查找的时候需要往右找到第一个符合条件的值。

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <stack>
#include <queue>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
#include <vector>
#include <bitset>
#include <list>
#include <sstream>
#include <set>
#include <functional>
using namespace std;

#define maxn 1000001
int n,k;
int a[maxn],ans[maxn],t[maxn];
int front,rear;

void get(int flag)
{
	front = rear = 0;
	//先把前面k-1个放入栈中
	for (int i = 0; i < k-1; ++i){
		while(rear>front && ((!flag && a[i]<ans[rear-1])||(flag && a[i] > ans[rear-1]))) rear--;
		ans[rear] = a[i];
		t[rear++] = i;
		while(t[front] <= i-k) front++;
	}
	//从第一个框开始判断
	for (int i = k-1; i < n; ++i){
		while(rear>front && ((!flag && a[i]<ans[rear-1])||(flag && a[i] > ans[rear-1]))) rear--;//队尾插值
		ans[rear] = a[i];
		t[rear++] = i;
		while(t[front] <= i-k) front++;//队头取值
		printf("%d ",ans[front],i == n-1?'\n':' ');
	}
}

int main(int argc, char const *argv[])
{
	scanf("%d%d",&n,&k);
	for (int i = 0; i < n; ++i) scanf("%d",&a[i]);
	get(0);//单调递增队列,维护对头最小值
	get(1);//单调递减队列,维护对头最大值
}


作者:xxiaobaib 发表于2017/11/27 23:21:08 原文链接
阅读:32 评论:0 查看评论

Python3与OpenCV3.3 图像处理(七)--洪填充

$
0
0

一、本节简介

本节主要讲解洪填充的简单使用,以及洪填充的概念


二、什么是洪填充

泛洪填充算法又称洪水填充算法是在很多图形绘制软件中常用的填充算法,最熟悉不过就是

windows paint的油漆桶功能。算法的原理很简单,就是从一个点开始附近像素点,填充成新

的颜色,直到封闭区域内的所有像素点都被填充新颜色为止。泛红填充实现最常见有四邻域

像素填充法,八邻域像素填充法,基于扫描线的像素填充方法。根据实现又可以分为递归与

非递归(基于栈)。



三、示例

按照惯例,通过示例来看一下洪填充的知识点,讲解的内容依然在注释里展现

import cv2 as cv
import numpy as np

def fill_color_demo(image):
    """
    漫水填充:会改变图像
    """

    #复制图片
    copyImg=image.copy()
    #获取图片的高和宽
    h,w =image.shape[:2]

    #创建一个h+2,w+2的遮罩层,
    #这里需要注意,OpenCV的默认规定,
    # 遮罩层的shape必须是h+2,w+2并且必须是单通道8位,具体原因我也不是很清楚。
    mask=np.zeros([h+2,w+2],np.uint8)

    #这里执行漫水填充,参数代表:
    #copyImg:要填充的图片
    #mask:遮罩层
    #(30,30):开始填充的位置(开始的种子点)
    #(0,255,255):填充的值,这里填充成黄色
    #(100,100,100):开始的种子点与整个图像的像素值的最大的负差值
    #(50,50,50):开始的种子点与整个图像的像素值的最大的正差值
    #cv.FLOODFILL_FIXED_RANGE:处理图像的方法,一般处理彩色图象用这个方法
    cv.floodFill(copyImg,mask,(30,30),(0,255,255),(100,100,100),(50,50,50),cv.FLOODFILL_FIXED_RANGE)
    cv.imshow("fill color",copyImg)


def fill_binary_demo():
    """
    二值填充:不改变图像,只填充遮罩层本身,忽略新的颜色值参数
    """

    #创建一个400*400的3通道unit8图片
    image=np.zeros([400,400,3],np.uint8)
    #将图片的中间区域变为白色
    image[100:300,100:300,:]=255

    cv.imshow("fill color",image)

    mask=np.ones([402,402,1],np.uint8)
    #将遮罩层变为黑色
    mask[101:301,101:301]=0
    #在图像的中间填充,颜色为红色,用FLOODFILL_MASK_ONLY方法填充
    cv.floodFill(image,mask,(200,200),(0,0,255),cv.FLOODFILL_MASK_ONLY)
    cv.imshow("filled",image)



作者:gangzhucoll 发表于2017/11/27 23:21:18 原文链接
阅读:39 评论:0 查看评论

极大似然估计

$
0
0

  在学统计学习方法的时候,里面经常提到最大似然函数,但是我一直不知道他代表什么意思,或者说每次要求参数的时候,都是先根据已有的分布连乘,然后求参数就行了。这种做法让我很困扰,今天就百度了一下, 看到了各路大神的解释,心中有些谱了,就在这里简单记录一下,尽量不涉及公式。
  极大似然估计就是利用已有样本的信息,反推最有可能导致这种情况出现的模型参数。即你手头有一堆样本,这些样本的值都已经出现,也就是找一组参数使得这些样本出现的概率值最大。因为根据你现有的样本来看,这些样本出现(发生的概率最大)才最符合逻辑。然后就是求这些样本的联合概率,也就是连乘。连乘取对数,因为取对数把乘法转换为了加法,并不会改变增减性,而且极值出现的位置也不会变,所以何乐而不为。然后再求导,一阶导数为0的点就是极值出现的地方。

  聊到极大释然函数了,顺便说说最小二乘法吧。最小二乘就是逐渐减小估计值与实际值之间的差异,计算距离有多种方法,但是最小二乘就是实际值与估计值做差然后平方相加,选用最小二乘的原因估计是求导比较方便。得到误差的算式之后,对其进行求导然后得到一阶导数为0的点更新参数即可。
  总结,极大似然就是KL散度,最小二乘就是L2正则。

作者:qq_18293213 发表于2017/11/27 23:36:56 原文链接
阅读:39 评论:0 查看评论

Wdf框架:FxDriverEntry----驱动程序的入口函数

$
0
0

    在前面的文章<Wdf框架中WdfDriverGlobals对象的创建>中简单的提到过WdfVersionBind函数的作用,但是没有来得及分析这个函数的调用处。今天得空,借这篇文章写下WdfVersionBind函数的调用者:FxDriverEntry。

    在写这篇文章前,我被WdfLdr.sys的名字误导,以为这个sys文件是内核的加载器,用于加载整个内核启动(Ldr是Loader的缩写)。所以,当时我很粗浅的认为这个驱动只在系统引导阶段才被调用,之后就靠边站了。然而事实却是,当系统启动后,只要加载基于WDF框架的驱动,就会中断在WdfVersionBind函数上。以登录到系统后,加载WinDDK中WdfSimple.sys驱动(toast驱动的WDF版本)为例:

kd> bp WDFLDR!WdfVersionBind
kd> g
Breakpoint 1 hit
WDFLDR!WdfVersionBind:
fffff802`9598d2d0 488bc4          mov     rax,rsp
kd> kb
RetAddr           : Args to Child                                                           : Call Site
fffff802`98a414a3 : ffff8881`fc86f000 ffff8881`fbf2c750 00000000`00000000 ffff327f`24cdc304 : WDFLDR!WdfVersionBind 
fffff802`5ab053e9 : 00000000`00000000 ffffd381`a819d460 ffff8881`fbf2c750 ffffffff`00000000 : wdfsimple!FxDriverEntryWorker+0x7f
fffff802`5abab13e : 00000000`00000000 00000000`00000000 00000000`00000004 ffffc285`00000004 : nt!IopLoadDriver+0x521
fffff802`5ab04495 : ffffd381`a819da01 ffffd381`a819d6c8 00000000`c0000000 ffffd381`a819d6a4 : nt!PipCallDriverAddDeviceQueryRoutine+0x1b6
fffff802`5ab03e93 : 00000000`00000000 00000000`00000014 00000000`c0000034 ffff8881`fbd6cd30 : nt!PnpCallDriverQueryServiceHelper+0xbd
fffff802`5ab0323d : ffff8881`fbd6cd30 ffffd381`a819d8e0 ffff8881`fbd6cd30 00000000`00000000 : nt!PipCallDriverAddDevice+0x317
fffff802`5acd5eda : ffff8881`fbd6cd30 00000000`00000001 ffffd381`a819db19 fffff802`5ab0397b : nt!PipProcessDevNodeTree+0x1cd
fffff802`5a824d9e : 00000001`00000003 00000000`00000000 ffffd381`a819daf0 00000000`00000000 : nt!PiRestartDevice+0xba
fffff802`5a6e0d79 : ffff8881`f97af040 fffff802`5a9a5320 fffff802`5aa46280 fffff802`5aa46280 : nt! ?? ::FNODOBFM::`string'+0x42bde
fffff802`5a7254bd : fffff802`5a9cb180 00000000`00000080 ffff8881`f88af6c0 ffff8881`f97af040 : nt!ExpWorkerThread+0xe9
fffff802`5a7d8456 : fffff802`5a9cb180 ffff8881`f97af040 fffff802`5a72547c 43ffff41`04080003 : nt!PspSystemThreadStartup+0x41
00000000`00000000 : ffffd381`a819e000 ffffd381`a8198000 00000000`00000000 00000000`00000000 : nt!KxStartSystemThread+0x16
图1:

图1结合调试输出显示,在登录系统后,加载驱动也会中断到WdfVersionBind。由此可见,OS会为每个Wdf驱动设置WDF框架的版本号等信息。这些内容属于上一篇文章的延伸,我要借助它引出这篇文章的主题:FxDriverEntry。

    上面的函数调用栈中只有frame 1与我们的驱动模块有关,但名字却是FxDriverEntryWorker。整份Sample源码中没有调用这个函数,可见,它是由编译器插入的。这就又引出2个疑问:

1.驱动入口DriverEntry在FxDriverEntryWorker函数执行完后调用;

2.反之,驱动入口DriverEntry在FxDriverEntryWorker函数之前执行。

如果属于情况2,可能我要重启或者重新加载驱动。怕麻烦的我最终选择用IDA加载wdfsimple.sys分析了函数流程。下图是在IDA中对DriverEntry函数执行"XRefs graph to"后生成的调用图,调用次序依次为FxDriverEntry->FxDriverEntryWorker->DriverEntry:


    FxDriverEntry只是一个包装函数,真正的工作全交由FxDriverEntryWorker完成。下面的代码是IDA对FxDriverEntryWorker函数反汇编后生成的伪代码。FxDriverEntryWorker首先为WdfSimple驱动对象DriverObject调用WdfVersionBind,设置Wdf框架信息并关联WdfDriverGlobals这个重要的全局变量。如果WdfVersionBind调用成功,则会调用DriverEntry进入我们程序的入口。这才是原来WDM模型中驱动程序的入口。

int __fastcall FxDriverEntryWorker(_DRIVER_OBJECT *DriverObject, _UNICODE_STRING *RegistryPath)
{
  _UNICODE_STRING *v2; // rsi@1
  _DRIVER_OBJECT *v3; // rdi@1
  int result; // eax@2
  int v5; // ebx@4
  void (__cdecl *v6)(_DRIVER_OBJECT *); // rax@7

  v2 = RegistryPath;
  v3 = DriverObject;
  if ( DriverObject )
  {
    result = WdfVersionBind_0(v3, &WdfDriverStubRegistryPath, &WdfBindInfo, &WdfDriverGlobals);
    if ( result >= 0 )
    {
      v5 = FxStubBindClasses(&WdfBindInfo);
      if ( v5 < 0 || (FxStubInitTypes(), v5 = DriverEntry(v3, v2), v5 < 0) )  //WdfVersionBind执行成功后,调用DrvierEntry
      {
      }
    本篇完



作者:lixiangminghate 发表于2017/11/27 23:40:01 原文链接
阅读:3 评论:0 查看评论

UIAutomator2.0详解(UIDevice篇----获取控件)

$
0
0

UIDevice提供了3个获取控件的方法,和一个判断控件是否存在的方法。

public UiObject findObject(UiSelector selector)
public UiObject2 findObject(BySelector selector)
public List<UiObject2> findObjects(BySelector selector)
public boolean hasObject(BySelector selector)

在UIAutomator2.0中,控件类型有两种,UIObject和UIObject2。而对应的传参也有两种,分别为UISelect和BySelector。对于四者的关系,前文(http://blog.csdn.net/daihuimaozideren/article/details/78625099)已经说过,这里不再重复。本文仅简单记录一下四种方法的使用。

还是用简单的示例来演示。核心代码如下:

public class UIObjectTest extends UIDeviceTest {

    private String text_Text="input";

    private String mPackageName="com.breakloop.test";

    private String mActivityName=".MainActivity";

    private long timeout=2000l;

    @Before
    public void start(){
        Utils.startAPP(mDevice,mPackageName,mActivityName);
        waitForAMoment();
    }

    private void waitForAMoment(){
        mDevice.waitForWindowUpdate(mPackageName,timeout);
    }

    @After
    public void end(){
        Utils.closeAPP(mDevice,mPackageName);
        waitForAMoment();
    }

    @Test
    public void test1(){
        UiObject2 uiObject2=null;
        UiObject uiObject=null;
        List<UiObject2> uiObject2List=null;
        boolean result=false;
        String input1="hello";
        String input2="world";

        Log.i(TAG, "start");
        result=mDevice.hasObject(By.textStartsWith(text_Text));
        Log.i(TAG, (result?"":"Do Not ")+"find UI start with "+text_Text);
        waitForAMoment();

        uiObject2=mDevice.findObject(By.textStartsWith(text_Text));
        if(uiObject2!=null){
            uiObject2.setText(input1);
            waitForAMoment();
            uiObject2.setText(text_Text);
        }
        waitForAMoment();

        uiObject2List=mDevice.findObjects(By.textStartsWith(text_Text));
        if(uiObject2List!=null){
            Log.i(TAG, "find "+uiObject2List.size()+" items");
            for (UiObject2 item :
                    uiObject2List) {
                item.setText(input2);
                waitForAMoment();
            }
        }

        uiObject=mDevice.findObject(new UiSelector().textStartsWith(input2));
        if(uiObject!=null){
            Log.i(TAG, "find UIObject by text "+input2);
            uiObject2.setText(input1);
        }
        waitForAMoment();
        Log.i(TAG, "end");        
    }
}

执行效果如图:

这里写图片描述

执行结果如下:

11-27 23:33:40.544 I/com.breakloop.u2demo.uidevice.UIObjectTest: start
11-27 23:33:40.613 I/com.breakloop.u2demo.uidevice.UIObjectTest: find UI start with input
11-27 23:33:47.611 I/com.breakloop.u2demo.uidevice.UIObjectTest: find 2 items
11-27 23:33:51.699 I/com.breakloop.u2demo.uidevice.UIObjectTest: find UIObject by text world
11-27 23:33:53.761 I/com.breakloop.u2demo.uidevice.UIObjectTest: end

需要指出的是:

这里写图片描述

(1)如果有多个符合条件的控件存在,findObject只返回从根目录开始遍历所找到的第一个控件对象。

(2)以txt为遍历依据的查询条件,对hint同样生效。

当然,获取控件的方法,取决于遍历条件。如何用好By和UISelector则是关键了。

作者:daihuimaozideren 发表于2017/11/27 23:43:35 原文链接
阅读:3 评论:0 查看评论

常用的排序算法性能分析(1)—— 选择排序、插入排序、希尔排序

$
0
0

规则


排序成本模型:在研究排序算法时,我们需要计算比较和交换的数量。对于不交换元素的算法,我们会计算访问数组的次数。

排序算法可以分为两类:

  • 除了函数调用所需的栈和固定数目的实例变量之外无需额外内存的原地排序算法
  • 需要额外内存空间来存储另一份数组副本的其他排序算法

选择排序


  • 首先,找到数组中最小的那个元素。
  • 其次,将它和数组的第一个元素交换位置。(如果第一个元素最小,那么它和自己交换位置)
  • 再次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。
  • 如此往复,不断在剩余元素中选出最小者,直到将整个数组排序。

命题A:对于长度为 N 的数组,选择排序大约需要 N^2/2 次比较和 N 次交换。

选择排序的特点:

  1. 运行时间和输入无关。

    已经有序的数组或是主键全部相等的数组和一个元素随机排序的数组所用的排序时间竟然一样长!
    
  2. 数据移动次数最少。

    交换次数和数组的大小是线性关系。
    

插入排序


为了给要插入的元素腾出空间,需要将其余所有元素都在插入之前向右移动一位。

插入排序所需的时间取决于输入元素的初始顺序。

命题B:对于随机排列的长度为 N 且主键不重复的数组,

  • 平均情况下插入排序需要 ~N^2/4 次比较 以及 ~N^2/4 次交换。
  • 最坏情况下需要 ~N^2/2 次比较和 ~N^2/2 次交换。
  • 最好情况下需要 N-1次比较和 0 次交换。

典型的部分有序的数组:

  • 数组中每个元素距离它的最终位置都不远
  • 一个有序的大数组接一个小数组
  • 数组中只有几个元素位置不正确

命题C:插入排序需要的交换操作和数组中倒置的数量相同,需要的比较次数 >= 倒置的数量,<= 倒置数量加上数组大小-1。

插入排序对于部分有序数组小规模数组十分高效。


希尔排序


希尔排序交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。

希尔排序的思想是使用数组中任意间隔为 h 的元素都是有序的。这样的数组被成为 h 有序数组。

排序之初,各个子数组都很短,排序之后子数组都是部分有序的,这两种情况都很适合插入排序。

希尔排序可以用于大型数组,对任意排序(不一定是随机的)的数组表现也很好。

希尔排序的运行时间达不到平方级别。最坏情况下,比较次数和 N^(3/2) 成正比。

希尔排序对于中等大小的数组的运行时间是可接受的。代码量很小,而且不会占用额外内存。


这里写图片描述

作者:HeatDeath 发表于2017/11/28 8:54:08 原文链接
阅读:48 评论:0 查看评论

n元语法

$
0
0

语言模型

在统计自然语言处理中语言模型是很重要的一块,常用的语言模型是n元语法模型,当然现在比较流行的还有神经网络语言模型。n元语法模型需要额外的平滑处理,而神经网络语言模型则不必,它自带平滑效果。

n元语法

n元语法(n-gram)是NLP中很重要的统计语言模型,简单来说就是计算某个句子出现的概率,比如“我今天上班迟到了”这句话在整个语言下的概率,一般我们会通过一个大的语料库来进行统计。

用数学语言来描述,假设我们的句子为s=w1,w2,...,wt,则该句子的概率为

P(s)=P(w1,w2,...,wt)=P(w1)P(w2|w1)P(w3|w1,w2)...P(wt|w1,w2,...,wt1)

其中P(w1)表示第一个词w1出现的概率;P(w2|w1)是第一个词出现的前提下第二个词出现的概率;以此类推。比如s=“我今天上班迟到了”,那么P(s)=P(我)P(今天|我)P(上班|我,今天)P(迟到了|我,今天,上班)。

如果严格按照上述公式计算概率,假设总词汇大小为 N,那么第 t 个词的概率 要考虑 Nt1种情况,当 N 较大时,句子长度也较长时,将产生一个天文数字的自有参数,无法计算。

上述的语言模型的参数空间太大而且数据稀疏,导致在实际中基本无法使用。所以我们需要一个假设,某个词出现的概率只与它前面的一个或几个词有关,这个假设就是马尔科夫假设。

  • 当 n=1 时,称为一元语法,被记为unigram,此时第 i 个词出现的概率完全独立与之前的情况。
  • 当 n=2 时,称为二元语法,被称为一阶马尔科夫链,记为bigram,此时第 i 个词出现的概率与它的前一个词有关。
  • 当 n=3 时,称为三元语法,被称为二阶马尔科夫链,记作trigram,此时第 i 个词出现的概率与它的前两个词有关。

有了上面的假设,问题就变简单了,对于二元语法,某个句子出现的概率就可以用下面表示

P(s)=P(w1,w2,...,wt)P(w1)P(w2|w1)P(w3|w2)...P(wt|wt1)

实际应用中 n 取3比较多,取太大仍然存在自由参数太多问题。另外为了使当 t=1 时上述公式仍然有意义,可以在句子面前加上一个句子起始标记,而结尾也可以添加句子结束标记。

计算条件概率

可以看到 n 元语法涉及到条件概率的计算,P(wt|wt1),可直接计算wt1,wt在语料出现的频率并归一化。

p(wt|wt1)=count(wt1wt)wtcount(wt1wt)

举个例子,语料库的3个句子为“i am chinese”、“you are chinese”、“i am handsome”,那么

p(am|i)=count(i,am)wcount(i,w)=23

p(are|you)=count(you,are)wcount(you,w)=11

p(chinese|am)=count(am,chinese)wcount(am,w)=12

p(chinese|are)=count(are,chinese)wcount(are,w)=11

p(you|<BOS>)=count(<BOS>,you)wcount(<BOS>,w)=13

p(you,are,chinese)=p(you|<BOS>)p(are|you)p(chinese|are)=131111

平滑处理

为什么需要平滑处理?假如有些句子中存在一些训练语料未包含的词,或者连着的词在训练语料中未出现过。这时候就会让概率变为0,比如,计算

p(do|i)=count(i,do)wcount(i,w)=03=0

但实际上它的概率不能说是为0,所以此时就需要平滑处理。最简单的平滑处理就是加1法,假设出现的次数比实际多一次,这样就不会存在为0的情况了。

p(wt|wt1)=1+count(wt1wt)T+wtcount(wt1wt)
其中T为总词汇数量。

加1法有时效果不好,可以用其他方法处理,包括
* Good-Turing估计法
* Katz平滑法
* Jelinek-Mercer平滑法
* Kneser-Ney平滑法
* 贝叶斯平滑法
* 等等

缺点

n-gram这种处理序列信息的方式依然存在局限性,比如当n大于3时基本就无法处理了,参数空间太大。另外它不能表示词与词之间的关联性。

========广告时间========

公众号的菜单已分为“分布式”、“机器学习”、“深度学习”、“NLP”、“Java深度”、“Java并发核心”、“JDK源码”、“Tomcat内核”等,可能有一款适合你的胃口。

鄙人的新书《Tomcat内核设计剖析》已经在京东销售了,有需要的朋友可以到 https://item.jd.com/12185360.html 购买。感谢各位朋友。

为什么写《Tomcat内核设计剖析》

=========================

欢迎关注:

这里写图片描述

作者:wangyangzhizhou 发表于2017/11/28 9:16:21 原文链接
阅读:22 评论:0 查看评论

LLVM每日谈之二十六 riscv-llvm

$
0
0
说起riscv-llvm,可能一下子让人摸不到头脑,因为有些人没有关注RISC-V。如果从RISC说起,RISC-V, riscv-llvm都会变得更容易理解。RISC就是经常说的精简指令集,这在之前计算机行业的发展过程中起到的作用,怎么形容都不为过了。真正做过编译器相关和CPU相关工作的人,可能对这些有更深的感触。RISC-V简单点来说,就是一个开源版本的RISC,并且由于设计团队包含了很多之前参与设计RISC的大牛,起点很高,团队力量很强,再加上借助开源社区的力量,至少从我的角度来说,发展前景一篇光明。再加上使用的是BSD许可,未来预计会有更多的商业公司加入。
riscv-llvm是基于LLVM的一个针对RISC-V指令集的后端。众所周知,LLVM架构的前端、IR和后端的定位,后端要将IR转换为针对具体指令集的代码。而riscv-llvm可以视为LLVM的一个新的后端,这个后端所要做的就是将已经生成的IR代码,转换为支持RISC-V指令集的代码。riscv-llvm的出现,对RISC-V和LLVM都是一个好事。对于RISC来说,扩展了它的工具链,可以借助LLVM直接面对使用高级编程语言的开发者。对于LLVM来说,拓展了它所支持的指令集,可以让它支撑更多的硬件。
因为本文的聚焦点是 riscv-llvm,所以本文不深入介绍RISC-V。收集资料的时候,整理出来一些资源,具体如下:
RISC-V登场,Intel和ARM会怕吗?
为RISC-V国内爱好者服务的网站,提供双周简报
RISC-V组织官方网站
RISCV-LLVM github 地址


作者:snsn1984 发表于2017/11/28 9:58:20 原文链接
阅读:0 评论:0 查看评论

常用的排序算法性能分析(2)—— 归并排序、快速排序

$
0
0

归并排序


要将一个数组排序,可以先(递归地)将它分成两半分别排序,然后将结果归并起来。


自顶向下的归并排序


归并排序应用了分治的思想,如果它能将两个子数组排序,它就能够通过归并两个子数组来将整个数组排序。

命题F:对于长度为 N 的任意数组,自顶向下的归并排序需要 (1/2)*NlgN  到 NlgN 次比较。

命题G:对于长度为 N 的人艺术组,自顶向下的归并排序最多需要访问数组 6NlgN 次。
因为递归会使小规模问题中方法的调用过于频繁,使用插入排序处理小规模子数组(比如长度小于15)一般可以将归并排序的运行时间缩短 10%-15%。


如果 arr[mid] <= arr[mid+1],就认为数组已经是有序的,跳过 merge() 方法。这个改动不影响排序的递归调用,但是任意有序的子数组算法的运行时间就变为了线性的。


当把辅助数组 aux[] 声明为 merge() 方法的局部变量时,即使每次归并很小的数组,都需要创建新的数组,这样创建新数组将成为归并排序运行时间的主要部分。更好的解决方案是将 aux[] 变为 sort() 方法的局部变量,并将它作为参数传递给 merge() 方法。

自底向上的归并排序


自底向上的归并排序是,先归并那些微型数组,然后再成对归并得到的子数组,如此这般,直到将整个数组归并到一起。

命题H:对于长度为 N 的任意数组,自底向上的归并排序需要 (1/2)NlgN 至 NlgN 次比较,最多访问数组 6NlgN 次。

自底向上的归并排序比较适合用链表组织的数据,只需要重新组织链表的链接就能将链表原地排序。


快速排序


快速排序切分方法的内循环会用一个递增的索引将数组元素与一个定值比较。归并排序和希尔排序一般较慢的原因是,它们在内循环中移动数据。

快速排序另一个优势是它的比较次数很少。

快速排序的最好情况是每次都正好能将数组对半分。

命题K:将长度为 N 的无重复数组排序,快速排序平均需要 ~2NlnN 次比较(以及1/6的交换)。

命题L:快速排序最多需要约 N^2/2 次比较,但随即打乱数组能够预防这种情况。

在快速排序中,对于小数组,使用插入排序,在 5~15之间的人一直在大多数情况下都让人满意。

对于包含大量重复元素的数组,三向切分快速排序的排序时间从线性对数降低到了线性时间。


这里写图片描述

作者:HeatDeath 发表于2017/11/28 10:14:07 原文链接
阅读:4 评论:0 查看评论

数据库七:索引的锁和闩

$
0
0

索引的锁和闩(Locking & Latching)

索引的锁和闩

索引作用

一种能够加快数据检索速度的数据结构,但是会占用额外的读写维护操作和存储空间。

索引实现

B树系列、哈希表等等。

这些别的地方说的比较多,我就不多提了。

索引锁

对于之前疏文章中提到的那些并发控制、多版本控制等,我们知道了如何使用锁来保护数据库的数据。但是对于索引,我们需要用别的方式和方法来对待它。

尽管索引的物理结构会发生改变,但是只要索引在逻辑层次上保持一致,就能够被使用者和开发者所接受。

举个简单的例子,我们读取数据的时候,并不关心它存储的位置或者索引指向的位置是哪里,我们只需要能够读取到正确的数据即可,就算因为插入了多个其他的数据,导致索引结构的改变,我们还是可以得到正确的数据。

锁和闩

  • 高层次,抽象
  • 保护索引的逻辑内容不受其他txns的影响,从逻辑层面保护索引。
  • 以txn为单位被保持。
  • 需要能够回滚更改。

插销

  • 低层次,具体
  • 保护索引内部数据结构的关键部分不受其他线程影响,从物理层面保护索引。
  • 以operation为单位被保持。
  • 不需要能够回滚更改(或者说是不能通过回滚修改,因为底层不知道操作了啥,底层只知道有操作,知道操作的具体内容是高层的事情)。

图1

在没有锁的情况下,事务就不通过锁去访问修改数据库,而是直接通过latches进行并发控制。
在没有闩的情况下,我们可以使用类似shadow paging等技术使得指针可以对数据进行原子操作,比如compare_and_swap(用于对特定内存地址进行值确认,如果确认成功则用新值覆盖,否则不做操作),但是我们也要通过locks来进行事务的并发控制。

因此没有一个数据库可以同时缺少这两种锁。

闩的实现

Blocking OS Mutex

简单易懂;
不可扩展,速度慢,每次分配和取消锁需要大概25纳秒。

每次mutex分配锁和取消锁都不能直接操作,而是需要调用操作系统的内核函数,这就导致非常缓慢了。每次没有获得锁,则可以直接通过内核函数表明,该线程需要锁,则有schedule的内核函数会在锁空闲的时候再次请求锁。

std::mutex m; //pthread_mutex_t  futex
⋮
m.lock();
// Do something special...
m.unlock();

Test-and-Set Spinlock

快速,单个指令实现加锁减锁;
不可扩展;
对缓存不友好,假设我们有三个CPU,我们需要一直去访问一块内存地址,并将内存地址放到CPU内部的缓存中,但是因为我们每次都要请求可能变化的新值,所以我们不可能将之存储下来继续用。

typedef std::atomic<bool> atomic_flag //std::atomic<T>
std::atomic_flag latch;
⋮
while (latch.test_and_set(…)) { // 请求锁,如果没有得到,则循环;得到结束循环
// 让位? 放弃? 重试?
// 因为在上一种方法,我们通过内核函数进行规划,所以它会自动请求锁
// 而这里则需要我们自己去考虑如何去做
}

Queue-based Spinlock(Mellor-Crummey and Scott)

比Mutex更快,更好的缓存系统。

对于CPU1,访问最基础的latch,占有并锁,基础Lacth指向新产生的CPU1 Latch

图2

对于CPU2,访问最基础的latch,发现已经被占有了,就产生了类似上面的步骤

图3

依次占有

图4

Reader-Writer Locks

这种方式不能说比上一种方式好。

允许并发读
需要管理读/写队列以避免饥饿
可以在自旋锁之上实现

一个latch中包含两种锁,一把读锁,一把写锁,并且有四个计数器,表示有多少线程在占用读/写锁,有多少线程在等待读/写锁。

图5

两个线程先占有了读锁,则直接分配读取权限;再来了一个线程请求写锁,则因为读锁已经被分配所以只能等到写锁。

图6

若再来一个请求读锁的线程,则因为有线程在等待写锁,所以只能等待读锁。

图7

具体的实现和设计与实现者需要这种latch的目的有关,可能会有更加细微的差别。而且这四种锁也并不是包含了所有的锁。

Latch Crabbing

在B+树上,如果一个子节点被认为是安全的,线程可以释放父节点上的latch
安全的指的是:更新时不会拆分或合并的任何节点。

  1. 未满(插入时)
  2. 超过半满(删除时)

操作

查找:自上而下,从根开始重复如下操作

  1. 获得子节点的读锁
  2. 如果子节点是安全的,则释放父亲结点的锁

插入/删除:从根开始,然后向下,根据需要获得写锁。

  1. 一旦孩子被锁定,检查是否安全:
  2. 如果孩子是安全的,释放祖先的所有锁

具体的图示在参考文献里的PPT中有写。

索引锁

需要一种方法来保护索引的逻辑内容免受其他TXT的影响,以避免幻读。

与索引闩的差异:

  1. 锁在整个txn持续时间。
  2. 只在叶节点分配。
  3. 没有在索引数据结构中进行物理层面的存储。

锁的实现

Predicate Locks

这种锁的设计非常简单易懂,但是没有人实现了这种锁

  • SELECT查询的WHERE子句中的关键词的共享锁。
  • 在任何UPDATE,INSERT或DELETE查询的WHERE子句中对关键词进行独占锁定。

图8

在查询语句中,我们发现了关键词name = "Biggie",则我们对该表中所有name = "Biggie"的数据加了一把锁。

在插入语句中,我们发现了关键词name = "Biggie" and balance = 100,并且和上一个关键词有重复的部分,所以这个锁就是上一个锁的子集,并且不能在上一个锁结束前使用。

Key-Value Locks

只能锁单个键值的锁。
需要“虚拟键”来表示不存在的值。

图9

如,14到16之间没有值,则需要用虚拟的值来填充;但是有可能值的差距特别大,则需要非常多的空间去存储这些虚拟的值。

Gap Locks

解决了上一种锁需要虚拟键来填充的问题。

每个txn获取要访问的单个键上的键值锁定。 然后在它与下一个键的间隙上获得一个间隙锁定。

这种间隙是开区间。

图10

Key-Range Locks

将上面两种锁合起来就成了这种锁。

锁定键空间范围的锁。

  • 每个范围是从关系中出现的一个键到出现的下一个键。
  • 定义锁定模式,以便冲突表将捕获可用操作。

图11

Hierarchical Locking

允许txn使用不同的锁定模式来保持更宽的键范围锁定,从而减少锁管理器的访问次数。

图12

参考文献

作者:u013007900 发表于2017/11/28 0:30:10 原文链接
阅读:75 评论:0 查看评论

【机器学习 基本概念】从朴素贝叶斯到维特比算法:详解隐马尔科夫模型

$
0
0

本文转载自:从朴素贝叶斯到维特比算法:详解马尔科夫模型

本文将从简要介绍朴素贝叶斯开始,再将其扩展到隐马尔科夫模型。我们不仅会讨论隐马尔科夫模型的基本原理,同时还会从朴素贝叶斯的角度讨论它们间的关系与局限性。


隐马尔科夫模型是用于标注问题的统计机器学习模型,是一种生成模型。隐马尔科夫模型是关于时序的概率模型,它描述了由一个隐藏的马尔科夫链随机生成不可观测的状态随机序列,再由各个状态生成一个观测而产生观测随机序列的过程。本文将重点介绍这种经典的机器学习模型。


简介


机器学习一个经典的问题就是学习一个能区分两个或多个类别的分类器,即在给定训练样本下能预测新样本所属的类别。机器学习分类器经常用于处理 NLP 任务,例如将邮件根据内容分类为垃圾邮件或正常邮件,将新闻按内容分为不同的主题等。但除了这种分类任务,NLP 还有很多都涉及到另一种与结构有关的预测,这种结构化预测一般可用概率图表示。


NLP 中一个经典的案例就是词性标注问题。在该任务中,x_i 表示一个个的单词,y_i 表示对应 x_i 的词性(如名词、动词和形容词等)。对于这种任务来说,输入的和输出都是一个序列,即给定一个单词序列,模型的输出为对应单词的标注序列,这种序列是与对应位置和上下文相关,所以是一种与结构相关的预测。


在这种序列预测问题中,数据由(x, y)的序列组成,即一个样本可描述为(x_1, x_2,...,x_m,y_1, y_2, ..., y_m)。我们要学习的是在给定一个 x 的序列下,各种 y 序列的概率是多少,即:


在大多数问题中,这些序列具有顺序相关性。即 x 近邻的值与 y 近邻的值具有相关性,或者也可以说 x_i 的近邻决定了 y_i 的属性。例如在英语中,介词 to(x_i)后面所带的单词常常是动词(y_i)。当然在机器学习中还有其它的任务涉及序列数据,例如在时序建模中,我们需要使用所有前面的观测值 y 在 t+1 步时预测新的 y。在序列监督学习中,我们必须序列地预测所有 y 值。


隐马尔科夫模型(HMM)是第一个针对序列分类所提出的算法。当然还有其它很多的序列模型,不过本文会从朴素贝叶斯模型开始逐步扩展到 HMM。


朴素贝叶斯分类器


朴素贝叶斯(分类器)是一种生成模型,它会基于训练样本对每个可能的类别建模。在预测中,朴素贝叶斯分类器在给定一个观察样本下,它会计算所有可能类别的概率并返回最可能由观察样本生成的类别。也就是说,朴素贝叶斯分类器会预测新样本最可能生成的类别是什么。相比之下,如 Logistic 回归那样的判别模型会尝试学习训练样本中的哪些特征最可能对区分类别起作用。


朴素贝叶斯模型在给定特征下最大化后验概率而返回最可能的类别:


其中 y 为类别,x arrow 为一个观察样本的特征向量。


NB 分类器是基于贝叶斯定理的,若我们将贝叶斯定理代入到上式,那么条件概率可以写为:



在训练中,对于一个给定的观察样本,我们可以用上式计算给定观察样本下是某个类别的概率,这样迭代地可以计算所有类别的概率。又因为分母对所有项都是相同的,且不影响最大化概率的结果,上式可以简写为以下:


如果我们将向量按分量分解出来,那么原式可以写为:


这个式子非常难以计算,因为它涉及到估计所有特征可能的组合。因此,我们可以根据朴素贝叶斯假设放宽各特征的条件。朴素贝叶斯定理假设:「给定一个类别,每一个特征都与其它特征条件独立」。该假设可以表示为 p(x_i | y, x_j)=p(x_i | y),其中 i 不等于 j。在给定类别 y 的条件下,概率 p(x_i∣y) 是相互独立的,因此也就能如下简单地乘积表示联合分布: 


将上式带入后验概率可得:


上式就是我们最终得到的朴素贝叶斯模型,我们根据朴素贝叶斯假设大大地简化了计算。


训练


朴素贝叶斯的训练主要由计算特征和类别的频率而实现。以下描述的过程需要对每一个类别 y_i 都执行一次计算。为了计算先验概率,我们简单地通过计算所有样本中类别 y_i 占的比率而估计类别出现的概率:



为了计算似然度估计,我们需要计算 x_i 和 y_i 一同出现的次数占 y_i 所出现次数的比率,来估计在 y_i 出现的情况下,x_i 出现的概率:


该计算式将产生一个关于训练样本中所有类别特征的大型同现矩阵。


分类


当我们给一个新的样本进行分类,假设该样本的特征为 x_1、w_3、w_5。那么对于每一个类别 y_i,我们需要计算:



上式可以分解为:



该式需要对每一个类别 y_i 都执行一次计算,因此我们可以选出在这些特征出现的情况下,最可能出现的类别是什么。


从朴素贝叶斯到隐马尔科夫模型


前面展示的模型预测了在给定观察样本下最可能出现的类别。要想预测观察序列 x=(x_1, …, x_n) 对应的类别序列 y=(y_1, …, y_n),可以使用多个朴素贝叶斯模型的累乘而构建一个序列模型:



该模型包括两个方面:

  • 每个序列位置只有一个特征,即假设每个特征都以类 y_i 为基础独立生成时每个观察结果对应的特征。
  • 该模型不捕捉可观察变量 x_i 之间的互动。

但是,假设在连续序列位置 y_i 上存在依赖项是合理的,还记得上文中关于词性标注的示例吗?这就是一阶马尔科夫模型,其引入了马尔科夫假设:「特定状态的概率仅依赖于前一个状态」。


更常见的形式是:


其中 Y 代表所有可能的标签序列 y arrow 的集合。


隐马尔科夫模型


隐马尔科夫模型(HMM)是一个序列分类器。和其他的机器学习算法一样,它可以被训练,即给定观察结果的标注序列,然后使用学得的参数给观察结果序列分配标签。我们可以把 HMM 框架定义为包含以下组件:

  • 状态(如标签):T=t_1, t_2, …, t_N
  • 观察结果(如单词):W=w_1, w_2, …, w_N
  • 两个特殊状态:t_start 和 t_end,这两个状态与观察结果无关

和状态和观察结果相关的概率:

  • 初始概率:状态的初始概率分布
  • 最终概率:状态的最终概率分布
  • 转移概率:从一个状态到另一个状态的概率矩阵 A
  • 发射概率(emission probability):从一个状态生成的观察结果的概率矩阵 B

一阶隐马尔科夫模型具备以下假设:

  • 马尔科夫假设:特定状态的概率仅依赖于前一个状态。形式:P(t_i∣t_1, …, t_i−1)=P(t_i∣t_i−1)
  • 输出的独立性:输出观察结果 wi 的概率仅依赖于输出观察结果 ti 的状态,而不是其他状态或观察结果。形式:P(w_i∣t_1…q_i, …, q_T, o_1, …, o_i, …, o_T)=P(o_i∣q_i)

注意:输出假设和前述朴素贝叶斯分类器紧密相关。下图便于理解该假设与朴素贝叶斯分类器之间的依赖性和关系:


HMM 中的转移概率和发射概率。(图源:维吉尼亚大学 CS6501 课程)


现在我们可以定义两个用 HMM 可以解决的问题。

  • 学习和给定观察序列相关的参数,即训练。例如,给定一个句子中的单词和相关的词性标注,模型可以学到其潜在结构(latent structure)。
  • 将训练后的 HMM 用于观察结果序列。例如,给定一个句子,使用 HMM 根据训练数据学得的潜在结构预测每个单词的词性。


学习:估计转移矩阵和发射矩阵(emission matrices)


给定一个观察结果序列 W 和相关状态 T,我们如何学习 HMM 参数,即矩阵 A 和 B?


在 HHM 监督场景下,可以使用最大似然估计原则来计算矩阵,从而完成参数学习。


过程为:计算每个事件在语料库中出现的次数,然后将次数归一化以形成适当的概率分布。我们需要对每个事件在语料库中出现的次数计算 4 次:





其中,M 代表训练样本的个数,N 代表序列长度,1 代表当特定事件发生时指示函数的值为 1,0 代表特定事件未发生。该公式概览了训练数据库,计算每个事件出现的频率。


然后将全部 4 次计算归一化,以得出正确的概率分布:


这些公式将输出转移概率矩阵 A 和发射概率矩阵 B。


拉普拉斯平滑


训练过程中,该模型如何处理未见过的单词?


当未见过的单词/观察结果出现了,P(W_i∣T_i)=0,且预测过程中将会作出错误的序列决策。


有一种技术可以处理这种情况,即拉普拉斯平滑(Laplace smoothing):每个状态总有一个小发射概率要输出未见单词(可标注为 UNK)。每次 HMM 遇到未知单词,该模型将使用 P(UNK∣T_i) 的值作为发射概率。


解码:为观察序列寻找隐藏状态序列


给定一个已训练的 HNN,即转移矩阵 A 和 B 以及一个新的观察序列 W=w_1,w_2,…,w_N,我们希望找到最佳的状态序列 T=t_1,t_2,…,t_N 以解释该观察序列。


这一过程可以通过使用维特比算法(Viterbi algorithm)实现,该算法试图找到总体上最佳的状态序列 T=t_1,t_2,…,t_N。一般来说我们还可以使用另外一种后验解码的算法,该算法独立地为序列中每个位置 i 选择后验概率最高的状态。


维特比算法


维特比算法实际是用动态规划解隐马尔科夫模型的预测问题,即用动态规划求概率最大的路径,在 HMM 中,这些路径对应着一个状态序列。根据动态规划的原理,如果最优状态序列在时刻 i 通过结点 t_i,那么这一状态序列从结点 t_i 到终点 t 的部分状态序列,对于从 t_i 到 t 所有可能的部分状态序列来说,必须是最优的。因为如果不是最优的,那么我们就能从 t_i 到 t 寻找一个更好的状态序列以加大获得观察序列的概率。


定义在时刻 i 状态为 t 的所有单个路径中概率最大值为 δ,维特比算法可以通过使用马尔科夫假设和如下定义的两个函数计算上式单个路径的最大值。


如下计算每一个状态最可能的前面状态:



维特比算法使用使用一个被称之为 trellis 的 HMM 表征,它折叠了每一个位置的所有可能状态,并作出了非常明确的独立性假设:每一个位置仅依赖于前一个位置。


HMM 的 trellis 表示。


发射概率矩阵和状态迁移概率矩阵。


通过使用维特比算法,转移概率矩阵,我们可以将数据填充到 trellis 图表中,并快速高效地找到维特比路径。


将数据填入 trellis 表示中。


上图是 Roger Levy 展示的维特比算法,完全的案例可参考:http://www.davidsbatista.net/assets/documents/posts/2017-11-12-hmm_viterbi_mini_example.pdf。


HMM 的重要观察结果


  • 本文的主要思想是看到朴素贝叶斯分类器和序列分类器 HMM 的联系。
  • 如果我们使 HMM 的隐藏状态固定,则它就是朴素贝叶斯模型。
  • 序列中每个单词/观察结果只有一个特征,即每个观察结果的值。
  • 每个状态仅依赖于前一个状态,即每个状态 t_i 都独立于前面所有状态 t_1, t_2, …, t_i−2,除了紧挨着的前一个状态 t_i-1。
  • 每一个观察结果变量 w_i 仅依赖于当前状态 t_i。


软件包


  • seqlearn:适合 Python 的序列分类库,包括隐马尔科夫模型实现,它使用 sklearn API。
  • NLTK HMM:NLTK 也包括一个可以实现隐马尔科夫模型框架的模块。
  • lxmls-toolkit:在里斯本机器学习夏季课程中使用的自然语言处理工具包,也包括隐马尔科夫模型实现。


声明:本文由机器之心编译出品,原文来自David S. Batista,作者David S. Batista,转载请查看要求,机器之心对于违规侵权者保有法律追诉权。


作者:gongxifacai_believe 发表于2017/11/28 10:31:25 原文链接
阅读:53 评论:0 查看评论

【蓝桥杯】【神秘三位数】

$
0
0

题目
有这样一个3位数,组成它的3个数字阶乘之和正好等于它本身。
即:abc = a! + b! + c!
请找出所有满足要求的三位数.

分析
因为0~9一共10个数字,所以可以提前把每个数字的阶乘都求出来存在数组中。
然后循环遍历所有的三位数,找到满足条件的数字。

源码

    private static int[] a;

    public static void main(String[] args) {
        a = new int[10];
        for (int i = 0; i < a.length; i++) {
            a[i] = getN(i);
        }

        for (int i = 100; i <=999; i++) {
            if(i == f(i)){
                System.out.println(i);
            }
        }


    }

    //返回a每个位上的阶乘之和
    private static int f(int b){
        String s = String.valueOf(b);
        int sum = 0;
        for (int i = 0; i <s.length(); i++) {
            int x = s.charAt(i)-'0';
            sum += a[x];
        }
        return sum;
    }

    //返回数字a的阶乘
    private static int getN(int a){
        if(a == 0){
            return 0;
        }
        int sum = 1;
        for (int i = a; i > 1; i--) {
            sum = sum*i;
        }
        return sum;
    }

结果
145

作者:bear_huangzhen 发表于2017/11/28 10:33:27 原文链接
阅读:58 评论:0 查看评论

HBase架构设计介绍

$
0
0

概述

在不久的过去,大数据的应用越来越多。为了支持这些应用以及扩展老的应用,很多新的数据管理系统被开发出来,被称作大数据革命。这些系统中很多都是开源和社区驱动的。Apache Hbase就是这样的一个系统,是一个开源的分布式的数据库,和Google Bigtable类似。并且发展迅速,为那些需要快速随机访问的大数据应用提供了好的选择。建立在Apache Hadoop之上,并且和Hadoop紧密的集成。

HBase和传统的数据库有很大的不同之处,比如MySQL,PostGreSQL,Oracle等。在架构和提供的特性方面都有不同之处,HBase去掉了一些伸缩和灵活性的特性,这也就使得hbase拥有一个非常不同的数据模型。设计hbase的表和传统关系数据库非常不同。我会通过解释hbase数据模型以及通过一些实例来介绍hbase表的基本设计。

Hbase数据模型

hbase数据模型和关系型数据库是非常不同的。就像Bigtable描述的那样,这是一个稀疏的,分布式的,持久化的,多维的,排序的映射,索引通过行键,列键以及时间戳来实现。你可能听别人把它当做一个key-value存储结构的,或者说是面向列族的数据库。或者说是多版本映射的数据库。所有的这些描述都是正确的。这个章节,我们介绍一下这些概念。

hbase数据模型最让人可以接收的描述就是使用表,行和列。这和关系型数据库很像。但是也就是名称相似而已,行列的概念还是略有不同的,下面我们介绍一下这些概念。

表(Table)

hbase在表中组织数据。表名是字符串和字符的组合,可以在文件系统路径中使用。

行(Row)

在表中数据依赖于行来存储,行通过行键来区分。行键没有数据类型,通常是一个字节数组。

列族(Column Family)

行中的数据通过列族来组织。列族也暗示了数据的物理排列。所以列族必须预先定义,并且不容易被修改。每行都拥有相同的列族,可能有些行的数据为空。列族是字符串和字符的组合,可以在文件系统路径
中使用。

列标识(Column Qualifier)

数据在列族中的位置是通过列标识来指定的。列标识不需要预先指定,每行的列标识也不需要相同。就像行键一样,列标识没有数据类型,通常也是字节数组。

单元(Cell)

单元是行键、列族、列标识的组合。这些数据存储在单元中,被称作单元数据。数据也不需要数据类型,通常也是字节数组。

时间戳(Timestamp)

单元数据是有版本的。版本的区分就是他们的版本号,版本号默认就是时间戳。当写入数据时,如果没有指定时间,那么默认的时间就是系统的当前时间。读取数据的时候,如果没有指定时间,那么返回的就是最新的数据。保留版本的数量根据每个列族的配置。默认的版本数量是3。

hbase中的一个表就像下面这张图:
这里写图片描述

在这个图中,表包含两个列族,personal和office。每个列族都有两列。每个方格就是一个单元,行键根据字母顺序进行排序。
这些概念也通过API方式暴露给客户端。hbase的API数据管理包含三个主要方法:get,put和scan。get和put方法需要制定行键,scan操作是浏览一定范围的行。范围可以通过开始和结束的行键来指定。如果不指定那么就是浏览整个表数据。有时候,使用多维映射来理解数据模型可能更简单。多维映射就像下图所示:
这里写图片描述

行键映射一个列族的列表,列族映射一个列标识的列表,列标识映射一个时间戳的列表,每个时间戳映射一个值,也就是单元值。如果你使用行键来检索映射的数据,那么你会得到所有的列。如果你检索特定列族的数据,你会得到此列族下所有的列标识。如果你检索列标识所映射的数据,你会得到所有的时间戳以及对应的数据。hbase优化了返回数据,默认仅仅返回最新版本的数据。当然你也可以得多一个多版本返回。行键和关系数据库中的主键有相同的作用,你不能改变列的行键,换句话说就是,如果表中已经插入数据,那么personal列族中的列名不能改变它所属的行键。

就像之前提到的,你可以使用不同的方式来理解数据模型。当然你也可以使用键值的方式来理解,键就是行键,值就是列中的值,但是给定一个行键仅仅能确定一行的数据。你可以把行键,列族,列标识,时间戳都看做键。而值就是单元中的数据。当你深入入到存储层的时候,你会看到如果你想要读取一个特定单元数据的时候,你就会先得到一个数据库块,这个块也会包含其他单元的数据。下面是键值结构图:

这里写图片描述

HBase表设计基础

之前的章节已经介绍,hbase的数据模型和关系型数据库是十分不同的。那么hbase的表设计就和关系数据库表设计有很大不同之处。设计hbase表需要回答下面的问题:

  • 1.行键的结构是什么的并且要包含什么内容?
  • 2.表有多少个列族?
  • 3.列族中都要放什么数据?
  • 4.每个列族中有多少个列?
  • 5.列名是什么?尽管列名在创建表时不需要指定,你读写数据是需要用到它们。
  • 6.单元数据需要包含哪些信息?
  • 7.每个单元数据需要存储的版本数量是多少?

定义hbase表最重要的事情就是行键的结构。为了更有效的定义,首先定义访问模式是很重要的。为了定义表的结构,一些hbase特定的属性是需要考虑在内的,如下所示:

  • 1.索引仅仅依赖于Key
  • 2.表数据根据行键排序,表中的每个区域都代表了一部分行键的空间,这个区域通过开始和结束行键来指定
  • 3.hbase表中的数据都是字节数组,没有类型之分。
  • 4.原子性仅保证在行级。跨行操作不保证原子性,也就是说不存在多行事务。
  • 5.列族必须在表创建的时候就定义。
  • 6.列标识是动态的,可以在写入数据是定义。

一个好的方法去学习这些感念就是通过一个例子。下面我们使用hbase表来设计推特的用户关系(用户关注另一个用户)。关注和被关注的关系实际上就是图,使用指定的图数据库可能工作的更加高效。然而,这里只是一个使用用例来理解hbase的一些概念。首先我们来设计表的访问模式。访问模式定义如下:

读模式:

  • 1.用户关注了谁?
  • 2.是否用户A关注了用户B?
  • 3.都有谁关注了用户A?

写模式:

  • 1.用户关注了一个新的用户。
  • 2.用户取消关注。

下面我们使用递进式的方法考虑几种表设计,并看她们的优缺点。第一种设计如下:

这里写图片描述

在一行中存储被关注的用户的列表(也就是此用户都关注了谁),行键是用户ID。每一列包含关注的用户ID。表中的数据如下:

这里写图片描述

这个表设计满足读模式的1和2。但是要想获得都有谁关注了此用户是非常耗时的,需要遍历整个表。添加一个关注的用户也是很棘手的,因为没有一个计数器来表示添加的用户是第几个,除非你遍历之前添加的所有的用户,这也有些不合理。一种可用的解决方法就是使用一个额外的列来保存一个计数器。如下图所示:

这里写图片描述

当添加关注用户的时候,要实时的更新count的值。
这个设计比最早的设计要好一点,但不能解决所有的问题。取消关注也是棘手的,因为你要遍历整行的数据来找到具体要删除的用户。而且删除之后计数器将会产生漏洞。

我们之前提到过,列标识是动态的,以字节数组的方式存储。这就可以让你使用任意的数据来代表列标识。考虑一下下面的设计,在这个设计中,数量不是必须的。所以添加用户变得很简单,取消关注也是很简单的。单元数据可以存储任意的值对结果都没有影响。

这里写图片描述

这个设计解决了定义的大部分的访问模式。仅仅只有读模式3没有满足:谁关注了此用户。当前设计中,行键仅仅是用户ID,你需要浏览整个表数据来得到结果。这就让我们想到能不能在行键指出被关注的用户ID。有两种方法来解决这个问题。第一种就是维护另外一张表包含反向列表(也就是此用户关注的用户列表)。第二种方法就是在一张表中使用不同的行键来存储关注和被关注的数据。上面的两种方法,都会实现信息的分隔,以至于你可以快速的访问。

对于当前表结构还可以进一步的优化,考虑下面的设计:

这里写图片描述

有两件事需要指出:行键现在包括关注着和被关注者;列族的名称已经缩短为f。短的列族名称没有任何影响,它仅仅是为了提高IO操作。这里获得一个被关注的列表变成了一个部分浏览,而不是之前的全表浏览。取消关注和用户A是否关注用户B变成了简单的删除和get操作,而不是之前的遍历整行。这在关注者和别关注者列表很大的时候将变得很有用。表中数据如下所示:

这里写图片描述

需要注意的是行键的长度是变化的。变化的长度使得监控性能变得困难,因为来自每个请求的长度不一致。一个解决方法就是行键使用哈希值,为了得到长度一致的行键,你可以先哈希用户的id然后连接他们,而不是简单的连接在一起。在查询的时候,因为你知道你要查询的用户ID,所以你可以重新计算哈希后,在进行查询。进行哈希后的表像下面这样:

这里写图片描述

这个表设计可以有效的满足所有的访问模式。

总结

这篇文章包含了hbase的基础架构设计,开始介绍了数据模型,并介绍了在设计表结构是需要注意的事项。当然关于表设计还有很多需要去探索的地方。这篇文章的关键点如下:

  • 1.行键在表设计中非常重要,决定着应用中的交互以及提取数据的性能。
  • 2.hbase表示非常灵活的,你可以使用字节数组存储任何数据。
  • 3.存错任何数据到列族中,都可以使用相同的访问模式来访问数据。
  • 4.索引仅仅是行键,好好利用,将成为你的优势。
  • 5.深度高的表结构,可以使得你快速且简单的访问数据,但是却丢掉了原子性。宽度广的表结构,可以保证行级别的原子操作,但每行会有很多的列。
  • 6.你需要好好的思考你的表设计,使得可以使用单条API就可以操作,而不是使用多条。hbase不支持跨行的事务,也尽量避免在客户端代码中使用这样的逻辑。
  • 7.行键的哈希可以使得行键有固定的长度和更好的分布。但是却丢弃了使用字符串时的默认排序功能。
  • 8.列标识可以用来存储数据,就像单元数据一样。
  • 9.列标识的长度影响数据存储的足迹。也影响硬盘和网络IO的花销,所以应该尽量简洁。
  • 10.列族名字的长度影响到发送到客户端的数据长度。所以尽量简洁。
作者:maosijunzi 发表于2017/11/28 11:14:34 原文链接
阅读:68 评论:0 查看评论

Redis键的生存时间和过期时间

$
0
0

一、设置生存时间

        Redis作为内存数据库,和memcached一样提供了设置键的生存时间和过期时间的功能。通过expire命令或者pexpire命令实现秒级或者毫秒级的生存时间的设置功能:

  • EXPIRE <KEY> <TTL> : 将键的生存时间设为 ttl 秒
  • PEXPIRE <KEY> <TTL> :将键的生存时间设为 ttl 毫秒
  • EXPIREAT <KEY> <timestamp> :将键的过期时间设为 timestamp 所指定的秒数时间戳
  • PEXPIREAT <KEY> <timestamp>: 将键的过期时间设为 timestamp 所指定的毫秒数时间戳.

虽然有多种不同单位和不同形式的设置命令,但实际上EXPIRE、PEXPIRE、EXPIREAT、PEXPIREAT四个命令内部都是调用同一个函数实现,在db.c 文件中,四个命令对应的函数如下:

//expire命令
void expireCommand(redisClient *c) {
    expireGenericCommand(c,mstime(),UNIT_SECONDS);
}
//expiread命令
void expireatCommand(redisClient *c) {
    expireGenericCommand(c,0,UNIT_SECONDS);
}
//pexpire命令
void pexpireCommand(redisClient *c) {
    expireGenericCommand(c,mstime(),UNIT_MILLISECONDS);
}
//pexpireat命令
void pexpireatCommand(redisClient *c) {
    expireGenericCommand(c,0,UNIT_MILLISECONDS);
}

        可以看到以上四条命令最终都调用expireGenericCommand函数执行。在redis.h/redisDb的结构的expires字典保存了数据库中所有键的过期时间,这个字典称为过期字典。

  • 过期字典是一个指针,指向键空间的某个键对象
  • 过期字典的值是一个 long long 类型的整数,这个整数保存了键所指向的数据库键的过期时间–一个毫秒级的 UNIX 时间戳
typedef struct redisDb {
    // 数据库键空间,保存着数据库中的所有键值对
    dict *dict;
    // 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
    dict *expires;
    //...
} redisDb;

因此,上一节数据库键空间的基础上加上过期时间:


其键空间变更为以下状态(在实际代码中,键空间的键和过期时间的键都指向同一个键对象):


一旦键过了过期时间,再通过Redis数据库取该键值对就为空:


二、移除过期时间

        persist命令就是pexpireat命令的反操作:persist命令在过期字典中查找给定的键,并解除键值在过期字典中的关联。如果对上述键空间执行:persist message、persist book,其键空间变更为如下状态:


三、计算并返回键的剩余时间

ttl或者pttl命令可以返回秒级或者毫秒级别的键的剩余生存时间。

//ttl命令
void ttlCommand(redisClient *c) {
    ttlGenericCommand(c, 0);
}
//pttl命令
void pttlCommand(redisClient *c) {
    ttlGenericCommand(c, 1);
}
/*
 * 返回键的剩余生存时间。
 * output_ms 指定返回值的格式:
 *  - 为 1 时,返回毫秒
 *  - 为 0 时,返回秒
 */
void ttlGenericCommand(redisClient *c, int output_ms) {
    long long expire, ttl = -1;

    /* If the key does not exist at all, return -2 */
    // 取出键
    if (lookupKeyRead(c->db,c->argv[1]) == NULL) {
        addReplyLongLong(c,-2);
        return;
    }
    /* The key exists. Return -1 if it has no expire, or the actual
     * TTL value otherwise. */
    // 取出过期时间
    expire = getExpire(c->db,c->argv[1]);
    if (expire != -1) {
        // 计算剩余生存时间
        ttl = expire-mstime();
        if (ttl < 0) ttl = 0;
    }
    if (ttl == -1) {
        // 键是持久的
        addReplyLongLong(c,-1);
    } else {
        // 返回 TTL 
        // (ttl+500)/1000 计算的是渐近秒数
        addReplyLongLong(c,output_ms ? ttl : ((ttl+500)/1000));
    }
}

四、过期键的删除策略

通过过期字典,程序可以通过以下步骤检查一个键是否过期:

(1)检查给定键是否存在于过期字典中,如果存在则取出其过期时间;

(2)检查当前UNIX时间戳是否大于键的过期时间,如果是则键过期执行过期删除;

对于过期键的删除,有三种策略:

(1)定时删除:在设置键的过期时间时,创建一个定时事件,当过期时间到达,由事务处理器自动执行过期键删除的操作。定时删除策略的优点是对内存友好缺点是对CPU不友好,查找一个时间事件的时间复杂度为O(N),所以不能高效处理大量时间事件。

(2)惰性删除:每次从键空间获取键时,检查键是否过期,如果过期则删除,否则返回对应的值。惰性删除策略的优点是对CPU友好缺点是对内存不友好

(3)定期删除:每过一段时间,对数据库检查一次,删除里面的过期键。整合了定时删除和惰性删除的策略进行一定程度的折中。

在db.c/expireIfNeeded函数中可以看到所有命令在读取或写入数据库之前,都会调用expireIfNeeded对键进行检查:

int expireIfNeeded(redisDb *db, robj *key) {
    mstime_t when = getExpire(db,key);
    mstime_t now;
    if (when < 0) return 0; /* 没有设置过期时间 */
    /* 如果服务器正在加载数据,稍后再处理 */
    if (server.loading) return 0;
    ...
    /* 没有过期 */
    if (now <= when) return 0;
    /* 删除键和过期时间 */
    server.stat_expiredkeys++;
    /*将删除命令传播到 AOF 文件和附属节点*/
    propagateExpire(db,key);
    notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED,
        "expired",key,db->id);
    return dbDelete(db,key);
}

参考文献
1、http://www.redis.net.cn/tutorial/3506.html
2、《Redis设计与实现》第二版---黄健宏
3、https://github.com/xingzhexiaozhu/redis-3.0-annotated
4、http://www.yiibai.com/redis/redis_strings.html

作者:u012050154 发表于2017/11/28 11:25:46 原文链接
阅读:31 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>