利用Docker搭建一个博客

前言

对于docker最早是在天猫双十一之后的一个直播视频中看到了,当时始终觉得这是都是很牛逼的人才玩的转的东西,也就没放在心上。后来公司的同事做了一个docker的分享培训,对docker有了进一步的了解。直到最近分到一个任务——配置一个开源论坛然后部署到公司服务器上,为了避免多次部署的麻烦,就要选择docker,至此铁下心来学习docker。

1、docker是干什么的?

作为程序员不知道你有没有这种感觉,每当你要部署一套系统时都要花费大量的时间和精来来配置各种运行环境?明明在自己机器上运行好好的系统,在别人机器上出现了很多问题,然后花费大量时间和精力去调试?很难说这种方式对我们的变成能力能提高多少,那怎么解决这个问题呢?答案就是daocker,它可以让你将你的系统连带运行环境一并“打包分发”。(说的优点low,但是有助于理解)

2、docker的安装

要使用docker,肯定是先安装docker了请参考下面的文章, 这里只提醒一点,一定要使用64位的操作系统,不然会遇到很多坑。另外windows、linux和mac都可以安装docker,windows10直接支持docker的安装而之前版本的windows需要通过docker官方提供的工具;mac安装最简单;推荐使用linux环境安装。

3、docker的基础

首次接触docker,对所谓的image、container、registry、client、server等等肯定会眼花。本文抛开对这些概念的重复,先让大家动手来感受一下docker能为我们做什么?我们要做一个在tomcat环境下运行的网站,并且支持mysql数据库。

4、搭建之旅

4.1 准备工作

首先去 jpress下载一个war文件,作为我们要运行的项目。结下来是不是要搭建java环境、搭建tomcat、搭建mysql数据了?这些全是重复性的劳动,有了docker就可以省去这些繁琐的过程。

4.2 docker环境下安装tomcat

docker按住哪个tomcat环境,分如下几步:

  • 搜索tomcat的image;
  • 下载tomcat的image;
  • 运行tomcat的image.

在linux通过快捷键 control + alt + t 调出终端,通过下面命令进入root模式:

1
su

根据提示输入密码,就进入了root模式,然后启动docker服务:

1
service start docker

接着在终端输入下面命令即可执行对tomcat的image的搜索:

1
docker search tomcat

就会出现下面的搜索结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
root@afei-virtual-machine:/home/afei# docker search tomcat
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
tomcat Apache Tomcat is an open source implementa... 1407 [OK]
tomee Apache TomEE is an all-Apache Java EE cert... 38 [OK]
dordoka/tomcat Ubuntu 14.04, Oracle JDK 8 and Tomcat 8 ba... 37 [OK]
davidcaste/alpine-tomcat Apache Tomcat 7/8 using Oracle Java 7/8 wi... 19 [OK]
consol/tomcat-7.0 Tomcat 7.0.57, 8080, "admin/admin" 16 [OK]
cloudesire/tomcat Tomcat server, 6/7/8 15 [OK]
tutum/tomcat Base docker image to run a Tomcat applicat... 7
jeanblanchard/tomcat Minimal Docker image with Apache Tomcat 7
andreptb/tomcat Debian Jessie based image with Apache Tomc... 7 [OK]
fbrx/tomcat Minimal Tomcat image based on Alpine Linux 4 [OK]
bitnami/tomcat Bitnami Tomcat Docker Image 3 [OK]
picoded/tomcat tomcat 8 with java 8, and MANAGER_USER / M... 2 [OK]
antoineco/tomcat-mod_cluster Apache Tomcat with JBoss mod_cluster 1 [OK]
qasymphony/tomcat Tomcat images 1
rstiller/tomcat Java 1.6, 1.7, 1.8 (OpenJDK & Oracle) for ... 1
dreaminsun/tomcat optimized tomcat 1 [OK]
foobot/tomcat 0 [OK]
s390x/tomcat Apache Tomcat is an open source implementa... 0
awscory/tomcat tomcat 0
ppc64le/tomcat Apache Tomcat is an open source implementa... 0
produtec/tomcat Oracle JDK and Tomcat 0
techangels/tomcat 0
i386/tomcat Apache Tomcat is an open source implementa... 0
hegand/tomcat docker-tomcat 0 [OK]
antoineco/tomcat Extra OS variants for the official Tomcat ... 0 [OK]
root@afei-virtual-machine:/home/afei# ^C

你可以选择上面的任意一个image进行下载,在此我们下载第一个名为tomcat的image,执行下面的命令:

1
docker pull tomcat

然后等待,直到成功的提示出现,我们的tomcat的环境就下载好了,当然里面自动包括java环境了。

4.3 将war文件放进tomcat的image中,构建自己的image

tomcat的image下载好之后,我们就有了tomcat的运行环境了,该如何把自己的war文件放入这个tomcat的运行环境呢?首先我们进入war包所在目录:

1
2
3
4
root@ahui-virtual-machine:/# cd home/ahui/demos
root@ahui-virtual-machine:/home/ahui/demos# ls
jpress.war
root@ahui-virtual-machine:/home/ahui/demos#

在当前目录下,新建一个Dockerfile的文件:

1
root@ahui-virtual-machine:/home/ahui/demos# vim Dockerfile

在弹窗中按 i键进入编辑模式,输入:

1
2
3
4
5
from tomcat
MAINTAINER yangyahui afei_ask@163.com
COPY jpress.war /usr/local/tomcat/webapps
~
~

然后按esc键,退出编辑模式,输入下面命令退出窗口

1
:wq!

说明一下:里面的

  • “ from tomcat ” 就是选择刚才我们下载tomcat的image,如果你下载的事其它的image,就换做你下载的那个image 的名称;
  • “ MAINTAINER yangyahui afei_ask@163.com ” 是作者信息;
  • “ COPY jpress.war /usr/local/tomcat/webapps ”意思是将当前目录下的jpress.war文件拷贝到tomcat的image中的/usr/local/tomcat/webapps的目录下,若要查看image中的具体目录位置,可以进入该image进行查看,命令如下:
    1
    docker exec -it imageID bash

最后就可以执行image的构建了,输入下面命令执行image的构建:

1
root@ahui-virtual-machine:/home/ahui/demos# docker build -t jpress .

其中的 “ -t jpress ” 表示构建的image的名字为jpress;最后的 “ . ” 表示在当前目录执行构建操作。

完成后,在终端输入一下命令查看image是否构建成功:

1
docker images

该命令的意思是查看当前所拥有的image。看到下面有 “ jpress ” 出现,即表示构建成功

1
2
3
root@ahui-virtual-machine:/home/ahui/demos# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
jpress latest d059622def4d 3 hours ago 355MB

4.4 运行我们自己的image

构建好了自己的jpress的image,如何运行它呢,在终端输入:

1
docker run -d -p 8888:8080 jpress

其中 “ -d ” 表示在后台运行;“ -p 8888:8080 ” 表示外部的8888映射image中的8080端口;

此时jpress的image就已经运行了,可以通过下面的命令查看:

1
docker ps

该命令表示当前正在运行的container;“ docekr ps -a ” 可以查看所有存在的containers。

这时就可以在浏览器中输入地址 “ loalhost:8888 ” 就可以进入tomcat的默认主页了,然后在浏览器中输入 “ loalhost:8888/jpress ” 就会进入jpress博客的主页,点击下一步,要求我们链接数据库;

注意:一个image一旦运行了,就转变成一个container了(如果多次执行运行image的命令就会创建多个不同container,可通过不同的端口区分);以后再启动该container可以通过下面的命令执行

1
docker start containerID

关闭某个运行的container可以通过以下命令执行:

1
docker stop containerID

删除一个已有的container可以通过以下命令执行:

1
docker rm containerID

而删除一个image的命令是:

1
docker rmi imageID

4.5 docker 搭建一个mysql数据库的image

在终端输入:

1
docker search mysql

然后选择一个image进行下载:

1
docker pull mysql

运行这个mysql的image:

1
docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=000000 -e MYSQL_DATABASE=jpress mysql

其中 : “ -e MYSQL_ROOT_PASSWORD=000000 -e MYSQL_DATABASE=jpress ” 表示环境变量mysql的数据库的密码为00000;创建一个jpress的数据库;

这时我的的mysql环境久安装好了,然后就可以回到 “ loalhost:8888/jpress ”页面 继续jpress博客的数据库的配置情况了;此时需要注意在填写数据库ip时,不能填写 “ localhost ” ,应为mysql的iage的ip不一定和本地ip一致,可以在终端输入 “ ifconfig ” 查看image的ip,然后填写上去。最后对tomcat的image执行重启久完成了:

1
docker restart containerID

后记

这一篇文章在好几天前都写好了,上传时发生了 “rm –rf ~”的悲剧。损失惨重,就不多说了,在此提示大家一定要常常备份。

WebGIS中切片获取和展示的原理

前言

在北京实习的时候一次跟强哥聊天,做GIS这么长时间了,自己能实现切片数据的展示,就没有那么多的迷茫了。一直以来对切片的展示都是一知半解的,现在静下来好好的梳理一下。

一、切片简介

说起切片,就离不开影像金字塔的概念,这里不予详细讲解。只需要明白一点:切片数据就是将不同比例尺下的地图进行切割,将切割后的切片文件按照一定的规则进行存储(当然切割方式,以及存储规则都各有讲究)。切片的展示就是按照一定的相应的规则取出这些切片,在相应的端口进行拼接展示。切片数据可以放在本地,也可以放在服务端。这里我们抛开一切个地理相关的细节,重点说明切片的展示方案。

二、切片数据获取流程

1 获取要展示的地理中心点位置point_screenCenter,即在屏幕中展示地图的中心点坐标;

2 根据中心点坐标,以及屏幕的大小screenSize,计算出屏幕内要呈现地图的范围;

3 根据屏幕边界处的坐标,计算出边界所在的切片行列号

4 计算边界处所在切片的边界坐标,计算切片边界与屏幕边界的偏移量offset

5 获去要求请的所有切片,按照计算的值进行排列展示

2.1 获取展示中心点的地理坐标

所谓展示中心点的坐标,就是当你准备在屏幕上展示地图时,首先你得对地图的范围进行初始化,比如设置地图中心点、地图的坐标系、显示级别等参数。就是这里的地图中心点,我们暂时命名为point_screenCenter,(属性X,Y表示地理坐标)。假设地图的坐标原点为point_original,则该点在x轴和y轴上距离为

1
2
x轴地理距离:(point_screenCenter.X-point_original.X)
y轴地理距离:(point_screenCenter.Y-point_original.Y)

注意:这里我们假设坐标原点在地图图片的左上角,水平向右为x轴正方向,水平向下为y轴正方向,当然不同的地图服务坐标原点和设定规则可能会有区别;
再说一下像素距离,像素距离指的是在一定比例尺的地图图片上的距离间隔,如果要计算像素距离,就需要用到分辨率(resolution)的概念了,resolution是指地图图片上一个像素所代表的实际距离,point_screenCenter距离坐标原点的像素距离的计算公式为

1
2
x轴方向像素距离:(point_screenCenter.X-point_original.X)/resolution
y轴方向像素距离:(point_screenCenter.Y-point_original.Y)/resolution

2.2 计算出屏幕内要呈现地图的范围

有了展示中心点(point_screenCenter)和屏幕的大小(screen_size,像素数量表示,如960*640)就可以计算屏幕内能展示的地图范围了,屏幕左上角的点为point_screenLT,它的地理坐标如下:

1
2
屏幕左上角的x坐标:point_screenLT.X=point_screenCenter.X-(resolution*screen_size.Width)/2
屏幕左上角的y坐标:point_screenLT.Y=point_screenCenter.Y-(resolution*screen_size.Height)/2

2.3 计算出边界所在的切片行列号

确定的屏幕左上角能展示的地理位置,就可以根据这个地理位置坐标计算该位置点所位于的具体切片了,给该切片命名为tileLT,则该切片的行列号的计算如下:

1
2
切片的行号:tileLT.Row=Math.floor((Math.abs(point_screenLT.X-point_original.X))/(resolution*tileSize.Width))
切片的列号:tileLT.Col=Math.floor((Math.abs(point_screenLT.Y-point_original.Y))/(resolution*tileSize.Height))

这里用到了Math.floor()函数是为了获取离坐标原点更近的点,因为我们获取的切片范围要大于在屏幕上展示的范围。
找到了行列号,还有一个小细节需要处理,处理这个细节前首先了解一个常识,一般情况下,切片的在展示时是按照切片左上角的位置进行排列的,此时我们只是确定了屏幕左上角位于该切片中,但是该切片的左上角并不一定与屏幕的左上角重合啊,怎么办?,计算该切片左上角的实际地理坐标为:

1
2
切片左上角x坐标:tileLT.LTX = tileLT.Col*tileSize.Width*resolution+point_original.X
切片左上角y坐标:tileLT.LTY = tileLT.Raw*tileSize.Height*resolution+point_original.Y

2.4 计算切片边界与屏幕边界的偏移量offset

有了切片的左上角的地理坐标,和屏幕左上角的地理坐标位置,就可以计算偏移量了,假设其在x轴和y轴上的偏移量为offSetX和offSetY,则偏移值为:

1
2
切片在x轴偏移像素值:offSetWidth=(point_screenLT.X-tileLT.LTX)/resolution
切片在y轴偏移像素值:offSetHeight=(point_screenLT.Y-tileLT.LTY)/resolution

有了这个值就可以将该切片展示在正常的位置了。

2.5 所有要展示切片的获取

最后就可以计算在x和y轴上要请求的切片数量了,获取个数时要用Math.ceil()函数了,原因是的切片的总范围要比屏幕上展示的范围要大一些,这里要获取离坐标原点尽量远的切片。

1
2
x轴切片数量:tileNumsX=Math.ceil((screenSize.Width + Math.abs(offSetWidth))/tileSize.Width);
y轴切片数量:tileNumsY=Math.ceil((screenSize.Height + Math.abs(offSetHeight))/tileSize.Width);

三、小结

到现在已经完成了切片的获取工作,这是一个简化的过程。在实际获中不同的切片种类(如TMS、WMS、WMTS等)有不同的切片大小和不同的切片规则,不同的坐标系对应不同单位,还会涉及到一些中间术语比如显示级别、比例尺等。在以后的文章中会介绍。因为我们要做一个自己的***。

Android开发中用到的jar和aar文件的区别

前言

在开发java应用时经常会引用第三方的jar包文件,而在开发Android应用时发现又多出了一个aar结尾的可引用文件。它们之间什么区别呢?

一、定义

jar:Java Application Resource;

aar:Android Application Resource。

可以看出jar文件是给java应用程序使用的,而aar是专门针对Android应用提供共的引用文件。

二、区别

jar:只包含了class文件与清单文件 ,不包含资源文件,如图片等所有res中的文件;

aar:包含jar包和资源文件,如图片等所有res中的文件。

三、导入

3.1 jar包的导入

  • 把工程切换到 Project 视图下,在module目录下可以看到 libs 目录;
  • 将相应的jar文件拷贝到 libs 文件夹中;
  • 更改module的build.gradle配置文件

    1
    2
    3
    dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    }

    在studio新版本中会自动生成,所有你只需直接拷贝.jar到lib目录下编译既可

  • 最后sync一下工程即可(或者rebuild一下工程)

3.2 aar包的导入

  • 将*.aar拷贝到app中的lib下;
  • 更改build.gradle 配置文件,在该module的 build.gradle中添加一个本地仓库,并把libs作为仓库地址:

    1
    2
    3
    4
    5
    6
    7
    8
    repositories {
    flatDir {
    dirs 'libs'
    }
    }
    dependencies {
    compile(name: '你aar文件的名字', ext: 'aar')
    }
  • 最后sync一下工程即可(或者rebuild一下工程)

四、导出

在Android studio中可以同时生成jar文件和aar文件,步骤如下:

  • 新建库,File——New——New Module——Android Library
  • 编译或生成工程,Build——Make Module即可得到相应的文件
    1
    2
    jar包路径:build\intermediates\bundles\release\class.jar
    arr包路径:build\outputs\aar\包名.aar

五、参考

http://www.aichengxu.com/android/11086139.htm;
http://jingyan.baidu.com/article/2a13832890d08f074a134ff0.html;
http://www.w2bc.com/article/214269.

Markdown语法简介

1、为什么要用markdown?

在接触hexo以及nodeppt时都需要markdown语法的编写,便下定决心好好学习一下markdown的语法。另外markdown也可以避开word或者不同软件的语法规则,通用性更强一些。套用一句有名的话:我们坚信写作写的是内容,所思所想,而不是花样格式

2、markdown难吗?

markdown常用的标记符号不超过10个,学习成本低,并且有如下优点:

  • 专注你的文字内容而不是排版样式,安心写作;
  • 轻松道出HTML、PDF和本身的 .md文件;
  • 纯文本内容,兼容所有的文本编辑器与字处理软件;
  • 随时修改你的文章版本,不必像字处理软件生成若干文件版本导致混乱;
  • 可读、直观、学习成本低。

3、工具选择

在知乎上有人推荐Mou,但是去官网下载失败了。然后发现macdown等同于Mou的开源版本,就下载来使用,可以编辑和查看markdown的文件。

4、语法简介

  • #、##、###、####、分别为一二三四级标题
  • *和 - 表示无序列表,数字开头表示有序列表
  • ”>“引用,即左划线
  • “![]()”是图片链接;“[](url)”是文字超链接
  • 两个星号包围的是粗体,单个星号包围的事斜体 分割线是三个星号
  • 表格
title1 title2 title3
aaa bbb cc
  • 代码框:前后各三个
    1
    2
    ```
    private static void main(String[] args)

Linux下npm的安装

什么是npm

npm是Node.js的包管理工具(package manager)。

#####为啥我们需要一个包管理工具呢?因为我们在Node.js上开发时,会用到很多别人写的JavaScript代码。如果我们要使用别人写的某个包,每次都根据名称搜索一下官方网站,下载代码,解压,再使用,非常繁琐。于是一个集中管理的工具应运而生:大家都把自己开发的模块打包后放到npm官网上,如果要使用,直接通过npm安装就可以直接用,不用管代码存在哪,应该从哪下载。

#####更重要的是,如果我们要使用模块A,而模块A又依赖于模块B,模块B又依赖于模块X和模块Y,npm可以根据依赖关系,把所有依赖的包都下载下来并管理起来。否则,靠我们自己手动管理,肯定既麻烦又容易出错。

npm的安装

在ubuntu系统中可以在终端敲入:

1
sudo apt install npm

在命令行提示中输入“y”,即可完成npm的自动安装。
然后在命令行输入:

1
npm -version

就会显示npm的安装版本,即安装成功。

Linux安装tomcat

1、官网下载文件

官方网站下载最新的tomcat:

1
http://tomcat.apache.org/download-80.cgi

在ubuntu上,我们下载zip和tar.gz。

2、解压压缩文件包

解压tomcat8,用下面的命令(我下载的是tar.gz格式的):

1
tar -zxvf apache-tomcat-8.5.16.tar.gz

3、移动到opt路径下

将解压后的文件移动到到 /opt 目录

1
sudo cp -r apache-tomcat-8.5.16 /opt

4、修改配置文件

进入 /opt/apache-tomcat-8.5.16/bin 目录

1
cd /opt/apache-tomcat-8.5.16/bin

编辑下面的startup.sh文件:

1
vim startup.sh

在文件中的esac下面添加:

1
2
3
4
5
JAVA_HOME=/usr/lib/jvm/java-8-oracle
JRE_HOME=$JAVA_HOME/jre
PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME
CLASSPATH=.:$JRE_HOME/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
TOMCAT_HOME=/opt/apache-tomcat-8.5.16

5、启动tomcat

在bin目录下输入:

1
sudo startup.sh

即可启动tomcat,此时可以在浏览器中输入:

1
localhost:8080

若要关闭tomcat,可以在bin文件夹下输入:

1
sudo shutdown.sh

小提示:
linux查找JAVA_HOME的路径命令为:

1
echo $JAVA_HOME

本文参考:
http://www.cnblogs.com/zhangmingcheng/p/5576499.html
http://www.cnblogs.com/kerrycode/archive/2015/08/27/4762921.html

Mac安装tomcat

步骤:

  • 去tomcat官网下载最新版的tomcat,格式选择tar.gz(当前最新版时9.0.0.M21);

  • 将该压缩文件解压到Library文件夹里,为了方便改名Tomcat9.0.0
    注意:Library在Mac中为隐藏文件,可以在finde-前往-前往文件夹中填写:

    1
    ~/Library
  • 进入Tomcat/bin文件夹下,启动tomcat

    1
    sudo sh startup.sh
  • 在浏览器中输入

    1
    localhost:8080

即可看到tomcat的主页

hexo创建github博客

初识Hexo

在之前接触github的时候就听说过hexo这个东西了,感觉挺高端同时心里面也有一些不自信,就一直没接触。直到在公司由同事再一次推荐hexo,拖到现在才着手。还是那一句话,对于一下好的工具,作为程序员不应该排斥,工具都是为了更好的提高作业效率的,不应心生畏惧,甩开膀子,尽管上吧!

一、github博客repository的创建

  • 账号登陆github;
  • create a new respositiry,名字一定要设为“yourname+github+io”;
  • 至于秘钥,可设可不设,为了简单此处不设置。

二、hexo的安装

2.1 hexo的安装

2.1.1 命令行输入:
1
$ nmp install hexo -g

关于npm的安装将会有单独的一章进行介绍;这里说一下后缀的-g表示全局的意思;

2.1.2 安装好之后,可以初始化一个文件夹了,命名为blog_hexo,依次输入命令:
1
2
3
4
$ hexo init blog_hexo
$ cd blog_hexo
$ npm install
$ hexo server

其中第一行目的是初始化一个文件夹blog_hexo,里面将存放一些配置型的文件;
第4行的目的是启动该启动服务,可以在浏览器输入http://localhost:4000 访问初始化的hexo博客了;

此时,我们的blog_hexo文件夹下有如下文件列表:

  • _config_yml // 注配置文件
  • db.json // 数据
  • debug.log // 调试日志
  • _node_mudules // nodejs 相关依赖
  • package.json // 配置依赖
  • scaffolds // 脚手架 - 也就是一个工具模板
  • source // 存放blog正文的 地方
  • themes // 存放皮肤的地方

注意:此时可能有些同学的页面打不开,原因可能是网络访问权限的问题,hexo默认使用google的提供的数据,因此需要做一些修改:

(1)进入blog_hexo/themes/landscape/layout/_partial 目录下,找到after-footer.ejs文件,将里面的:

1
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"> </script>

修改为:

1
<script src="http://cdn.bootcss.com/jquery/2.1.1/jquery.min.js“ > </script>

(2)找到header.ejs文件,删除里面下行:

1
<link href="//fonts.googleapis.com/css?family=Source+Code+Pro" rel=”stylesheet” type=”text/css”>

再次执行:

1
hexo server

就可以访问 http://localhost:4000 就可以正常的打开了。
此时本地的server已经部署完毕;
下面看一下如何添加新的文章:

三、新建文章

思路:自己编写md文件,通过hexo实现静态化,就可以自动生成相应的html页面了。

3.1 编写md文件

回到blog_hexo目录下,新建主题为hellohexo的blog:

1
hexo new hellohexo

此时会在source/_posts/下新建hellohexo.md的文件,然后就可以对该文件进行相应的文字编写工作了,采用的是markdown格式(markdown语法会有单独篇章总结)。

3.2 静态化

因为最终要将blog部署在github静态服务器上,所以需要静页面进行静态化。回到blog_hexo跟目录,执行:

1
$ hexo g

经过一些提示,就在blog_hexo/public文件夹下面生成了相应的静态文件。

四、将blog部署到github上

4.1 修改_config.yml文件

因为hexo已经集成好了发布到github的配置,所以只需要修改一下blog_hexo根目录下面的
_config.yml文件即可,将里面最下面的内容:

1
2
3
deploy:
type: github
repo:

修改成自己的repo

1
2
3
4
deploy:
type: git
repo: https://github.com/asafei/asafei.github.io.git
branch: master

注意:每个冒号 “:”一定需要跟一个空格。

4.2 回到blog_hexo根目录,执行

1
2
npm install hexo-deployer-git --save //没有这句会报错
hexo deploy

就会发现将public目录下的页面发布到github gh_pages分支上了。

4.3 访问博客

在浏览器输入

1
asafei.github.io

就可以看到自己的博客了。

此时利用hexo来搭建github的博客已经可以了,其余的关于域名指向、插件安装等内容暂时先不讲。

本文参考:
http://www.netpi.me/实用/hexo/
http://www.jianshu.com/p/e99ed60390a8

位移运算

世界上有两种人,一种知道二进制,而另一种人不知道二进制。

前言:

​ 对二进制知道已久,但是对于位运算却始终云里雾里。直到半年前去网易面试实习生被问到“给定一个的整数,求出它的二进制数中1的个数”,回头我详细搜了一下相关的解决方案,有常规解法,但是无论从效率还是代码简洁性来说,位运算都是最适合的。

​ 首先来普及一下二进制:二进制是指数字上的每一位都是0或1,即所谓的逢二进一。例如十进制的2转化为二进制之后是10,十进制的10转换成二进制之后是1010。

​ 所以二进制的运算就只设计0和1的运算,这比十进制的计算简单多了,它只有五种运算:与、或、异或、左移和右移。根据这种特点你可以给0和1赋予任何你想要的含义,来提取日常中的问题。​

一、与、或和异或

规律如下表

1
2
3
| `与(&)`   | `0 & 0 = 0` | `1 & 0 = 0` | `0 & 1 =`0  | `1 & 1 = 1` |
| `或(|)` | `0 | 0 = 0` | `1 | 0 = 1` | `0 | 1 = 1` | `1 | 1 =1` |
| `异或(^)` | `0 & 0 = 0` | `1 ^ 0 = 1` | `0 ^ 1 = 1` | `1 ^ 1 = 0` |

对异或的理解可以是:不同值间异或为1。

二、位移运算:

​ 左移运算符m<<n表示把m左移n位。左移n位时,最左边的n位将被丢弃,同时在最右边补上n个0。比如:

1
2
3
00001010<<2 = 00101000

10001010<<3 = 01010000

​ 右移运算符m>>n表示把m右移n位。右移n位时最右边的n位将被丢弃,但是右移时处理最左边位的情形要稍微复杂一点。如果数字是一个无符号数值,则用0填补最左边的n位;如果数字是一个有符号数值,则用数字的符号位填补最左边的n位。也就是说如果数字原先是一个正数,右移之后最左边补n个0;如果数字原先是负数,则右移之后最左边补n个1。例如下面是对两个8位有符号数作为右移的例子:

1
2
3
00001010>>2 = 00000010

10001010>>2 = 11100010

三、思路

现在回到问题本身,基本思路是:

  • 先判断整数的二进制表示中最右边的是不是1;

  • 接着把输入的整数右移一位,此时原来处于从右边数起第二位被移到了最右边,再判断是不是1;

  • 就这样每次移一位,直到整个整数变成0为止。

这样整个问题的核心就变成了判断一个整数的最右边是不是1了。这很简单,只要将整数和1做位与运算看结果是不是0就知道了。1除了最右边的一位之外所有位都是0,如果一个整数与1做与运算的结果是1,表示该整数的最右边一位是1,否则是0。

代码如下:

1
2
3
4
5
6
7
8
9
10
int NumberOf1(int n){
int count=0;
while(n){
if(n&1){
count++;
}
n>>1;
}
return count;
}

运用除法当然也可以实现等价的功能,但是除法的效率上远远低于位移运算。

​ 到这里是捋清了基本的思路,接下来就该处理细节问题了。前边也说了,有符号的数字右移后在最左边补上与符号数相等的数字,而这个数字未必就是0 。如果输入的n是负数,怎么办呢,整数的最左边会一直补上数字1,这样整个计算就会变成一个死循环……

​ 如何避免死循环呢,一种做法是,既然我不确定输入的n是不是带符号的数,但是我可以确定我的1是无符号的,我可以将1左移来达到相同的效果。

  • 首先把n和1做与运算,判断最低位是不是1;

  • 接着把1左移一位得到2,再和n做与运算,就能判断n的次低位是不是1;

  • 这样反复左移,每次都能判断n的其中一位是不是1。

代码修改如下

1
2
3
4
5
6
7
8
9
10
11
int NumberOf1(int n){
int count=0;
unsigned int flag=1;
while(flag){
if(n&flag){
count++;
}
flag<<1;
}
return count;
}

​ 是不是很有成就感,到这里本应该就结束了。书中给我们分析,这种解法的循环次数等于整数的二进制位数,32位的整数需要循环32次。

但是对原整数进行操作就没有更好的方法了吗?答案是有,而且书提出一个更好的解法,整数中有几个1就循环几次,而且理解起来比上边的更加简单。咱们的解体思路说白了不就是把整数最右边的1依次做归零计算吗,左移位只是一种途径,其它途径多的是。

​ 先来干货:把一个整数减去1,再和原来的整数做与运算,会把该整数的最右边的一个1变成0。

分析:

  • 如果一个整数不等于0,那么该整数的二进制表示中至少有一位是1,先假设这个数最右边一位是1,那么减去1时,最后一位变成0而其它所有位都保持不变。也就是最后一位相当于做了取反操作,由1变成了0;

  • 接下来假设最后一位不是1而是0的情况。如果该整数的二进制表示中最右边的1位于第m位,那么减去1后,第m位变成了0,第m位之后的所有的0变成了1,第m为之前的所有位保持不变。例如二进制1100,减去1后,变成那个了1011。

  • 由上边两种情况,可以得出一个整数减去1,都是把最右边的的1变成了0,如果它右边还有0的话,所有的0都变成1,而它左边的所有位保持不变。

  • 最后就是,当我们用减1之后的数字与原数字做位与运算,就相当于把原整数最右边的1变成了0。例如1100,减1之后变成1011,然后1100&1011 = 1000。

就下来就是代码了:

1
2
3
4
5
6
7
8
int NumberOf1(int n){
int count =0;
while(n){
++count;
n=(n-1)&n;
}
return count
}

好了,接下来补充一些相关知识:

  • 一个整数如果是2的整数次方,那么它的二进制表示中只有一位是1,其它的所以位都是0。根据前面的结论,把这样的整数减1之后再和它自己做与运算,这个整数中唯一的1就变成了0。

  • 输入两个整数m和n,计算要改变m二进制表示中的多少位才能得到n。可以分两步走:①求这两个数的异或,②统计异或结果中1的位数。