使用高德地图画飞行轨迹

高德地图提供了很多API来满足我们在其地图上画不同的图形,比如描绘出一段公交的路线,或确定一个建筑的位置等。

下面我以工作中的例子,描绘在高德地图上如何画出一条飞行轨迹。

高德提供了JS的API,需要先引入,后面的key需要你去高德的开发平台去申请

<script type="text/javascript" src="https://webapi.amap.com/maps?v=1.3&key=你的key"></script>

在html中定义一个div,来展示地图

<div id="container" style="text-align:center; margin: 0 auto; margin-top:25px;"></div>

下面就可以开始画图了,首先我要调用接口获取所有的飞行点坐标,一个轨迹就是通过一个个点来显示的

    var data = postRequest();
    //获取地图的中心点坐标
    var centerlat = (data.dep.lat + data.arr.lat)/2
    var centerlon = (data.dep.lon + data.arr.lon)/2
    var centerArr = [centerlat, centerlon];
    var map = new AMap.Map('container', {
        resizeEnable: true,
        center: centerArr,
        zoom: 5
    });

定义一个地图对象map,其中心点通过起/始点经纬度来计算,zoom为地图显示的缩放级别,resizeEnable:true,表示可以调整地图大小

好了就可以在地图上画线段了,获取点集合,格式为:[[40.0618,116.5991],[40.0552,116.6002],[40.0485,116.6013]],下面也设置了线条的一些样式。

strokeDasharray为勾勒形状轮廓的虚线和间隙的样式,此属性在strokeStyle 为dashed 时有效, 此属性在ie9+浏览器有效,用法:

[10,5] 表示10个像素的实线和5个像素的空白(如此反复)组成的虚线

    var lineArr1 = data.pointList1;
    var polyline = new AMap.Polyline({
        path: lineArr1,          //设置线覆盖物路径
        strokeColor: "#3366FF", //线颜色
        strokeOpacity: 1,       //线透明度
        strokeWeight: 2,        //线宽
        strokeStyle: "solid",   //线样式
    });
    polyline.setMap(map);

    var lineArr2 = data.pointList2;
    var planPolyline = new AMap.Polyline({
        path: lineArr2,          //设置线覆盖物路径
        strokeColor: "#3366FF", //线颜色
        strokeOpacity: 0.7,       //线透明度
        strokeWeight: 2,        //线宽
        strokeStyle: "dashed",   //线样式
        strokeDasharray: [10, 5] //补充线样式
    });
    planPolyline.setMap(map);

另外还需要画一个飞行物,更好的展示视觉效果,飞行物即一个点坐标,但这个坐标我用一张图片来表示

    //添加飞行物图标
    var icon = new AMap.Icon({
	    size: new AMap.Size(54, 52), //图标大小
            image: "images/MapPlane.png"
        });
	var marker = new AMap.Marker({
	    map: map,
	    angle: nowPlanePos.course,
	    position: [nowPlanePos.lng,nowPlanePos.lat],
	    offset: new AMap.Pixel(-27, -52),
	    icon: icon       
	});

使用marker方法来描绘一个点,angle表示点标记的旋转角度,即飞行物的朝向,offset函数一定要加上,因为你如果缩放地图后,那么这个飞行物可能在地图上呈现的位置会相差很大,

可以看看没有设置offset和设置了offset的差别,前两幅是设置了offset,后两幅是没有

6A3B1EF7-D539-4EE8-87D6-175B5767883C

C1EC8C8B-02B0-4534-8111-E4C2052C5306

可以看到,地缩放比例飞行物的位置相差很多,但实质,坐标的位置都是一样的,飞行物并没有根据地图来改变自己的偏移量

offset的用法:点标记显示位置偏移量,默认值为Pixel(-10,-34)。Marker指定position后,默认以marker左上角位置为基准点,对准所给定的position位置,若需使marker指定位置对准在position处,需根据marker的尺寸设置一定的偏移量。

以上我的图片宽度是52,只需要设置横线偏移量为宽度一半,向左则为-27。

注意:我的飞行物是不断的移动的,所以我需要实时的请求接口获取最新的点坐标,所以每次需要重新再map上画图,但在画图前,先要清除原来的图标对象,所以delete方法删除即可

比如在画飞行物时,使用:

    if(marker != null) {
		marker.setMap(null);
		delete marker;
	}

另外map对象是不需要重画的,map初始化一次即可。

参考:

官网文档地址:覆盖物-参考手册-JavaScript API | 高德地图API

图形示例:折线、多边形、圆-折线、多边形和圆-示例中心-JavaScript API | 高德地图API

阿米尔·汗《摔跤吧,爸爸》

f9dcd100baa1cd1139763732b312c8fcc2ce2ddb

一直很喜欢阿米尔·汗主演的电影,他的电影总是给给予人一些力量,并引发深入思考,看完这个电影,流了好几次泪,有下面几个体会:

1,当过去一直自己带来荣誉或好成绩的技能(或者叫思想更为贴切),当有人让你放弃它,或者告诉你那是错的,一定要持有一种怀疑的态度,若这个人毫无成就,那就更不要相信了。

2,要在任何一个领域取得成功,必定是要话费比其他人更多的练习,适当舍弃一些东西才会收获的更多。

3,即使准备的很好了,在第一次尝试中也不可能一次获胜,获胜了那算是运气,每一次都是一次吸取经验的机会,失败了还想不断尝试就一定会获胜。

4,每个人在自己行动中都会是一个“迷茫者”,因为看不到自己,所以希望有人能在旁给予一些指点;在关键的时刻以旁观者看看自己,便会对自己身处的环境有更好的认识,从而做出正确的选择。

谈注意力

 C5E52D0E-82BB-4021-B8CB-CD119E5702DA

我一直认为注意力是决定人成败的关键因素,也是最重要的因素,你把注意力放在哪,哪一点就会更突出

在谈注意力之前我先先说说健身

一直以来我还是一个比较喜欢锻炼的,由于一直是坐着办公,所以有时间一般就会发时间去锻炼下身体。最初是是想练就更强壮的肌肉,买过哑铃,等各种健身器材,也办过健身卡,请教练训练,教练指导的基本方式就是,今天炼胸,明天炼背,后天练手臂… 总之每一次要让练的地方受伤。之后又喜欢上了跑步,因为跑步相对于去练肌肉时间显的比较自由,不管是早上,还是晚上,也不至于损伤肌肉,关节

我想喜欢或健身的,应该知道这两种健身方式的不同,前者是爆发力练习,后者则是耐力练习。

先说说爆力练习吧,那是在教练指导练习时,我有时总找不到感觉,比如今天教练是指导练胸的,我推了几组哑铃后,教练就问,感觉胸部怎么样,我说没感觉啊!这时他就会对我说:你没练到位,你肯定是其它地方借力了,你要练胸,比如是上胸部,就应该把注意力放在那里,用它来发力。果然按照他的建议,的确有效果。这就是我所理解的集中注意力带来的效果,当把自己所有力量放在胸部上时,它就被激活了,迫使它要发动所有的力量去工作,当然前几次是会受伤的,因为之前没经历过,只能举几次,并因此产生了疼痛,但如果是下次再使用同样重量来练习,我们就可以举的更多了

相反跑步,跑步是一件很枯燥的事情,一般跑步我都会带上耳机,听听音乐。

跑步,没有人会对你说,你要把注意力要放在身体的哪个部位上,今年我参加了一次马拉松,回想起来,我全程根本没有去想我正在跑步,我的两个腿要怎么运动,手要怎么摆,我一直想的是能让我提高热情,能再坚持一会的事,比如:旁边有人给我鼓掌加油,我可不能停下脚步啊;看下路牌,还有1公里就到15公里了,1公里多简单啊,在路程还很远时,我会想,前面有一个目标,我要先跑到那个目标点,不会把注意力放在剩余的路程上,时刻让自己能保持热情持续去跑!

两种运动,通过转移你的注意力就能锻炼爆发力或耐力。

结合我们的工作,生活,当我决心去学习一门新技术时,这显然是跑步这项运动,而且是长跑,最初学习是枯燥的,所以开始划分一个小的目标点,再转移注意力到这个小目标上,这是就会实现起来容易的多。在实现目标中,每一个小难题,或遇到的障碍就相当于健身中的小创伤,通过集中注意力,思考和反思,下次如果再遇到就可以比先前做的更好了。

再说注意力的一个好处,他可以让大脑更省力,如果把人脑比作一台电脑,我们都知道,当电脑只执行一个进程时,速度是很快的,如果频繁切换任务,这中间是很消耗CPU资源的,大脑也是,在大脑正处于高速运转时,突然被迫停止,如果再要重新启动起来,是要花费时间成本恢复到先前的状态的。所以你看到一个人再深入思考时,尽量少去打扰别人,因为你浪费的不是这一点点时间,还有别人恢复到之前状态的时间。

说了这么多注意力带来的好处,那如何能训练提高自己的注意力,我觉得最好的办法是把自己要当前重要的时间和计划写下来,反复的去看,当有些其它事情进入我们的视线,引起我们关注时,想一想,我目前要做的事情是什么,我的计划实现了多少,这样一些无关紧要的事情,就会主动去放弃,把注意力转移到要做的重要事情上。

引用古人的一句话:精诚所至,金石为开,当一件事情,高度集中的去做,即时像金石顽固的障碍都可以攻克!

HashMap原理解析

HashMap,在程序中我们经常要用到的集合,它的实现是通过数组+单向链表来实现的

先从HashMap的put方法讲起,基本思路是:

1,会通过数据的key做hash算法,得到数组bucket的下标值,

2,然后再把<key,value>以链表的形式(只有一个节点)插入到bucket[i]中,如果两个key算出来的下标值i一样,那么新的元素就会添加到链表之后

查看源代码:

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

当key为null时,默认放入到数组的第0个位置,不为null,获取key的hashcode,进行右移16位并与hashcode做异或运算,得到下标值

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //tab数组为空,则创建一个
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //根据容量大小和hash值计算(&)下标值
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            //若节点存在,则替换
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //tab存放为树,jdk8默认为8个节点
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            //tab存放为链表,添加元素
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //如果map数量大于负载因子*最大容量,则扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

获取元素,基本思路:如果key为null,直接命中value,如果不为空,通过key获取数组下标值,如果第一个节点的key值相同,则直接返回,若为树,则遍历树结构,若为链表,则遍历链表对比key

    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        //先判断首节点
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            //多个节点
            if ((e = first.next) != null) {
                //为树
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                //为链表
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

数据扩容,resize(),基本思路是:将新的容量扩充到原来的2倍,并将旧的数组copy到新的数组中,注意原来链表或树的节点,位置可能会发生变化,要么是原来的位置,要么是原来位置*2,下面一幅图可以很好的描述resize元素的变化情况

d7acbad8-d941-11e4-9493-2c5e69d084c0

    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            //超过限制的最大值,则不再扩容
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //计算新的容量,是原来的2倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            //将旧的bucket复制到新的bucket中
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            //若节点为双数,则index位置不变
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            //若节点为单数,则index位置为原位置+oldCap
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

总结:

对于HashMap的原理可以理解如下:

目前有n个篮子,我要把贴有标签(标签代表key)的苹果放入篮子中,首先确定放入哪一个篮子中,所以通过苹果的标签来分类(hash算法),某一类的都放入在一个篮子中

为何要这样做,想想,如果将所有苹果都放入一个篮子中,那么我要取出某个标签的苹果,我就要在篮子中一个一个的找,如果苹果很多,这样效率是很低的,所以我是先通过苹果的标签定位到某个分类的篮子,再去里面找,这样的效率就提高很多了

参考文章:

1,http://yikun.github.io/2015/04/01/Java-HashMap%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E5%8F%8A%E5%AE%9E%E7%8E%B0/

2,http://blog.csdn.net/vking_wang/article/details/14166593

将项目发布到Maven仓库

我的开源包eweb已经发布到maven中心库了,使用maven配置就可以引入:

    <dependency>
        <groupId>com.github.chuanzh</groupId>
        <artifactId>eweb</artifactId>
        <version>1.0.1</version>
    </dependency>

下面讲讲如何发布到中心库,查了网上的步骤,都说的比较复制,但其实也很简单,这里分下面几个步骤:

一,Sonatype账号注册&创建一个Issue

二,使用GPG生成秘钥

三,修改项目的maven配置文件

四,上传构件到OSS&发布

看懂了之后其实很好理解,其实就类似于申请接入腾讯,微博的APP应用,

第一步,你得成为一个开发者吧,也就是要注册一个开发者账号,接下来就是要创建一个应用,等待腾讯、微博服务商审核

第二步,审核通过后,腾讯、微博会给你一对秘钥,但maven是要自己生成的,也就是用GPG

第三步,在你的项目中配置密钥和其他配置。

第四部,就是上传你的项目到腾讯、微博的服务平台,然后再等待审核,审核通过,那你就可以在他们的平台使用APP了

下面就从这4个方面说明下。

一,Sonatype账号注册&创建一个Issue

注册地址: Sign up for JIRA

记住你的用户名和密码,之后要用到

创建一个Issue:https://issues.sonatype.org/secure/CreateIssue.jspa?issuetype=21&pid=10134

v2-404b13199782405587859b6e73c02854_b

填写项目的基本信息:简单描述,详细描述,项目地址

重点说明下:Group Id为你项目的groupid,一般为公司或个人域名,如果你没有,就不要随便写了(他们在审核时会要求你提供一些证明的),写github的项目地址就可以了

好了,现在你就可以静静的等待maven审核了,一般为1~2天

二,使用GPG生成秘钥

首先需要下载GPG软件,Mac下载地址:GPG Suite,Windows用户下载地址:Secure email and file encryption with GnuPG for Windows,我的是Mac,所以具体说说Mac下的生成方式,Window方式请参考这篇博客

Mac下生成很简单,如下几个步骤:

1,新建一个密钥

v2-4fe9a772f939f0a8ef4fd2bfd103f97d_b

2,点击右键,将公钥发送至公钥服务器

v2-30789dafea5f96661c8e22b9c58ac165_b

到此密钥就生成了,记住你的口令,后面要用到

三,修改项目的maven配置文件

1,修改maven的setting配置

<settings>

    ...

    <servers>
        <server>
            <id>oss</id>
            <username>用户名</username>
            <password>密码</password>
        </server>
    </servers>

    ...

</settings>

用户名和密码就是你注册Sonatype的账号

2,配置项目pom.xml文件

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.github.chuanzh</groupId>
  <artifactId>eweb</artifactId>
  <version>1.0.1</version>
  <packaging>jar</packaging>

  <name>eweb</name>
  <description>轻量web开发框架</description>
  <url>https://github.com/chuanzh/eweb</url>

  <licenses>
    <license>
      <name>The Apache Software License, Version 2.0</name>
      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
    </license>
  </licenses>

  <developers>
    <developer>
      <name>chuan.zhang</name>
      <email>zhangchuan0305@gmail.com</email>
    </developer>
  </developers>

  <scm>
    <connection>scm:git@github.com:chuanzh/eweb.git</connection>
    <developerConnection>scm:git@github.com:chuanzh/eweb.git</developerConnection>
    <url>git@github.com:chuanzh/eweb.git</url>
  </scm>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding>
  </properties>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.5.1</version>
        <configuration>
          <source>1.7</source>
          <target>1.7</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <profiles>
    <profile>
      <id>release</id>
      <distributionManagement>
        <snapshotRepository>
          <id>oss</id>
          <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
        </snapshotRepository>
        <repository>
          <id>oss</id>
          <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
        </repository>
      </distributionManagement>
      <build>
        <plugins>
          <!-- Source -->
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>3.0.1</version>
            <executions>
              <execution>
                <phase>package</phase>
                <goals>
                  <goal>jar-no-fork</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
          <!-- Javadoc -->
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-javadoc-plugin</artifactId>
            <version>2.10.4</version>
            <executions>
              <execution>
                <phase>package</phase>
                <goals>
                  <goal>jar</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
          <!-- Gpg Signature -->
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-gpg-plugin</artifactId>
            <version>1.6</version>
            <executions>
              <execution>
                <id>sign-artifacts</id>
                <phase>verify</phase>
                <goals>
                  <goal>sign</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>

</project>

pom.xml必须配置的有:name,description,licenses,developers,scm。

snapshotRepository和repository在Issure初审通过后会给你,另外id要和setting.xml中保持一致

6EC7FDB0-E87F-4A66-ABCD-D7DC14EC17D1

四,上传构件到OSS&发布

1,使用下面命令,会提示要求你输入GPG的密钥,就是申请时候填写的,此过程比较慢,需要耐心等待

mvn clean deploy -P release

2,在OSS中发布构件

使用sonatype账号登陆https://oss.sonatype.org ,通过模糊查找,选择Staging Repositories,如下图,该构件的状态此时应该为open,勾选它,点击close按钮,sonatype会先做检验,通过后,就可以点击Release了,如果审核不通过,尝试删掉后重新上传

49767CB8-C939-456A-B314-1DA45F606BFB

A521FDDB-E66E-4116-B604-DF098982EA20

3,通知sonatype已经发布构建

也就是在Issure中回复,再次等待,也是需要审核1~2天,若审核通过,会回复你如下的信息

C86DFA4C-BC11-4A82-8CEB-FD3F3D9BF75C

等待10分钟,你就可以在maven库中搜索到你的jar包了

3CB6AF70-C0F5-4F63-8F3C-14D22E8C3CA8

 

若下次修改了本地的代码,只需要重新执行第四步的命令即可

Mysql插入表情字符问题处理

最近在处理微信数据时,出现如下的错误:

ERROR[12-02 11:11:57]cn.hl.basic.datapersist.DbBasicService.execSql(DbBasicService.java:632): java.sql.SQLException: Incorrect string value: '\xF0\x9F\x8D\xAC",...' for column 'user_info' at row 1

错误是在获取微信用户信息后,插入数据库时发生的,基本可以判断是微信用户名中有特殊字符,所以导致数据插入不了

在网上也找资源半天,说是编码问题,需要调整编码为utf8mb4,但看了数据库这个字段的编码,却就是utf8mb4的,又看了些文章,网上有说要改MySQL服务端的编码,也就是修改配置,然而服务器的配置不是那么好动的,现在运行的项目有很多。

是否是我连接的时候编码就指定错了,或是可以通过连接URL来指定呢?这样想着,查看了MySQL的连接URL,目前的如下:

jdbc:mysql://ipandport/dbname?useOldAliasMetadataBehavior=true&autoReconnect=true&failOverReadOnly=false&characterEncoding=utf8

不是说使用utf8mb4吗?这里用的utf8,会影响吗?是这个问题吗?带着问题,又查看了网上的资料,

明白了一个问题,这些乱码是微信中的表情符,这些表情符是占4个字节的,而utf8却三个字节的,如果在连接URL中指定characterEncoding=utf8的话,那么他写入数据时就按照3个字节写入了,那肯定写不进去的,有不指定写入字节,按照本身的字符的字节来写吗?

有!!!就是useUnicode参数,设置为useUnicode=true,那么在写入数据时,会根据数据本身的字节来写,好吧,明白了问题的原因,果断试下,果然不出所料,实践证明了预想的结论。

说明:useUnicode的作用:

当设置useUnicode=true时,数据在存入数据库时会根据数据库字段的编码进行转换后,再存储。所以数据库的编码设置为utf8mb4,那么存储肯定没啥问题了。

正确的配置如下:

jdbc:mysql://ipandport/dbname?useOldAliasMetadataBehavior=true&autoReconnect=true&failOverReadOnly=false&userUnicode=true

以下为再网上查询的一些资料,放在这里,以供参考:

http://ourmysql.com/archives/1402

http://www.jianshu.com/p/20740071d854

网上还说了另一种方案,就是在知道会有这样字符的字段时,统一对其进行转码,比如转成base64的存入到数据库,取出时再统一转码,但是对于之前数据库中已经存在历史大量的数据,操作是很麻烦的,还要去刷数据,所以在连接指定字符是最简单可行的方法。

另外说下,对于有这样的字符,插入到数据库中的时候会变成?,不用担心,取出来后数据还是可以还原的。

7e5771da-804a-49c1-948d-42c83ad8a9aa

9a84e520-7b2b-468b-85e3-24e14962146a

LTS算法-判断物体上升还是下降

LTS算法又称最长公共子序列,即找出一组数中最长的子序列,比如我们可以用于判断一个物体是在上升还是下降

如何判断物体是在上升还是下降,单纯用末尾数字-首位数字,可能结果并不准确,因为物体可能是一个抖动形的移动趋势。

比如一个物体的高度移动数据为:{ 2000, 3125, 3325, 2000, 3325, 4025, 4575, 5625, 6900, 7925, 7950, 7925, 7700, 7275, 7050, 6925, 6500, 6350, 5400, 4825, 3150, 2250 }

使用LTS算法正序求得:{2000 2250 3150 4025 4575 4825 6350 6925 7950 },

逆序求得:{2000 3125 3325 5400 5625 6500 6900 7050 7275 7700 7925 7950}

我们从结果中分析逆序LTS更长,表明物体是在下降的趋势。

 

关于join查询使用遇到的问题

今天发现服务器上的一个sql执行非常慢,两张关联表查询

有一张关注表user_focus,和一张关注备注表user_focus_note

sql如下:

select t1.FLYID,t1.FLYKEY,t1.ISPUSH,t1.NOTIFYSTATE,t2.description,t2.remind_times from USER_FOCUS t1 left join USER_FOCUS_note t2 on t1.ID=t2.user_focus_id where (t1.USERID=’1378416183′ or t1.PHONEID=’18820063′) and t1.ORDERTYPE=’0′ and t1.NOTIFYSTATE=’0′ and t1.UPDATETIME >= ’2016-09-24 00:00:00′ UNION select t3.FLYID,t3.FLYKEY,t3.ISPUSH,t3.NOTIFYSTATE,t4.description,t4.remind_times from USER_FOCUS t3 left join USER_FOCUS_note t4 on t3.ID=t4.user_focus_id where (t3.USERID=’1378416183′ or t3.PHONEID=’18820063′) and t3.ORDERTYPE=’0′ and t3.NOTIFYSTATE in (1,2) and t3.UPDATETIME >= ’2016-10-24 09:55:19′;

进一步分析,发现下面sql执行很慢,基本上再20s以上,但返回结果只有1000多条

select t1.FLYID,t1.FLYKEY,t1.ISPUSH,t1.NOTIFYSTATE,t2.description,t2.remind_times from USER_FOCUS t1 left join USER_FOCUS_NOTE t2 on t1.ID=t2.user_focus_id where (t1.USERID=’1378416183′ or t1.PHONEID=’18820063′) and t1.ORDERTYPE=’0′ and t1.NOTIFYSTATE=’0′ and t1.UPDATETIME >= ’2016-09-24 00:00:00′

其中user_focus有1600万条数据,PHONEID,USERID,UPDATETIME 都建了索引,

去掉关联查询后,速度一下子就提升上来了,基本100ms就执行完了,所以确定是使用left join的问题,查看了下user_focus_note表,发现有18w条数据,数据量不大啊,怎么回事?

仔细查看了sql,这里有用到t1.ID=t2.user_focus_id,USER_FOCUS_NOTE 表user_focus_id建立了一个索引,重新执行sql,速度果然很快。USER_FOCUS 表虽然只有1000多条数据满足,但是查询USER_FOCUS_NOTE表时都需要扫描全表,这样就有180000*1000条扫描,所以查询肯定很慢

总结:类似两个大表,或一个大表、一个小表做关联查询时,一定要建立好索引。

 

JAVA内存模型

Java内存模式是理解多线程执行的重要依据。

什么是内存模型,为什么需要它?

先看一张图:

8A628D2C-5E67-4A6B-9A2E-66699729E2D4

比如给一个变量aVariable赋值:aVariable=3,当然单线程下是没什么问题的,但如果是多线程呢?会不会有影响?答案是肯定的,多线程下数据的读写都会发生竞争。

所以用一句话总结JAVA内存模型是:

通过加锁来控制多线程下共享变量的读写顺序操作

如何保证?JMM定义了一套Happens-Before规则,包括:

程序顺序规则:如果程序中操作A在操作B之前,那么在线程中A操作将在B操作之前执行

监视器锁规则:同一监视器的解锁发生在加锁之前。

volatile变量规则:对volatile变量的写入操作必须在对该变量的读操作之前执行。

线程启动规则:Thread.start的调用必须在线程其他操作之前执行

线程结束规则:线程的任何操作必须在检测到其他线程结束之前执行,或从Thread.join中成功返回,或调用Thread.isAlive时返回false

中断规则:调用interrupt时,必须在中断线程检测到interrupt之前执行(就是先调用了interrupt才会抛出InterruptedException,感觉是废话)

终结器规则:对象的构造函数必须在启动对象的终结器之前执行完成

传递性:如果操作A在操作B之前执行,且操作B在C之前执行,那么A必须在C之前执行

如果程序中不满足以上的情况,那么多线程执行下就会发生重排序,什么是重排序?举个例子,如下:

public class PossibleRecordering {

	static int x=0,y=0;
	static int a=0,b=0;

	public static void main(String[] args) throws InterruptedException {
		Thread one = new Thread(new Runnable() {
			@Override
			public void run() {
				a = 1;
				x = b;
			}
		});

		Thread other = new Thread(new Runnable() {
			@Override
			public void run() {
				b = 1;
				y = a;
			}
		});

		one.start();other.start();
		one.join(); other.join();
		System.out.println("x="+x+",y="+y);
	}

}

上面的例子x和y是多少呢?不确定,可能是0,也可能是1,线程中a=1和x=b两者没有任何依耐性,所以JMM会对这两个操作执行重排,先执行哪个还真不一定。如果想要x=b在a=1之后执行,那么将x用volatile修饰

当使用volatile修饰后,JMM确保

1)当第二个操作是volatile写时,不管第一个是什么,都不能发生重排

2)当第一个操作是volatile读时,不管第二个操作是什么,都不能发生重排

3)当第一个操作为volatile写,第二个是volatile读时,都不能发生重排

为了好记就是:一读二写不能发生重排

final关键字

在构造函数中初始化的使用final修饰的关键字对象,优先于其他线程的读操作

举个例子:

public class SafeStates {

	private final Map<String, String> states;
	private int i=0;

	static SafeStates obj;

	public SafeStates () {
		states = new HashMap<String, String>();
		states.put("alaska", "AK");
		states.put("alabama", "AL");
                i=1;
	}

	public void addStates() {
		states.put("add", "AD");
	}

	public static void writer() {
		obj = new SafeStates();
	}

	public static void reader() {
		SafeStates o = obj;
		System.out.println(o.states);
		System.out.println(o.i);
	}

}

一个线程先调用了writer方法,然后另外一个线程调用reader方法,JMM可确保使用final修饰的变量,的赋值操作在reader方法之前,即reader方法读取到的states对象是{“alaska”, “AK”,alabama”, “AL”},但i值就不可保证了,可能读取的值为0

另外如果在非构造方法中,比如调用了addState方法,其他线程在读取的时候也不能确保读取到states值的最新数据

 

一个简单的爬虫

在做数据爬虫的时候,我们会面临以下几个问题:

1, 页面数据获取(有时需要传入cookie)

2, IP被封,请求失败

3, 请求失败如何重试

针对这几个问题,我写了一个小的爬虫例子。

1, 请求数据:

以某官网数据为例:

根据查询条件输入数据,进行查询,打开chrome的控制台,找出返回数据的请求,可以看到head中需要传入cookie,请求参数为一个json格式字符串

HMW2BI{GB`]5G_IL}PW{{T6

1

 

下面我们使用JAVA中httpClient包发送请求:

以下在head中添加相关信息,请求参数封装为一个StringEntity传入,模拟http请求即可获取请求数据

private String catchTicket(String depCode,String arrCode,String flightDate) throws Exception {
    String param = "json格式参数";
    String cookie = "cookie数据";
    
    DefaultHttpClient httpclient = new DefaultHttpClient();
    HttpResponse response = null;
    String responseString = null;
    HttpPost httpost = null;
    IProxy iproxy = null;
    try {
        httpclient = new DefaultHttpClient();
        HttpParams params = httpclient.getParams();
        HttpConnectionParams.setConnectionTimeout(params, 50*1000);
        HttpConnectionParams.setSoTimeout(params, 120*1000);
        
        /* 设置代理 */
        iproxy = HttpProxy.getProxy();
        HttpHost httphost = new HttpHost(iproxy.getIp(), iproxy.getPort());    
        httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, httphost); 
        
        httpost = new HttpPost(POST_URL);
        httpost.addHeader("Accept", "application/json, text/javascript, */*; q=0.01");
        httpost.addHeader("Accept-Language", "zh-CN,zh;q=0.8");
        httpost.addHeader("Connection", "keep-alive");
        httpost.addHeader("Content-Type","application/json; charset=UTF-8");
        httpost.addHeader("Cookie", cookie);
        httpost.addHeader("Host", "www.united.com");
        httpost.addHeader("UserAgent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36");
        httpost.addHeader("X-Requested-With", "XMLHttpRequest");
        //httpost.addHeader("Accept-Encoding", "gzip, deflate");
        
        StringEntity parEntity = new StringEntity(param);
        parEntity.setContentType("application/json; charset=utf-8");
        httpost.setEntity(parEntity);
        
        response = httpclient.execute(httpost); // 执行
        responseString = EntityUtils.toString(response.getEntity(),"UTF-8");
        if (response.getStatusLine().getStatusCode() != 200 && response.getStatusLine().getStatusCode() != 404) {
            logger.info("response code error({}) throw exception",response.getStatusLine().getStatusCode());
            throw new Exception();
        }
    } catch (Exception e) {
        e.printStackTrace();
        HttpProxy.removeProxy(iproxy);
        throw e;
    } finally {
        httpost.abort();
        httpost = null;
        httpclient.getConnectionManager().shutdown();
        httpclient = null;
        
    }

    // 休眠1秒
    TimeUnit.SECONDS.sleep(1);
    return responseString;
}

可以看到以上代码使用了代理,每次请求都会从HttpProxy获取一个代理,当此请求出现错误,标识代理异常,将此代理IP从HttpProxy中移除

代理IP我是在kuaidaili网站上抓取的,HttpProxy类如下:

/**
 * 代理类
 * chuan.zhang
 */
public class HttpProxy {
    
    private static final Logger logger = LoggerFactory.getLogger(HttpProxy.class);
    private final static String POST_URL = "http://www.kuaidaili.com/proxylist/1/";
    private static List<IProxy> iproxys = new ArrayList<IProxy>();
    
    public static void main(String[] args) throws Exception {
        System.out.println(HttpProxy.getProxy());
    }
    
    /**
     * 随机生成一个代理
     * @return
     * @throws Exception
     */
    public static IProxy getProxy() throws Exception {
        if (iproxys.size() == 0) {
            initProxys();
            logger.info("init proxy over");
        }
        
        Random rand = new Random();
        int num = rand.nextInt(iproxys.size()-1);
        return iproxys.get(num);
    }
    
    public static void removeProxy(IProxy iproxy) {
        if (iproxy != null) {
            iproxys.remove(iproxy);
            logger.info("send request error remove the iproxy: "+iproxy.getIp()+":"+iproxy.getPort());
        }
    }

    /**
     * 初始化代理
     * 从http://www.kuaidaili.com/获取最新代理
     */
    private static List<IProxy> initProxys() throws Exception {
        DefaultHttpClient httpclient = new DefaultHttpClient();
        HttpResponse response = null;
        String responseString = null;
        HttpGet httget = null;
        
        try {
            httpclient = new DefaultHttpClient();
            HttpParams params = httpclient.getParams();
            HttpConnectionParams.setConnectionTimeout(params, 50*1000);
            HttpConnectionParams.setSoTimeout(params, 120*1000);
            
            httget = new HttpGet(POST_URL);
            httget.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
            httget.addHeader("Accept-Language", "zh-CN,zh;q=0.8");
            httget.addHeader("Connection", "keep-alive");
            httget.addHeader("cookie", "channelid=0; sid=1470121255086558; _gat=1; _ga=GA1.2.2135905250.1469704395; Hm_lvt_7ed65b1cc4b810e9fd37959c9bb51b31=1469704395,1469781681,1470121266; Hm_lpvt_7ed65b1cc4b810e9fd37959c9bb51b31=1470121847");
            httget.addHeader("Content-Type","application/json; charset=UTF-8");
            httget.addHeader("Host", "www.kuaidaili.com");
            httget.addHeader("Referer", "http://www.kuaidaili.com/proxylist/2/");
            httget.addHeader("UserAgent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36");
            
            response = httpclient.execute(httget); // 执行
            responseString = EntityUtils.toString(response.getEntity(),"UTF-8");
            
            Pattern p = Pattern.compile("<td data-title=\"IP\">([\\s\\S]*?)</td>[\\s\\S]*?<td data-title=\"PORT\">([\\s\\S]*?)</td>",
                    Pattern.DOTALL);
            Matcher m = p.matcher(responseString);
            IProxy iproxy = new IProxy();
            while (m.find()) {
                iproxy.setIp(m.group(1));
                iproxy.setPort(Integer.parseInt(m.group(2)));
                iproxys.add(iproxy);
            }
            
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("init proxy error");
            throw e;
        } finally {
            httget.abort();
            httget = null;
            httpclient.getConnectionManager().shutdown();
            httpclient = null;
        }
        
        return iproxys;
    }
    
}

当代理池中没有IP,则从代理网站中抓取一批代理,我这里只抓取了10个,当然事前抓取更多IP保存下来

最后说说访问失败重试机制:

public void execute() {
    while(true) {
        try {
            Map<String, Object> routeMap = this.dao.getRoute();
            logger.info("start catch {}", routeMap);
            String depCode = routeMap.get("dep_code").toString();
            String arrCode = routeMap.get("arr_code").toString();
            String flightDate = routeMap.get("catch_date").toString();
            JSONObject json = null;
            try {
                String result = this.catchTicket(depCode,arrCode,flightDate);
                json = JSONObject.parseObject(result);
            } catch (Exception e) {
                logger.info("catch error result: "+routeMap);
                this.retryCatch(routeMap,5);
                continue;
            }
        
            this.parseDataToDb(json);
        } catch (Exception e) {
            e.printStackTrace();
	    try {
                TimeUnit.MINUTES.sleep(30);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
        }
    }
    
}

当请求数据出现异常,则会调用this.retryCatch(routeMap,5);方法,前面的一个参数为请求数据,后面的一个参数为重试次数,

如下:retryCatach方法实质是开启了一个线程,防止堵塞原来的逻辑,此线程会重新调用请求方法获取数据,如果请求成功,则停止请求,若请求失败,则会请求指定重试次数后停止,并在错误信息记录到数据库

/**
 * 重新抓取
 * @param routeMap
 * @param count
 */
private void retryCatch(final Map<String, Object> routeMap,final int count) {
    new Thread(new Runnable() {
        
        @Override
        public void run() {
            String depCode = routeMap.get("dep_code").toString();
            String arrCode = routeMap.get("arr_code").toString();
            String flightDate = routeMap.get("catch_date").toString();
            JSONObject json = null;
            for (int i=0;i<count;i++) {
                logger.info("retry catch ("+i+") {}", routeMap);
                try {
                    String result = catchTicket(depCode,arrCode,flightDate);
                    json = JSONObject.parseObject(result);
                } catch (Exception e) {
                    logger.info("retry catch ("+i+") error result: "+routeMap);
                    if (i == count-1) {
                        dao.updateRoute(routeMap.get("id").toString(), flightDate);
                    }
                    continue;
                }
                break;
            }
            
            parseDataToDb(json);
        }
    }).start();
}

以上一个基本的数据爬虫基本完成。