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

Java并发编程札记-(一)基础-02创建线程

$
0
0

本文介绍Java中如何创建线程。

目录:

  1. Runnable-定义无返回值的任务
  2. Thread-线程构造器
  3. Callable-定义可以返回值的任务
  4. Executor-线程执行器

在Java中,创建新执行线程有三种方法。

  1. 继承Thread。
  2. 实现Runnable。
  3. 实现Callable。

我认为Runnable和Callable的作用只是定义任务,创建线程还是需要Thread构造器或者Executor执行器来完成。

Runnable

线程可以驱动任务,所以我们需要一种可以描述任务的方式,Runnable可以来实现这个需求。

Runnable是一个接口,其中只声明了一个run方法。

public interface Runnable {
    public abstract void run();
}

要想定义任务,只需要实现Runnable接口,并实现run方法。

下面通过实例看下如何通过实现Runnable创建和启动线程。

例1:

//定义任务
class MyThread2 implements Runnable {
    // 重写run方法
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + "在运行");
        }
    }
}

public class MyRunnableTest {
    public static void main(String[] args) {
        // 创建任务
        MyThread2 mt= new MyThread2();
        //将任务附着在线程上
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        // 启动线程
        t1.start();
        t2.start();
    }
}

通过实现Runnable接口来定义的任务并无线程能力。要实现线程能力,必须显示的将一个任务附着到线程上。

运行结果为:

Thread-1在运行
Thread-0在运行
Thread-0在运行
Thread-1在运行
Thread-0在运行
Thread-1在运行

Thread

Thread是一个类,它实现了Runnable,并额外提供了一些方法供用户使用。它的定义如下:

public class Thread implements Runnable {...}

在我看来,Thread就是一个线程构造器。
MarkdownPhotos/master/CSDNBlogs/concurrency/0102/threadConstruction.png

下面通过实例看下如何通过继承Thread创建和启动线程。

例2:

//定义线程类
class MyThread extends Thread {
    //重写run方法
    public void run() {
        for (int i = 0; i < 3; i++) {
                System.out.println(this.getName() + "在运行");
        }
    }
};

public class MyThreadTest {
    public static void main(String[] args) {
        // 创建线程
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        //启动线程
        t1.start();
        t2.start();

        for (int i = 0; i < 3; i++)
        {
            System.out.println(Thread.currentThread().getName() + "在运行");
        }
    }
}

运行结果为:

Thread-1在运行
main在运行
Thread-0在运行
main在运行
Thread-1在运行
main在运行
Thread-0在运行
Thread-0在运行
Thread-1在运行

Thread-0和Thread-1是我们创建的线程,因为我们没有给它们命名,所以JVM为它们分配了两个名字。名字为main的线程是main方法所在的线程,也是主线程。主线程的名字总是mian,非主线程的名字不确定。从执行结果中可以看到,几个线程不是顺序执行的,JVM和操作系统一起决定了线程的执行顺序。所以,你运行后可能看到的不是这样的打印顺序。

Callable

待补充

Executor

待补充

Thread和Runnable该如何选择?

  • 因为Java不支持多继承,但支持多实现。所以从这个方面考虑Runnable比Thread有优势。
  • Thread中提供了一些基本方法。而Runnable中只有run方法。如果只想重写run()方法,而不重写其他Thread方法,那么应使用Runnable接口。除非打算修改或增强Thread类的基本行为,否则应该选择Runnable。

从上面的分析可以看到,一般情况下Runnable更有优势。

run方法与start方法的区别

启动线程的方法是start方法。线程t启动后,t从新建状态转为就绪状态, 但并没有运行。 t获取CPU权限后才会运行,运行时执行的方法就是run方法。此时有t和主线程两个线程在运行,如果t阻塞,可以直接继续执行主线程中的代码。

直接运行run方法也是合法的,但此时并没有新启动一个线程,run方法是在主线程中执行的。此时只有主线程在运行,必须等到run方法中的代码执行完后才可以继续执行主线程中的代码。

可以将例2中代码t1.start();改为t1.run()t2.start();改为t2.run(),你会发现打印结果会变为

Thread-0在运行
Thread-0在运行
Thread-0在运行
Thread-1在运行
Thread-1在运行
Thread-1在运行
main在运行
main在运行
main在运行

说明执行顺序为t1、t2、主线程(main)。

线程只能被启动一次

一个线程一旦被启动,它就不能再次被启动。在例2中在代码t1.start();后再加一句t1.start();,再运行会抛出java.lang.IllegalThreadStateException。

总结:

  • 两种方式定义线程类和创建线程的方法是不同的。
  • 两种方式启动线程都是用start方法。
  • 线程只能被启动一次。
  • 多个线程的执行顺序无法保证。

本文就讲到这里,想了解更多内容请参考

END.

作者:panweiwei1994 发表于2017/11/14 21:43:52 原文链接
阅读:122 评论:0 查看评论

QML开发实例(二)Qt5.9 安卓环境配置

$
0
0

 本教程适合所有Qt5的版本…


一、下载Qt5.9并安装Android模块


详见上一篇博客


二、下载Java和Android配置包


即 JDK,Android SDK,Android NDK


第一个是JAVA开发包,安装了这个才能使用JAVA语言开发软件,第二个是Android SDK,安装了这个才能编写安卓软件,就是安卓开发所需的JAVA包。第三个Android NDK,可以理解为,有了这个包,JAVA就能和C++交互了。因为我们的Qt是用C++编写的,要想用C++编写JAVA程序,就必须安装这个NDK,因为安卓程序是用JAVA写的,所以我们也需要安装这个包。


除了这几个我们还需要一个叫做Apache-Ant的东西。这些东西都可以通过上网下载,Android的配置包下载比较麻烦!所以这里我就分享了我收藏多年的Qt Android开发包啦!我从Qt5.2开始用的就是他……一直没啥毛病,很稳定。但是要紧跟科技的步伐,也是时候更新了。


这里提供的JDK是1.7版本。Android SDK是14年版本。后面的所有开发和讲解都基于这个工具包啦!其他版本的类似!需要新版本的可以自行下载……当时我折腾了好久才下完。


JDK下载 : http://pan.baidu.com/s/1kVgkLyJ

Android工具包下载:http://pan.baidu.com/s/1hrG8rUs



三、在Qt Creator配置安卓环境


1.解压我上面提供的两个工具包


得到两个文件夹一个是Java,一个是Android,我把Java重命名为了JavaSDK

打开安卓应该可以看到三个文件夹。





2.打开QtCreator ,点击工具,选中选项





然后找到设备,选中安卓选项卡。依次点击浏览,然后找到相应的文件夹。如图!!!四个都要选中喔!!





3.点击OK,然后我们环境就配置好啦!!



四、安装模拟器


环境配置好了,但是我们怎么运行我们的程序呢?总不能编译一次,把软件拷贝到手机,安装再调试吧????所以我们要安装模拟器!之前一直想弄Android SDK自带的模拟器,但是没有弄好,所以想到了一个办法。


1.打开你要在安卓上运行的项目,点击运行



然后你会看到这个





2.安装模拟器


意思就是你要设置一个模拟器来运行你的安卓程序!这里强烈推荐 玩游戏常用的  夜神模拟器!


官网 https://www.yeshen.com/


安装什么的就不说了!安装完毕后,运行你的模拟器,你会看到这样





然后你就可以用这个模拟器在电脑上玩你的安卓游戏啦!!!至于怎么使用这个模拟器,就不讲了,这不是重点。


3.再次点击运行


奇迹般地事情发生了,如无意外,你应该能看到这个





选中它,点击OK,等他编译,然后你就可以看到,你的程序在模拟器上运行啦!!同时你还能在Qt Creator里看到各种输出信息,就跟调试程序一样啦!很完美!





五、真机调试


上面是介绍模拟器的调试方式,当然最后我们还是要回到真机测试的。这里有两种方法,一种就是把安装包拷贝到手机上安装运行,你可以在这里找到你的安装包

大概就是你项目文件下这个目录位置。




这种方式很不方便,而且看不到你的程序的控制台输出信息,很难调试,怎么办呢?这里介绍第二种方法。


把你的手机通过数据线连到电脑上,点击手机里的设置,把USB调试打开(不懂的自行百度,每台机都不一样)。然后再次点击运行,像上面那样,如无意外,你能看到自己的手机显示在了列表里,当然前提是你在电脑上安装了自己的手机的驱动程序一般都会自动安装!剩下的操作都一样啦!




到这里为止,就介绍完啦!谢谢阅读!如果你遇到了疑问,可以留言,尽量解答~!











作者:lzc504603913 发表于2017/11/14 21:51:22 原文链接
阅读:15 评论:0 查看评论

数据结构与算法分析(Java语言描述)(19)—— 二叉搜索树删除指定的节点

$
0
0

代码


    // 移除 指定 key 的节点操作
    public void remove(Key key){
        root = remove(root, key);
    }

    // 删除掉以node为根的二分搜索树中键值为key的节点, 递归算法
    // 返回删除节点后新的二分搜索树的根
    private Node remove(Node node, Key key){
        if (node == null)
            return null;

        if (key.compareTo(node.key) < 0){
            node.left = remove(node.left, key);
            return node;
        }else if (key.compareTo(node.key) > 0){
            node.right = remove(node.right, key);
            return node;
        }else { // key = node.key

            // 待删除节点 左子树 为空
            if (node.left == null){

                // 存储 右子树
                Node rightNode = node.right;

                // 删除节点
                node.right = null;
                count--;

                // 返回右子树
                return rightNode;
            }

            // 待删除节点 右子树 为空
            if (node.right == null){
                // 存储 左子树
                Node leftNode = node.left;

                // 删除节点
                node.left = null;
                count--;

                // 返回左子树
                return leftNode;
            }

            // 待删除节点 左右子树 都不为空
            // 找到比 待删除节点大 的最小节点,即待删除节点 右子树 的最小节点
            // 使用此节点顶替 待删除节点的位置

            // 找到 右子树 的最小节点
            Node successor = new Node(minimum(node.right));
            count++;

            // successor 的 右子树 为被删除节点的 右子树(删除了最小节点)
            successor.right = removeMin(node.right);

            // successor 的 左子树 为被删除节点的 左子树
            successor.left = node.left;

            // 删除节点
            node.left = node.right = null;
            count--;
            return successor;
        }
    }

这里写图片描述

这里写图片描述

这里写图片描述


删除左右子树都不为空的节点

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

作者:HeatDeath 发表于2017/11/14 23:08:14 原文链接
阅读:19 评论:0 查看评论

一个屌丝程序猿的人生(七十六)

$
0
0

  拥友的面试官接过林萧的简历,凝神的看了片刻,随后先是把林萧的简历放了下来,然后才抬头对着林萧说道:“不错,你的简历我们收下了。后面我们会再联系你的。”

  林萧闻言心中略微有些失望,不过还是微笑的说了句“好的”,这才转头离开了。

  不过林萧不知道的是,拥友这家公司和其它公司最大的区别是,凡是没有当场面试的,反而是简历直接通过,后续会继续联系面试的,反倒是那些被当场面试的,都是面试官觉得简历不过关,这才需要当场面试一下,看是否有能力参加接下来的面试。

  而也正因为如此,林萧和拥友这家公司,最终还是失之交臂,这在后面一段时间里,着实让林萧郁闷了一番。

  ......

  一连投了四家公司,林萧对于这场招聘会的收获也算比较满意了。

  毕竟严格来说,他现在还没真正学完,最后一个项目也还没做完,他倒不急于非得在这个招聘会上找到工作。

  更何况,林萧对于讯飞科技这家公司的面试结果,还是有着几分自信的,而这家公司刚好也是一家游戏公司,算是林萧的理想行业了,这让林萧的心中更加多了几分淡定。

  就这样,接下来的时间里,林萧就仿佛一个旁观者一般,优哉游哉的开始观察起了,他为数不多还算熟悉的几个人。

  其中徐博是最轻松的一个,虽然他一直在马不停蹄的面试,但从其脸上不以为然的表情就不难看出,他恐怕并不是特别在意这份工作。这也难怪,徐博的老家是山西的,属于煤老板的家庭出身,就算家里财力不是十分夸张,但恐怕也不缺徐博这点工资。

  而朱昊、婷风和张世宇三人就不同了,这三人脸上的表情显然有些凝重,看来直到现在,恐怕这三人还没有比较有把握的公司。

  除了这四人以外,林萧还从人群中看到了任瑞强,从他现在无精打采的模样也不难看出,他恐怕在这次招聘会上的收货也不大。不过林萧对此倒也没有幸灾乐祸,毕竟二人也没有什么深仇大恨,只是脾气不相投罢了。

  而朱昊三人和任瑞强的处境,其实也是招聘会上大多数人的现状,也就林萧这样对自己比较有信心的,又或者是徐博这样满不在乎的,才会心中稍微轻松一些。

  如此过了大约不到1个小时,林萧便结束了自己的闲逛,来到了度娘公司的跟前。

  因为,笔试马上就要开始了!

  度娘公司这里已经聚集了不少人,不过其中并没有林萧熟识的人,而徐博、朱昊、婷风和张世宇这四人,更是一个都没在。看来这四人恐怕也觉得自己来了也没戏,索性就干脆放弃了,还不如趁此多面试几家更实在一些。

  没过多久,度娘的面试官便召集着众人,离开了招聘会现场,来到了招聘会现场旁边的一间教室当中。

  说是教室,但整间教室却连个黑板和讲台都没有,显然是为了度娘的笔试,才搬来了一些桌凳,临时当做考场使用。

  见所有人都进来了,度娘的面试官来到了第一排桌凳跟前,大声的说道:“好了,大家都尽快坐下来。位置大家可以自由选择,但两个人之间,最少要间隔一人的距离才行。”

  众人闻言,都开始纷纷落座,而林萧考试一向不喜坐在前排,因此便挑选了一个靠后的位置坐了下来。

  由于位置比较靠后,因此教室内的情景一览无余,林萧大概数了一下,这次参加度娘笔试的一共只有24人,这比林萧想象当中要少了一些。

  不过这也难怪,招聘会除去中午吃饭休息的时间,满打满算其实也就不到8个小时的时间,再加上很多公司都要排队,其实真正能够面试的时间并不多,很多人觉得自己笔试八成没戏,自然不愿意浪费时间在这上面了。

  随着众人坐好,笔试也随之开始了。

  林萧拿着笔试题先浏览了一遍,脸上表情不断变化着,最后忍不住皱了皱眉。

  这里面的笔试题,不光有Java基础、JavaWeb和面向对象的部分,竟然还有C语言和数据库的内容。前三个部分还好,林萧就算不知道确切的答案,至少每一题也都有自己的思路,想必蒙对的概率还是蛮高的。但这C语言,林萧也只是在大学考计算机二级的时候,稍微学过一段时间,至于数据库,林萧也是只会些皮毛,除了简单的增删改查以外,其余的就全不会了。

  就这样,前三个部分,林萧花了大概半个多小时,总归是有惊无险的做完了。而C语言的部分,虽然都忘的差不多了,但也能蒙个大概,最多就是错误的概率高些罢了。

  而数据库这部分就惨了,林萧基本上一道题都不会,尤其是最后的三道,林萧连个思路都没有,蒙都不知道怎么蒙,无奈之下,最后几道数据库的题林萧只得留着空白,就干脆把笔试卷交了上去。

  其实这最后几道题,也就是考察一下“内外连接”、“子查询”和“行转列”这部分知识,可惜这些对于现在的林萧来说,在不借助搜索引擎的情况下,还是太难了些。

  就这样,考试大概进行了有1个小时的时候,林萧便站起身来,把笔试的卷子交了上去,随后便离开了教室。

  而这个时候,已经有少数几个同学,在林萧之前就已经交卷离开了,不过,这些同学到底是做得太快,还是一看卷子觉得没戏,索性放弃了,就不得而知了。

  出了笔试考场以后,林萧又再次回到了招聘会现场。

  不过这次笔试过以后,林萧原本拥有的几分信心,一时间散去了不少,对于度娘和辛浪这两家大公司来说,自己或许还是差了一点,恐怕目前为止,也就只有那家游戏公司,自己的希望还大一些而已了。

  想到这,林萧不禁苦笑了一声,现在招聘会已经临近结束了,他再想多投几家,恐怕也来不及了,之前还是自己太过自信了,以至于除了度娘和辛浪以外,基本上没面试其它的公司。

  不过现在离结束还有将近1个半小时,运气好的话,面试上两家,时间上应该还是足够的。

  这样想着,林萧深吸一口气以后,便来到了一家公司跟前,排起了长队。

  而林萧此时却不知道,若非他参加完度娘笔试以后信心不足,又继续排队参加面试的话,恐怕这场招聘会,他真的要空手而归了。

-----分割线-----

      传说中的爆发来了~哈哈!

作者:zuoxiaolong8810 发表于2017/11/14 23:11:48 原文链接
阅读:28 评论:0 查看评论

HDU1018 POJ1423 UVALive2697 UVA1185 ZOJ1526 Big Number【阶乘位数】

$
0
0

Time Limit: 1000MS   Memory Limit: 65536K
Total Submissions: 28524   Accepted: 9075

Description

In many applications very large integers numbers are required. Some of these applications are using keys for secure transmission of data, encryption, etc. In this problem you are given a number, you have to determine the number of digits in the factorial of the number.

Input

Input consists of several lines of integer numbers. The first line contains an integer n, which is the number of cases to be tested, followed by n lines, one integer 1 <= m <= 10^7 on each line.

Output

The output contains the number of digits in the factorial of the integers appearing in the input.

Sample Input

2
10
20

Sample Output

7
19

Source


Regionals 2002 >> Asia - Dhaka


问题链接HDU1018 POJ1423 UVALive2697 UVA1185 ZOJ1526 Big Number

问题简述:(略)

问题分析

  这是一个数论题,可以根据题意用暴力法来求解,也可以使用来Stirling公式求解。

  暴力法:N的阶乖的位数等于LOG10(N!)=LOG10(1)+.....LOG10(N)。

程序说明

  暴力法在POJ中出现TLE。

题记:(略)

 

AC的C++语言程序如下

/* HDU1018 POJ1423 UVALive2697 UVA1185 ZOJ1526 Big Number(根据Stirling公式计算) */

#include <iostream>
#include <stdio.h>
#include <math.h>

using namespace std;

const double PI = acos(-1.0);
const double LN10 = log(10.0);

double stirling(int n)
{
    return ceil((n * log(double(n)) - n + 0.5 * log(2.0 * n * PI)) / LN10);
}

int main()
{
    int t, n;

    scanf("%d", &t);
    while(t--) {
        scanf("%d",&n);

        printf("%d\n", n <= 1 ? 1 : (int)stirling(n));
    }

    return 0;
}


AC的C语言程序如下

/* HDU1018 POJ1423 UVALive2697 UVA1185 ZOJ1526 Big Number(暴力法) */

#include <stdio.h>
#include <math.h>

int main(void)
{
    int t, n, i;
    double ans;

    scanf("%d", &t);
    while(t--) {
        scanf("%d",&n);

        ans = 0.0;
        for(i=1; i<=n; i++)
            ans += log10(i);

        printf("%d\n", (int)ans + 1);
    }

    return 0;
}




作者:tigerisland45 发表于2017/11/15 5:09:15 原文链接
阅读:23 评论:0 查看评论

netty中epoll server和nio server的使用

$
0
0

netty中epoll server和nio server的使用


这几天有空研究了下netty中的EpollEventLoopGroupNioEventLoopGroup的用法,在编码上没有显著的不同,对应的epoll,有一套的api供于使用,但是因为只能在linux机上使用,因此又借助了docker运行linux容器来运行相应程序,这节就来具体的讲述下。

nio server


编写了一个简单的Hello world的http server,不讲述详细代码了,只讲下最后的server中的部分源码,我采用的netty的版本是netty 4.0的,在这就不再使用netty5了,netty5因为一些更为复杂的特性和没有显著的提高性能已经被放弃了,这里就不再提了。

HttpHelloWorldServerHandler:

package cn.com.epoll;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import io.netty.util.AsciiString;

/**
 * Created by xiaxuan on 17/11/14.
 */
public class HttpHelloWorldServerHandler extends ChannelInboundHandlerAdapter {
    private static final byte[] CONTENT = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};

    private static final AsciiString CONTENT_TYPE = new AsciiString("Content-Type");
    private static final AsciiString CONTENT_LENGTH = new AsciiString("Content-Length");
    private static final AsciiString CONNECTION = new AsciiString("Connection");
    private static final AsciiString KEEP_ALIVE = new AsciiString("keep-alive");

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof HttpRequest) {
            HttpRequest req = (HttpRequest) msg;

            if (HttpUtil.is100ContinueExpected(req)) {
                ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE));
            }
            boolean keepAlive = HttpUtil.isKeepAlive(req);
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(CONTENT));
            response.headers().set(CONTENT_TYPE, "text/plain");
            response.headers().set(CONTENT_LENGTH, response.content().readableBytes());

            if (!keepAlive) {
                ctx.write(response).addListener(ChannelFutureListener.CLOSE);
            } else {
                response.headers().set(CONNECTION, KEEP_ALIVE);
                ctx.write(response);
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

channelRead的方法很简单,就是判断当前是不是http请求,是的话就输出Hello World,功能比较简单。

HttpHelloWorldServerInitializer:

public class HttpHelloWorldServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(new HttpServerCodec());
        ch.pipeline().addLast(new HttpHelloWorldServerHandler());
    }
}

pipline添加Handler处理。

NioHttpHelloWorldServer:

package cn.com.epoll;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * Created by xiaxuan on 17/11/14.
 */
public class NioHttpHelloWorldServer {

    private static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));

    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        ServerBootstrap b = new ServerBootstrap();
        try {
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                    .childHandler(new HttpHelloWorldServerInitializer());

            Channel ch = b.bind(PORT).channel();
            ch.closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }
}

普通的netty演示程序的写法,没什么特殊的,但是还是需要提一下其中使用的NioEventLoopGroup,NioEventLoopGroup就是一个简单的线程池调度服务,我们再追溯NioEventLoopGroup的源码的时候可以发现最终NioEventLoopGroup继承的就是ScheduledExecutorService,就是有多个NioEventLoop对象的线程池,如果不指定线程池的的容量的话,默认就是当前cpu * 2的数量,转到源码可以看到传入的构造函数为0,为以下:

 * Create a new instance using the default number of threads, the default {@link ThreadFactory} and
     * the {@link SelectorProvider} which is returned by {@link SelectorProvider#provider()}.
     */
    public NioEventLoopGroup() {
        this(0);
    }

但是转到最终源码会发现当判断为0的时候,去了默认值,源码如下:

 static {
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

        if (logger.isDebugEnabled()) {
            logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
        }
    }

    /**
     * @see MultithreadEventExecutorGroup#MultithreadEventExecutorGroup(int, Executor, Object...)
     */
    protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }

如上,最终取得默认值就是cpu * 2。

而这里最终还是需要提一下NioEventLoop,NioEventLoop的父类继承了SingleThreadEventExecutor,也是一个线程池调度服务,但是只有一个单线程,在NioEventLoop创建的时候,同时也会创建一个Selector,selector管理channel,所以实际上NioEventLoopGroup就是一组管理Channel的线程池。

源码解析就到此未知,运行程序,在web端请求的效果如下:

十分简单,这是nio server的运行。

epoll server


epoll server的源码主要在server上的不同,其他的与上相同,server如下:

package cn.com.epoll;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;

/**
 * Created by xiaxuan on 17/11/14.
 */
public class HttpHelloWorldServer {

    static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));

    public static void main(String[] args) {
        EventLoopGroup bossGroup = new EpollEventLoopGroup(1);
        EventLoopGroup workerGroup = new EpollEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.channel(EpollServerSocketChannel.class);
            b.option(ChannelOption.SO_BACKLOG, 1024);
            b.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            b.group(bossGroup, workerGroup)
                    .childHandler(new HttpHelloWorldServerInitializer());
            Channel ch = b.bind(PORT).sync().channel();
            ch.closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

暂时先不讲Epoll和nio selector特性的不同,首先先把这里的应用程序讲完,在普通的windows或者mac上运行当前的程序会运行不起来的,会报错为:

Exception in thread "main" java.lang.UnsatisfiedLinkError: failed to load the required native library
    at io.netty.channel.epoll.Epoll.ensureAvailability(Epoll.java:78)
    at io.netty.channel.epoll.EpollEventLoopGroup.<clinit>(EpollEventLoopGroup.java:38)
    at cn.com.epoll.HttpHelloWorldServer.main(HttpHelloWorldServer.java:19)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: java.lang.ExceptionInInitializerError
    at io.netty.channel.epoll.Epoll.<clinit>(Epoll.java:33)
    ... 7 more
Caused by: java.lang.IllegalStateException: Only supported on Linux
    at io.netty.channel.epoll.Native.loadNativeLibrary(Native.java:189)
    at io.netty.channel.epoll.Native.<clinit>(Native.java:61)
    ... 8 more

epoll模型只有在linux kernel 2.6以上才能支持,在windows和mac都是不支持的,因此需要在linux上运行这个程序,但是本机是mac系统,因此不能在本地运行,然后本地也没有安装linux虚拟机,因此便借助了docker来使程序运行,于此同时为了方便运行maven打出的jar包,借助了一个maven插件以供打出一个可执行的jar包,插件如下:

<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>${exec.mainClass}</mainClass>
                                </transformer>
                            </transformers>
                            <artifactSet>
                            </artifactSet>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

然后编写的Dockerfile文件如下:

FROM java:8
MAINTAINER bingwenwuhen bingwenwuhen@163.com

RUN mkdir /app
COPY target/epoll-server-1.0-SNAPSHOT.jar /app

ENTRYPOINT ["java", "-jar", "app/epoll-server-1.0-SNAPSHOT.jar"]
EXPOSE 8080

我拉取的这个java:8基础镜像就是以centos为基础镜像,因此就是linux环境,在可能一些基础镜像上并非支持epoll模型的可能性上,可以直接拉取centos镜像,然后配置java环境等等,在此不再详述,这个在网上有着足够的资料。

在maven编译打包之后,docker进行镜像构建,运行,最后执行docker ps命令,查看容器运行情况,如下:

容器正常运行,使用curl命令请求服务,将会获取如上一样的结果,epoll server正常运行。

我们在查看EpollEventLoopGroup源码的时候可以发现,NioEventLoopGroup和EpollEventgroup最终继承的类都是相同,只是部分特性不同而已,因此在这就不再讲述EpollEventLoopGroup的源码,然而epoll模型本身讲述起来又相当复杂,不是本节能够讲述清楚的,对于EpollEventLoopGroup与epoll模型,以后有空再做专题详述。

源码下载地址


NioEventLoopGroup的源码下载地址就不再给出,下面是epoll server的源码下载地址:

源码下载地址

作者:u012734441 发表于2017/11/15 8:03:36 原文链接
阅读:21 评论:0 查看评论

设计模式六大原则之----里氏替换原则

$
0
0

一、定义

所有引用基类的地方,必须能透明的使用其子类对象。通俗的说:遵循里氏替换原则的代码,只要父类出现的地方就可以使用子类来替换它而不会产生任何错误,使用者不需要知道用的是父类还是子类。
它的核心是继承

友情提醒:xmind导出的图片有点模糊,请放大查看

这里写图片描述

二、优缺点

它的核心是继承,它的优缺点也是继承的优缺点

2.1 优点

  • 代码共享:子类拥有父类的属性和方法
  • 重用性:子类重用父类的代码
  • 子父类异同:子类形似父类,异于父类,子父类不同
  • 扩展性:子类可以随意扩展父类
  • 开放性:父类可以随意被扩展,所以开放性随之增加

2.2 缺点

  • 侵入性:继承是具有侵入性的,强制继承父类的属性和方法
  • 灵活性:降低了了灵活性,子类必须拥有父类的方法,从子类角度看,子类被父类约束了
  • 耦合性:耦合性增强,子类继承了父类的属性和方法,如果修改父类,那么所有子类的逻辑有可能都要修改

三、重点

  • 返回值范围
    • 子类返回值S,父类返回值F,根据里氏替换原则,F范围必须大于S
  • 重载约束
    • 在对象调用时,不能让子类的方法被调用;继承父类时,子类不能重写父类的方法;必须重写时子类的方法参数个数大于父类

四、 注意点

如果想要遵循里氏替换原则,那么尽量避免让子类拥有自己单独的属性和方法,子类个性多了,子父类关系将难以调和

五、案例

路边摆摊打气球,我们可以用玩具枪射击,也可以用仿真枪射击。用代码描述出来。

5.1 遵循里氏替换原则代码实现

/**
 * 枪的抽象类,包含抽象方法和一个成员变量
 *
 * Created by rytong on 2017/11/14.
 */

public abstract class IGun {
    protected static final String TAG = "IGun";

    public abstract void shoot();
}

玩具枪类,仿真枪类似:

/**
 * 玩具枪类
 * Created by rytong on 2017/11/14.
 */

public class PlayerGun extends IGun {

    @Override
    public void shoot() {
        Log.e(TAG,"用玩具枪打气球");
    }
}

打气球的玩家:

/**
 * 打气球的玩家
 * Created by rytong on 2017/11/14.
 */

public class Player {
    IGun gun;

    public Player(IGun gun) {
        this.gun = gun;
    }
    public void shoot(){
        gun.shoot();
    }
}

玩家打气球:

    IGun iGun = new PlamingGun();
    new Player(iGun).shoot();

    IGun iGun1 = new PlayerGun();
    new Player(iGun1).shoot();

玩家打了两枪,结果:

Log的TAG是继承的父类,射击方法是实现的父类的抽象方法。

11-14 21:04:49.084 20053-20053/com.designpatterndisclipline E/IGun: 使用仿真枪打气球
11-14 21:04:49.084 20053-20053/com.designpatterndisclipline E/IGun: 用玩具枪打气球

5.2 修改逻辑模拟不严格遵循里氏替换原则可能存在的问题

给IGun增加两个方法,一个内部调用,一个开放出来player调用

/**
 * 枪的抽象类,包含抽象方法和一个成员变量
 *
 * Created by rytong on 2017/11/14.
 */

public abstract class IGun {
    protected static final String TAG = "IGun";

    public abstract void shoot();

    private void preBullet(){
        Log.e(TAG,"先准备好子弹才能射击。");
    }

    public void fire(){
        preBullet();
        shoot();
    }
}

PlayerGun不遵循里氏替换原则,覆盖父类的成员变量和方法,而PlamingGun完全遵循里氏替换原则。

/**
 * 玩具枪类
 * Created by rytong on 2017/11/14.
 */

public class PlayerGun extends IGun {

    private String TAG = "PlayerGun";

    @Override
    public void shoot() {
        Log.e(TAG,"用玩具枪打气球");
    }

    @Override
    public void fire() {
//        super.fire();
        Log.e(TAG,"我在玩玩具枪,不打气球");
    }
}

/**
 * 仿真枪类
 * Created by rytong on 2017/11/14.
 */

public class PlamingGun extends IGun {
    @Override
    public void shoot() {
        Log.e(TAG,"使用仿真枪打气球");
    }
}

修改玩家射击的方法

/**
 * 打气球的玩家
 * Created by rytong on 2017/11/14.
 */

public class Player {
    IGun gun;

    public Player(IGun gun) {
        this.gun = gun;
    }
    public void shoot(){
        gun.fire();
    }
}

玩家射击气球:

    IGun iGun = new PlamingGun();
    new Player(iGun).shoot();

    IGun iGun1 = new PlayerGun();
    new Player(iGun1).shoot();

执行结果:

11-14 21:17:50.800 28393-28393/com.designpatterndisclipline E/IGun: 先准备好子弹才能射击。
11-14 21:17:50.800 28393-28393/com.designpatterndisclipline E/IGun: 使用仿真枪打气球
11-14 21:17:50.802 28393-28393/com.designpatterndisclipline E/PlayerGun: 我在玩玩具枪,不打气球

如果设计时规定了要遵循里氏替换原则那么就要严格遵守,否则在编程过程中往往会发生一些难以预料的错误,也就是你的代码的bug率会大大提升。

作者:xwh_1230 发表于2017/11/14 21:24:30 原文链接
阅读:6 评论:0 查看评论

前端基础-04-盒子模型

$
0
0

盒子模型

1.盒子模型简介

#他是由内容、内边距、外边距、边框
border:边框 类型 颜色;
border-width
border-style solid实线 dashed虚线 dotted点线 double双边框
border-color
一个值的时候: 代表四个方向值一样 上右下左(顺时针)
二个值的时候: 上下  右左
三个值的时候: 上 右左 下
四个值的时候: 上  右  下 左
div {
width: 200px;
height: 200px;
border: 10px double green; /*复合样式*/
}

2.padding内边距

#padding  内边距,边框与内容之间的距离
一个值的时候: 代表四个方向值一样 上右下左(顺时针)
二个值的时候: 上下  右左
三个值的时候: 上 右左 下
四个值的时候: 上  右  下 左

p {
    width: 100px;
    height: 100px;
    border: 2px solid red;
    padding: 50px 20px 30px 40px;
}

3.margin外边距

#margin 外边距 元素与其他元素的距离(边框以外的距离)
一个值的时候: 代表四个方向值一样 上右下左(顺时针)
二个值的时候: 上下  右左
三个值的时候: 上 右左 下
四个值的时候: 上  右  下 左
margin: auto; 左右才有居中效果,上下没有用处
*/
* {
    margin: 0;/*去掉元素的默认margin*/
    padding: 0;/*去掉元素的默认padding*/
}

4.盒子宽高计算

盒子大小=样式宽 + 内边距 + 边框
盒子宽度=左border+右border+width+左padding+右padding
盒子高度=上border+下border+height+上padding+下padding
div {
    padding: 20px;
    margin: 30px;
    width: 200px;
    height: 200px;
    border: 10px solid red;
}
width=200+10*2+20*2=260px

5.浮动属性

# float:浮动的特点
如果只给前面的元素浮动,后面的要占据他的位置
ul {
    border: 1px solid red;
    width: 204px;
    height: 500px;
}

li {
    width: 20px;
    height: 20px;
    background: green;
    border: 1px solid #ddd;
    list-style: none; /*去掉小黑圆点*/
    float: right;/*右浮动*/
    border-radius: 50%;/*标签导圆角*/
}

6.清除浮动


#清除浮动三种方式:
    1.给父级增加高度(不推荐使用)
    2.给父级加overflow:hidden;
    3.给父级加一个类
        .clearfix:after{
          content: "";
          display: block;
          clear: both;}

div {
    /*height: 200px;*/
    width: 500px;
    border: 10px solid red;
    /*清除浮动*/
    /*overflow: hidden;*/
}

p {
    width: 200px;
    height: 300px;
    /*display: inline-block;*/
    background: green;
    float: left;
}

.clearfix:after {
    content: "";
    display: block;
    clear: both;
}


7.定位position

#position 定位
1.static 静态定位(没有定位),默认
2.relative 相对定位,相对于元素起始位置进行定位,元素占有位置=相对位置+元素本身位置
.box1 {
    width: 200px;
    height: 200px;
    background: red;
    position: relative;
    top: 50px;
}

3.absolute 绝对定位,没有占据位置,
      没有定位父级,则相对于整个文档发生偏移
        参考最近非static定位的父级进行定位
.box2 {
    width: 100px;
    height: 100px;
    background: green;
    /*position: relative;*/
    position: absolute;
    top: 50px;
}

4.fixed  固定定位,相对于浏览器窗口进行定位
.box3 {
    margin-top: 200px;
    width: 150px;
    height: 150px;
    background: blue;
    position: fixed;
    top: 50px;
}

#方向分为4个:
  left
  right
  top
  bottom
作者:lianjiaokeji 发表于2017/11/15 9:27:21 原文链接
阅读:18 评论:0 查看评论

Unity Shader 学习笔记(8) 纹理映射、凹凸映射

$
0
0

Unity Shader 学习笔记(8) 纹理映射、凹凸映射

参考书籍:《Unity Shader 入门精要》
3D数学 学习笔记(9) 凹凸映射(bump mapping)和切线空间(tangent space)

  • 逐纹素(texel,区别与像素)控制模型的颜色。

单张纹理,凹凸映射的逐顶点和逐像素对比:


纹理映射坐标(UV坐标)

  定义了该定点在纹理中对应的2D坐标。通常用一个二维变量(u, v)表示,u为横坐标,v为纵坐标,坐标原点在左下角。


单张纹理

使用Blinn光照模型。
面板属性:

Properties {
    _Color ("Color", Color) = (1,1,1,1)
    _MainTex ("Main Tex", 2D) = "white" {}      // 纹理图片,默认为全白
    _Specular ("Specular",Color) = (1,1,1,1)
    _Gloss ("Gloss",Range(8.0,256)) = 20
}

CG代码中对应添加的变量:_MainTex_ST可以得到该纹理的缩放和平移值,对应面板的Tiling(平铺)和Offset(偏移)。

sampler2D _MainTex;
float4 _MainTex_ST;     // S:scale, T:translation, _MainTex_ST.xy:缩放值, _MainTex_ST.zw:偏移值
struct a2v {
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;            // 第一组纹理
};

struct v2f {
    float4 pos : SV_POSITION;
    float3 worldNormal : TEXCOORD0;
    float3 worldPos : TEXCOORD1;
    float2 uv : TEXCOORD2;                  // 存储纹理坐标
};

v2f vert(a2v v) {
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.worldNormal = UnityObjectToWorldNormal(v.normal);
    o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;

    o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
    // 变换纹理,先缩放(乘xy),后偏移(加zw)。下同,下面是内置函数。
    // o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
    return o;
}

fixed4 frag(v2f i) : SV_TARGET {
    fixed3 worldNormal = normalize(i.worldNormal);
    fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

    // CG的tex2D函数对纹理进行采样,乘于颜色,作为散射值。
    fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;     

    // 散射值和环境光相乘得到环境光部分
    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;     

    // 漫反射公式(Lambert法则)
    fixed3 diffuse = _LightColor0.rbg * albedo * max(0,dot(worldNormal,worldLightDir)); 

    // 下面是高光反射
    fixed3 viewDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
    fixed3 halfDir = normalize(worldLightDir + viewDir);
    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);
    return fixed4(ambient + diffuse + specular,1.0);
}

UnityCG.cginc中 TRANSFORM_TEX (纹理转换)定义如下:

// Transforms 2D UV by scale/bias property
#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)

凹凸映射

两种方法:
- 切线空间下计算光照:光照方向、视角方向需要变换到切线空间计算。
- 世界空间下计算光照:把采样得到的法线方向变换到世界空间下,再和世界空间下的光照和视角方向计算。

效率上:第一种优于第二种,第一种在顶点着色器就完成对光照方向和视角的变换,第二种要对法线采样,必须在片元着色器中实现,即进行一次矩阵操作。

通用性:第二种优于第一种,如使用Cubemap进行环境映射时,需要使用世界空间下下的反射方向对Cubemap采样时,那就需要世界空间下的法线方向。

切线空间下计算

计算光照、视角方向从模型到切线空间的矩阵:因为这个变换只包含平移和旋转(正交矩阵),所以变换的逆矩阵就是转置矩阵,即将切线(x)、副切线(y)、法线(z)按行排列。

Properties {
    ...
    _BumpMap ("Bump Map", 2D) = "bump" {}       // bump:内置法线纹理,对应模型自带的法线信息。
    _BumpScale ("Bump Scale",Float) = 0.5       // 凹凸程度:0为没影响
    ...
}
CGPROGRAM

···
sampler2D _BumpMap;
float4 _BumpMap_ST;     // 同上面纹理的ST,即缩放和偏移值
float _BumpScale;
···

struct a2v {
    ...
    float4 tangent : TANGENT;       // 顶点的切线方向,tangent.w用来决定副切线的方向性。
};

struct v2f {
    float4 pos : SV_POSITION;
    float4 uv : TEXCOORD0;
    float3 lightDir : TEXCOORD1;
    float3 viewDir : TEXCOORD2;
};

v2f vert(a2v v) {
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);

    // 为了减少插值寄存器数目,可以把纹理映射坐标和凹凸映射坐标放一起。
    o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;  // xy存主纹理的纹理坐标
    o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;  // zw存凹凸感的纹理坐标

    // 法线叉乘切线得出副切线,tangent.w决定方向。rotation为按行排列的 模型到切线 的变换矩阵。
    //float3 binormal = cross(normalize(v.normal),normalize(v.tangent.xyz))*v.tangent.w;
    //float3x3 rotation = float3x3(v.tangent.xyz,binormal,v.normal);
    TANGENT_SPACE_ROTATION;         // 完全等于上面两句。在UnityCG.cginc内。

    o.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;  // 获取模型空间下的光照和视角,转换到切线空间
    o.viewDir = mul(rotation,ObjSpaceViewDir(v.vertex)).xyz;

    return o;
}

fixed4 frag(v2f i) : SV_TARGET {
    fixed3 tangentLightDir = normalize(i.lightDir);
    fixed3 tangetViewDir = normalize(i.viewDir);

    fixed4 packedNormal = tex2D(_BumpMap,i.uv.zw);      // 获取顶点对应法线纹理的像素值
    fixed3 tangentNormal;

    // 如果法线纹理类型没有设置成Normal map,要从像素映射回法线。
    // 因为法线都是单位矢量。所以 z = 根号下(1 - x*x + y*y )。
    //tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;
    //tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy)));

    // 如果设置了Normal map类型,Unity会根据平台使用不同的压缩方法,_BumpMap.rbg值不是对应的切线空间的xyz值了,要用宏映射回法线。
    tangentNormal = UnpackNormal(packedNormal);
    tangentNormal.xy *= _BumpScale;
    tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy)));

    ...
}

ENDCG

UnityCG.cginc中TANGENT_SPACE_ROTATION 定义如下:

// Declares 3x3 matrix 'rotation', filled with tangent space basis
#define TANGENT_SPACE_ROTATION \
    float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; \
    float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal )

UnityCG.cginc中UnpackNormal() 定义如下:

inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(UNITY_NO_DXT5nm)
    return packednormal.xyz * 2 - 1;
#else
    return UnpackNormalDXT5nm(packednormal);
#endif
}

世界空间下计算

切线空间转换到世界空间矩阵:切线(x)、副切线(y)、法线(z)按列排列。

struct v2f {
    float4 pos : SV_POSITION;
    float4 uv : TEXCOORD0;
    float4 TtoW0 : TEXCOORD1;       // 切线到世界空间变换矩阵3x3。w分量作为世界空间顶点位置,因为计算相同,所以放一起3x4。
    float4 TtoW1 : TEXCOORD2;
    float4 TtoW2 : TEXCOORD3;
};

v2f vert(a2v v) {
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);

    o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;  // xy存主纹理的纹理坐标
    o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;  // zw存凹凸感的纹理坐标

    // 计算世界空间下的顶点法线、切线、副切线
    float3 worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
    fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
    fixed3 worldTangent = UnityObjectToWorldNormal(v.tangent.xyz);
    fixed3 worldBinormal = cross(worldNormal,worldTangent) * v.tangent.w;

    // 变换矩阵,转置即可,每一行按照列摆放
    o.TtoW0 = float4(worldTangent.x,worldBinormal.x,worldNormal.x,worldPos.x);
    o.TtoW1 = float4(worldTangent.y,worldBinormal.y,worldNormal.y,worldPos.y);
    o.TtoW2 = float4(worldTangent.z,worldBinormal.z,worldNormal.z,worldPos.z);

    return o;
}

fixed4 frag(v2f i) : SV_TARGET {
    float3 worldPos = float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w);        // 世界坐标存在w值

    ...

    fixed3 bump = UnpackNormal(tex2D(_BumpMap,i.uv.zw));
    bump.xy *= _BumpScale;
    bump.z = sqrt(1.0 - saturate(dot(bump.xy,bump.xy)));

    // 从切线转换到世界空间,即转换矩阵乘于法线纹理向量
    bump = normalize(half3(dot(i.TtoW0.xyz,bump),dot(i.TtoW1.xyz,bump),dot(i.TtoW2.xyz,bump))); 

    ...
}

作者:l773575310 发表于2017/11/15 10:32:24 原文链接
阅读:0 评论:0 查看评论

leetcode题解-58. Length of Last Word && 67. Add Binary && 383. Ransom Note

$
0
0

今天的三道题目都特别简单,直接上题目和解法~~

58.,Length of Last Word题目:

Given a string s consists of upper/lower-case alphabets and empty space characters ' ', return the length of last word in the string.

If the last word does not exist, return 0.

Note: A word is defined as a character sequence consists of non-space characters only.

Example:

Input: "Hello World"
Output: 5

就是找到最后一个单词的长度,和昨天题目一样,trim去除空格:

    //59%
    public int lengthOfLastWord(String s) {
        return s.trim().length()-s.trim().lastIndexOf(" ")-1;
    }

    //59%
    public int lengthOfLastWord1(String s){
        int count = -1, i = s.length();
        while (--i >= 0 && s.charAt(i) == ' ');
        while (i - ++count >= 0 && s.charAt(i - count) != ' ');
        return count;
    }

67.,Add Binary题目:

Given two binary strings, return their sum (also a binary string).

For example,
a = "11"
b = "1"
Return "100".

二进制加法:

    //46%
    public String addBinary(String a, String b) {
        StringBuilder sb = new StringBuilder();
        int i = a.length() - 1, j = b.length() -1, carry = 0;
        while (i >= 0 || j >= 0) {
            int sum = carry;
            if (j >= 0) sum += b.charAt(j--) - '0';
            if (i >= 0) sum += a.charAt(i--) - '0';
            sb.append(sum % 2);
            carry = sum / 2;
        }
        if (carry != 0) sb.append(carry);
        return sb.reverse().toString();
    }

383, Ransom Note题目:

Given an arbitrary ransom note string and another string containing letters from all the magazines, write a function that will return true if the ransom note can be constructed from the magazines ; otherwise, it will return false.

Each letter in the magazine string can only be used once in your ransom note.

Note:
You may assume that both strings contain only lowercase letters.

canConstruct("a", "b") -> false
canConstruct("aa", "ab") -> false
canConstruct("aa", "aab") -> true

判断一个字符串是否可以从另一个字符串完全构造,直接对每个字符计数即可,代码如下所示,其中第二种方法可以实现早停,所以代码的效率比较高:

    //45%
    public boolean canConstruct(String ransomNote, String magazine) {
        if(ransomNote.length() > magazine.length())
            return false;
        int [] count = new int [26];
        for(int i=0; i<magazine.length(); i++){
            if(i<ransomNote.length())
                count[ransomNote.charAt(i)-'a'] ++;
            count[magazine.charAt(i)-'a'] --;
        }

        for(int num : count)
            if(num > 0)
                return false;

        return true;
    }

    //87%
    public boolean canConstruct2(String ransomNote, String magazine) {
        int[] table = new int[26];
        for (char c : magazine.toCharArray())   table[c - 'a']++;
        for (char c : ransomNote.toCharArray())
            if (--table[c - 'a'] < 0) return false;
        return true;
    }
作者:liuchonge 发表于2017/11/15 10:33:21 原文链接
阅读:0 评论:0 查看评论

LeetCode-120:Triangle (三角形列表的最小路径和) -- medium

$
0
0

Question

Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.

For example, given the following triangle
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).

Note:

  • Bonus point if you are able to do this using only O(n) extra space, where n is the total number of rows in the triangle.

问题解析:

给定三角形二维列表,求出从顶到底的和最小的路径,每步只能在下一行的相邻两元素中选取。

Answer

Solution 1:

DP。

  • 由题目可以看出,其中每一步均由上一步的问题构成,所以利用动态规划来求解。
  • 由题目条件限定,要求只能在相邻元素中选取,观察可知,如本行取j,则下一行在jj+1中选取;
  • 构建动态规划数组,保存前一行和当前行符合题目要求的路径和,以自底向上的方式,最终归一到第0行的0元素位置。那么动态规划数组的第0个元素即为最小路径和。
class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        int[] res = new int[triangle.size()+1];
        for (int i = triangle.size()-1; i >= 0; i--){
            for (int j = 0; j < triangle.get(i).size(); j++){
                res[j] = Math.min(res[j], res[j+1]) + triangle.get(i).get(j);
            }
        }

        return res[0];
    }
}
  • 时间复杂度:O(n^2),空间复杂度:O(n)

Solution 2:

Solution 1 的空间复杂度优化。

  • 通过观察可以知道,因为最终的和保存在动态规划数组的首位,我们可以直接将选取出的路径和加到triangle的前一行中,故无需构建新的动态规划数组。
  • 但由于需要修改triangle列表,需要set操作,相比解法一,减少了空间复杂度,却增加了时间复杂度;
class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        for (int i = triangle.size()-2; i >= 0; i--){
            for (int j = 0; j < triangle.get(i).size(); j++){
                triangle.get(i).set(j, triangle.get(i).get(j) + Math.min(triangle.get(i + 1).get(j), triangle.get(i + 1).get(j + 1)));
            }
        }

        return triangle.get(0).get(0);
    }
}
  • 时间复杂度:O(n^2) * O(set()),空间复杂度:O(1)

Solution 3:

python 解法,DP,与解法一相同。

为了加强对Python的练习,以后的练习中都会Python的程序。

class Solution:
    def minimumTotal(self, triangle):
        """
        :type triangle: List[List[int]]
        :rtype: int
        """
        if not triangle:
            return
        res = triangle[-1]
        for i in range(len(triangle)-2, -1, -1):
            for j in range(len(triangle[i])):
                res[j] = min(res[j], res[j+1]) + triangle[i][j]

        return res[0]
作者:Koala_Tree 发表于2017/11/15 10:43:43 原文链接
阅读:69 评论:0 查看评论

LeetCode--Same Tree

$
0
0

Given two binary trees, write a function to check if they are the same or not.

Two binary trees are considered the same if they are structurally identical and the nodes have the same value.

Example 1:

Input:     1         1
          / \       / \
         2   3     2   3

        [1,2,3],   [1,2,3]

Output: true

Example 2:

Input:     1         1
          /           \
         2             2

        [1,2],     [1,null,2]

Output: false

Example 3:

Input:     1         1
          / \       / \
         2   1     1   2

        [1,2,1],   [1,1,2]

Output: false

思路:递归。自顶向下深度优先搜索,递归判断是否是相同的子树,如果元素不同,或者一个有元素一个没有,则不同,如果最后都没有元素,则相同。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if((!p&&q)||(!q&&p)) return false;
        if(!p&&!q) return true;
        return (p->val==q->val&&isSameTree(p->left,q->left)&&isSameTree(p->right,q->right));
    }
};
作者:qq_20791919 发表于2017/11/15 10:55:24 原文链接
阅读:35 评论:0 查看评论

第16章 非阻塞式I/O

$
0
0

执行时间:

        停等版本(完全阻塞)  》  select加阻塞I/O版本  》  fork多进程版本(Linux下多线程也应该差不多)  》  非阻塞I/O版本


非阻塞读写

#include "../Gnet.h"

void do_client(int connfd)
{
    char to[MAX_LINE], fr[MAX_LINE];
    char *toiptr, *tooptr, *friptr, *froptr;
    int val, stdineof, maxfd, n, nwritten;
    fd_set rset, wset;

    val = fcntl(connfd, F_GETFL, 0);
    fcntl(connfd, F_SETFL, val|O_NONBLOCK);
    val = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, val|O_NONBLOCK);
    val = fcntl(STDOUT_FILENO, F_GETFL, 0);
    fcntl(STDOUT_FILENO, F_SETFL, val|O_NONBLOCK);

    toiptr = tooptr = to;
    friptr = froptr = fr;
    stdineof = 0;

    maxfd = MAX(MAX(STDIN_FILENO, STDOUT_FILENO), connfd);
    while(1)
    {
        FD_ZERO(&rset);
        FD_ZERO(&wset);
        if(stdineof==0 && toiptr<&to[MAX_LINE])//标准输入->套接字 还有数据要发送&&缓冲区还有容量
            FD_SET(STDIN_FILENO, &rset);
        if(friptr<&fr[MAX_LINE])//套接字->标准输出 缓冲区还有空闲
            FD_SET(connfd, &rset);
        if(tooptr!=toiptr)
            FD_SET(connfd, &wset);
        if(froptr!=friptr)
            FD_SET(STDOUT_FILENO, &wset);

        select(maxfd+1,&rset,&wset,NULL,NULL);

        if(FD_ISSET(STDIN_FILENO, &rset))
        {
            if((n = read(STDIN_FILENO, toiptr, &to[MAX_LINE]-toiptr)) < 0)
            {
                if(errno != EWOULDBLOCK)
                    perr_exit("read error on stdin");
            }
            else if(n == 0)
            {
                fprintf(stderr,"EOF on stdin\n");
                stdineof = 1;
                if(tooptr == toiptr)
                    shutdown(connfd, SHUT_WR);
            }
            else
            {
                fprintf(stderr, "read %d bytes from stdin\n", n);
                toiptr += n;
                FD_SET(connfd, &wset);
            }
        }

        if(FD_ISSET(connfd, &rset))
        {
            if((n = read(connfd, friptr, &fr[MAX_LINE]-friptr)) < 0)
            {
                if(errno != EWOULDBLOCK)
                    perr_exit("read error on socker");
            }
            else if(n == 0)
            {
                fprintf(stderr, "EOF on socket\n");
                if(stdineof)
                    return;
                else
                    perr_exit("server terminated prematurely");
            }
            else
            {
                fprintf(stderr, "read %d bytes from socket\n", n);
                friptr += n;
                FD_SET(STDOUT_FILENO, &wset);
            }
        }

        if(FD_ISSET(STDOUT_FILENO, &wset) && ((n = friptr-froptr) > 0 ))
        {
            if((nwritten = write(STDOUT_FILENO, froptr, n)) < 0)
            {
                if(errno != EWOULDBLOCK)
                    perr_exit("write error to stdout");
            }
            else
            {
                fprintf(stderr, "wrote %d bytes to stdout\n", nwritten);
                froptr += nwritten;
                if(froptr == friptr)
                    froptr = friptr = fr;
            }
        }

        if(FD_ISSET(connfd, &wset) && ((n = toiptr-tooptr) > 0))
        {
            if((nwritten = write(connfd, tooptr, n)) < 0)
            {
                if(errno != EWOULDBLOCK)
                    perr_exit("write error to socket");
            }
            else
            {
                fprintf(stderr, "wrote %d bytes to socket\n", nwritten);
                tooptr += nwritten;
                if(tooptr == toiptr)
                {
                    toiptr = tooptr = to;
                    if(stdineof)
                        shutdown(connfd, SHUT_WR);
                }
            }
        }
    }
}

    int main(int argc, const char* argv[])
    {
        int connfd;
        struct sockaddr_in server_addr;

        if(argc < 2)
            perr_exit("usage : client <IPaddress>");

        connfd = Socket(AF_INET, SOCK_STREAM, 0);
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(SERVER_PORT);
        inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
        Connect(connfd, (struct sockaddr*)&server_addr, sizeof(server_addr));

        do_client(connfd);

        return 0;
}

多进程版本

#include "../Gnet.h"

void do_client(int connfd)
{
    char buf[MAX_LINE];
    pid_t pid;

    if((pid = fork()) == 0)
    {
        while(Readline(connfd, buf, MAX_LINE) > 0)
            fputs(buf, stdout);

        kill(getppid(), SIGTERM);
        exit(0);
    }
    else
    {
        while(fgets(buf, MAX_LINE, stdin) != NULL)
            Write(connfd, buf, strlen(buf));

        shutdown(connfd, SHUT_WR);
        pause();
    }
}

int main(int argc, const char* argv[])
{
    int connfd;
    struct sockaddr_in server_addr;

    if(argc < 2)
        perr_exit("usage : client <IPaddress>");

    connfd = Socket(AF_INET, SOCK_STREAM, 0);
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
    Connect(connfd, (struct sockaddr*)&server_addr, sizeof(server_addr));

    do_client(connfd);

    return 0;
}
github:https://github.com/gongluck/unp-notes

作者:gongluck93 发表于2017/11/15 11:28:06 原文链接
阅读:35 评论:0 查看评论

Mysql启动报错:Unit mysql.service failed to load: No such file or directory的解决办法

$
0
0

1、软件环境:

OpenSUSE 13.1 x64

MySQL 5.6.20 x64


2、采用RPM包安装MySQL 5.6.20

[python] view plain copy
  1. # rpm -ivh MySQL-server-5.6.20-1.sles11.x86_64.rpm  
  2. # rpm -ivh MySQL-client-5.6.20-1.sles11.x86_64.rpm  
  3. # rpm -ivh MySQL-devel-5.6.20-1.sles11.x86_64.rpm  
  4. # rpm -ivh MySQL-shared-5.6.20-1.sles11.x86_64.rpm  

3、查看MySQL服务
[javascript] view plain copy
  1. # /etc/init.d/mysql status  
  2. MySQL is not running                                                                                                    failed  
  3. mysql.service  
  4.    Loaded: not-found (Reason: No such file or directory)  
  5.    Active: inactive (dead)  
  6. Aug 22 15:42:26 173-168-58-139 systemd[1]: Stopped LSB: Start the MySQL database server.  

可见,MySQL服务未启动。


4、启动MySQL服务
[javascript] view plain copy
  1. # /etc/init.d/mysql start  
  2. redirecting to systemctl start mysql  
  3. Failed to issue method call: Unit mysql.service failed to load: No such file or directory.  

报错,无法启动MySQL服务。

用systemctl方法

[javascript] view plain copy
  1. # systemctl start mysql.service  
仍然报如下错误
Failed to issue method call: Unit mysql.service failed to load: No such file or directory. See system logs and 'systemctl status mysql.service' for details.


5、解决方法

[javascript] view plain copy
  1. # systemctl enable mysql.service  


6、再次启动MySQL
[javascript] view plain copy
  1. # systemctl start mysql  
  2. # ps -ef | grep mysql  
  3. root     27930     1  0 16:30 ?        00:00:00 /bin/sh /usr/bin/mysqld_safe --datadir=/var/lib/mysql --pid-file=/var/lib/mysql/173-168-58-139.pid  
  4. mysql    28045 27930  0 16:30 ?        00:00:00 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/lib/mysql/173-168-58-139.err --pid-file=/var/lib/mysql/173-168-58-139.pid  
  5. root     28074 25545  0 16:30 pts/1    00:00:00 grep --color=auto mysql  

可见,MySQL已经正常运行。


Mysql启动报错:Unit mysql.service failed to load: No such file or directory的解决办法

作者:zhou_shaowei 发表于2017/11/15 11:33:27 原文链接
阅读:43 评论:0 查看评论

0基础lua学习(十九)C调用Lua----03C调用lua 函数和变量

$
0
0



下面的demo,主要演示 C如何访问 Lua中 函数、变量。

lua代码

function add(x,y)
      print("add") 
	  return x + y
end 

width = 100
height= 200
background = {r=0, g=0, b=1}



C代码

extern "C"  
{  

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

}  





lua_State* L;
int luaadd(int x, int y)
{
	int sum;
	/*调用函数名称*/
	lua_getglobal(L,"add");

	/*第一个参数*/
	lua_pushnumber(L, x);
	/*t第二个参数*/
	lua_pushnumber(L, y);

	/*调用函数两个参数一个返回值.*/
	lua_call(L, 2, 1);

	/*获取返回值*/
	sum = (int)lua_tonumber(L, -1);
	/*清空栈*/
	lua_pop(L,1);

	return sum;
}



//获取宽度和高度
int GetParam(int *width, int *height)
{	
	lua_getglobal(L, "width");
	lua_getglobal(L, "height");

	*width = (int)lua_tonumber(L, -2);
	*height = (int)lua_tonumber(L, -1);
	lua_pop(L,1);
	return 0;
}

#define MAX_COLOR 255
int getfield (const char *key)
{
	int result;
	lua_pushstring(L, key);
	lua_gettable(L, -2); /* get background[key] */
	//if (!lua_isnumber(L, -1))
	//	error(L, "invalid component in background color");
	result = (int)lua_tonumber(L, -1) * MAX_COLOR;
	lua_pop(L, 1); /* remove number */
	return result;
}


void GetTable()
{
	lua_getglobal(L, "background");


	int red = getfield("r");
	int green = getfield("g");
	int blue = getfield("b");
	printf("r:%d,g:%d,b:%d",red,green,blue);

}




int main()  
{  
	int sum;
	/*初始化lua*/
	L = lua_open();
	/*加载 Lua lib*/
	luaL_openlibs(L);
	/*打开hello.lua脚本*/
	luaL_dofile(L, "hello.lua");
	
	sum = luaadd(10, 15);
	printf("The sum is %d \n",sum);
	
	int width,height;
	GetParam(&width,&height);
	printf("%d,%d",width,height);
	GetTable();


	//关闭lua
	lua_close(L);
	return 0;  
}  


作者:hiwoshixiaoyu 发表于2017/11/15 11:40:13 原文链接
阅读:36 评论:0 查看评论

移动Web开发基础-rem布局

$
0
0

前言

在了解了基本的布局方案 移动Web开发基础-百分比+flex布局方案 之后,我们来了解下利用rem这个css单位来布局的方案。
其实不管哪种方案,最终的目的都是为了适配不同设备尺寸的屏幕显示。

基本认识

1.px是布局当中的css像素
2.em是相对父元素大小的单位,可以用来设置字体,行高,缩进等一些css属性
3.rem是根据根元素,也就是HTML元素的字体大小来计算的,因为rem单位都是根据根元素字体大小来计算的,所以我们就有了统一的参照标准,利用rem我们可以实现移动端页面的适配。

使用

设置了根元素html的字体大小之后,我们所有的尺寸属性都可以用rem单位来度量了,例如下图:

这里写图片描述

当然,这里开发的是移动端页面,所以需要加上视口设置meta,这里讲的是适配不同屏幕,既然屏幕尺寸一直在变化,那相应的代码里面是不是应该也有什么“尺寸”在变化呢?是的,代码里面根元素html的font-size大小也要相应的根据屏幕尺寸进行变化。

总结起来就是三点:
1.设置viewport
2.根据屏幕尺寸相应的设置根元素html的font-size大小
3.布局元素用rem作为单位,如上图

方案

这里的方案主要是指使用rem布局时,设置根元素字体大小的方案,页面布局有几个前提:
1.基于固定viewport设置

<meta name="viewport" content="width=device-width,initial-scale=1, minimum-scale=1.0, maximum-scale=1, user-scalable=no">

2.750px的设计稿大小(现在一般都是这个尺寸吧,这个根据市场上主流设备是会变化的)

3.约定 1rem = 100px,(本文按此约定)

注:也可以约定 750px = 100a ; (为了兼容以后的vw,vh)
10a = 1rem = 75px,当然这样可能没有100px好计算,可能需要利用sass这样的预编译器或者其他工具。

方案一 媒体查询较密集的断点

当然可以更丰富

@media (min-width: 320px){html{font-size: 42.6667px;} }
@media (min-width: 360px){html{font-size: 48px;} }
@media (min-width: 375px){html{font-size: 50px;} }
@media (min-width: 384px){html{font-size: 51.2px;} }
@media (min-width: 414px){html{font-size: 55.2px;} }
@media (min-width: 448px){html{font-size: 59.7333px;} }
@media (min-width: 480px){html{font-size: 48px;} }
@media (min-width: 512px){html{font-size: 68.2667px;} }
@media (min-width: 544px){html{font-size: 72.5333px;} }
@media (min-width: 576px){html{font-size: 76.8px;} }
@media (min-width: 608px){html{font-size: 81.0667px;} }
@media (min-width: 640px){html{font-size: 85.3333px;} }
@media (min-width: 750px){html{font-size: 100px;} }

方案二 JS根据屏幕尺寸计算并赋值

/*rem计算根元素字体大小*/
!(function(doc, win) {
    var docEl = doc.documentElement,
        resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
        recalc = function() {
            var clientWidth = docEl.clientWidth;
            if (!clientWidth) return;
            if (clientWidth < 750) {
                docEl.style.fontSize = 100 * (clientWidth / 750) + 'px';
            } else {
                docEl.style.fontSize = '100px';
            }
        };
    if (!doc.addEventListener) return;
    win.addEventListener(resizeEvt, recalc, false);
    doc.addEventListener('DOMContentLoaded', recalc, false);
})(document, window);

注:最好css中先设置html{font-size: 50px;}

当然,是用JS计算会遇到一些问题,有兴趣的可以看我之前写的两篇文章:
1.rem布局加载闪烁问题
2.rem布局在webview中页面错乱

总结

本文主要介绍了固定viewport,约定1rem=100px,设置根元素字体大小,375px设备时,html{font-size: 50px;} ,通过密集的媒体查询和JS根据屏幕大小动态计算的两种方案,以及使用方案二时会遇到的问题和解决方案。

个人经验:活动页面比较适合使用rem布局,内容型的移动端页面比较适合使用普通的px作为单位的布局方式。

后面还会继续介绍到淘宝的flexible布局方案。有一些元素字体大小用px还是rem作为单位的讨论。

扩展阅读

1.移动web适配利器-rem
2.CSS3的REM设置字体大小
3.手机端页面自适应解决方案—rem布局

作者:u013778905 发表于2017/11/15 13:07:45 原文链接
阅读:3 评论:0 查看评论

Quartz-异常处理

$
0
0

概述

我们根据官网示例说明Quartz在job执行异常情况时的处理。

参考官方原文:
http://www.quartz-scheduler.org/documentation/quartz-2.2.x/examples/Example6.html

本文涉及3个类:
BadJob1.java、
BadJob2.java
一个调度类 JobExceptionExample.java


示例

package com.xgj.quartz.quartzItself.exception;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.PersistJobDataAfterExecution;

/**
 * 
 * 
 * @ClassName: BadJob1
 * 
 * @Description: setRefireImmediately
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年11月15日 上午1:10:17
 */

@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class BadJob1 implements Job {

    @Override
    public void execute(JobExecutionContext context)
            throws JobExecutionException {
        SimpleDateFormat dateFormat = new SimpleDateFormat(
                "yyyy-MM-dd HH:mm:ss");

        JobKey jobKey = context.getJobDetail().getKey();
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();

        int flag = dataMap.getInt("flag");
        System.out.println("---" + jobKey + ",执行时间:"
                + dateFormat.format(new Date()) + ", flag: " + flag);

        // 由于零错误除以此作业将生成的异常的例外(仅在第一次运行)
        try {
            int result = 4815 / flag;

        } catch (Exception e) {
            System.out.println("--- Job1 出错!");

            // 修复分母,所以下次这个作业运行它不会再失败
            JobExecutionException e2 = new JobExecutionException(e);
            dataMap.put("flag", "1");

            // 这个工作会立即重新启动
            e2.setRefireImmediately(true);

            throw e2;
        }

        System.out.println("---" + jobKey + ",完成时间:"
                + dateFormat.format(new Date()));
    }


}
package com.xgj.quartz.quartzItself.exception;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.PersistJobDataAfterExecution;

/**
 * 
 * 
 * @ClassName: BadJob2
 * 
 * @Description: setUnscheduleAllTriggers
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年11月15日 上午1:10:24
 */

@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class BadJob2 implements Job {
    public void execute(JobExecutionContext context)
            throws JobExecutionException {
        SimpleDateFormat dateFormat = new SimpleDateFormat(
                "yyyy-MM-dd HH:mm:ss");

        JobKey jobKey = context.getJobDetail().getKey();
        System.out.println("---" + jobKey + " ,执行时间:"
                + dateFormat.format(new Date()));

        try {
            int result = 4815 / 0;

        } catch (Exception e) {
            System.out.println("--- job2 出错!");

            // Quartz将自动取消与此作业相关联的所有触发器,以使其不再运行
            JobExecutionException e2 = new JobExecutionException(e);
            e2.setUnscheduleAllTriggers(true);

            throw e2;
        }

        System.out.println("---" + jobKey + ",完成时间:"
                + dateFormat.format(new Date()));
    }

}
package com.xgj.quartz.quartzItself.exception;

import static org.quartz.DateBuilder.nextGivenSecondDate;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.SchedulerMetaData;
import org.quartz.SimpleTrigger;
import org.quartz.impl.StdSchedulerFactory;

/**
 * 
 * 
 * @ClassName: JobExceptionExample
 * 
 * @Description: 演示 Quartz 如何处理 从job中抛出的 JobExecutionExceptions
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年11月15日 上午1:10:02
 */
public class JobExceptionExample {
    public void run() throws Exception {
        // 任务执行的时间 格式化
        SimpleDateFormat dateFormat = new SimpleDateFormat(
                "yyyy-MM-dd HH:mm:ss");

        SchedulerFactory sf = new StdSchedulerFactory();
        Scheduler sched = sf.getScheduler();
        System.out.println("--------------- 初始化 -------------------");

        // 下一个15秒
        Date startTime = nextGivenSecondDate(null, 15);

        // badJob1 每10s执行一次 , 抛出异常,并立即重新执行
        JobDetail job = newJob(BadJob1.class).withIdentity("badJob1", "group1")
                .usingJobData("flag", "0").build();
        SimpleTrigger trigger = newTrigger()
                .withIdentity("trigger1", "group1")
                .startAt(startTime)
                .withSchedule(
                        simpleSchedule().withIntervalInSeconds(10)
                                .repeatForever()).build();

        Date ft = sched.scheduleJob(job, trigger);
        System.out.println(job.getKey().getName() + " 将在: "
                + dateFormat.format(ft) + "  时运行.并且重复: "
                + trigger.getRepeatCount() + " 次, 每次间隔 "
                + trigger.getRepeatInterval() / 1000 + " 秒");

            // badJob2 每5秒执行一次 , 并且 会抛出异常,然后 不再执行
        job = newJob(BadJob2.class).withIdentity("badJob2", "group1").build();
        trigger = newTrigger()
                .withIdentity("trigger2", "group1")
                .startAt(startTime)
                .withSchedule(
                        simpleSchedule().withIntervalInSeconds(5)
                                .repeatForever()).build();

        ft = sched.scheduleJob(job, trigger);

        System.out.println(job.getKey().getName() + " 将在: "
                + dateFormat.format(ft) + "  时运行.并且重复: "
                + trigger.getRepeatCount() + " 次, 每次间隔 "
                + trigger.getRepeatInterval() / 1000 + " 秒");

        sched.start();
        System.out.println("------- 开始调度 (调用.start()方法) ----------------");

        try {
            // 睡眠 30s
            Thread.sleep(60L * 1000L);
        } catch (Exception e) {
        }

        sched.shutdown(false);

        // 显示一下 已经执行的任务信息
        SchedulerMetaData metaData = sched.getMetaData();
        System.out.println("~~~~~~~~~~  执行了 "
                + metaData.getNumberOfJobsExecuted() + " 个 jobs.");
    }

    public static void main(String[] args) throws Exception {

        JobExceptionExample example = new JobExceptionExample();
        example.run();
    }
}

运行结果

INFO  StdSchedulerFactory - Using default implementation for ThreadExecutor
INFO  SimpleThreadPool - Job execution threads will use class loader of thread: main
INFO  SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
INFO  QuartzScheduler - Quartz Scheduler v.2.2.3 created.
INFO  RAMJobStore - RAMJobStore initialized.
INFO  QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.2.3) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

INFO  StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
INFO  StdSchedulerFactory - Quartz scheduler version: 2.2.3
--------------- 初始化 -------------------
badJob1 将在: 2017-11-15 01:14:15  时运行.并且重复: -1 次, 每次间隔 10 秒
badJob2 将在: 2017-11-15 01:14:15  时运行.并且重复: -1 次, 每次间隔 5 秒
INFO  QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
------- 开始调度 (调用.start()方法) ----------------
---group1.badJob1,执行时间:2017-11-15 01:14:15, flag: 0
--- Job1 出错!
---group1.badJob2 ,执行时间:2017-11-15 01:14:15
--- job2 出错!
INFO  JobRunShell - Job group1.badJob1 threw a JobExecutionException: 
org.quartz.JobExecutionException: java.lang.ArithmeticException: / by zero
    at com.xgj.quartz.quartzItself.exception.BadJob1.execute(BadJob1.java:51)
    at org.quartz.core.JobRunShell.run(JobRunShell.java:202)
    at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573)
Caused by: java.lang.ArithmeticException: / by zero
    at com.xgj.quartz.quartzItself.exception.BadJob1.execute(BadJob1.java:45)
    ... 2 common frames omitted
INFO  JobRunShell - Job group1.badJob2 threw a JobExecutionException: 
org.quartz.JobExecutionException: java.lang.ArithmeticException: / by zero
    at com.xgj.quartz.quartzItself.exception.BadJob2.execute(BadJob2.java:44)
    at org.quartz.core.JobRunShell.run(JobRunShell.java:202)
    at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573)
Caused by: java.lang.ArithmeticException: / by zero
    at com.xgj.quartz.quartzItself.exception.BadJob2.execute(BadJob2.java:38)
    ... 2 common frames omitted
---group1.badJob1,执行时间:2017-11-15 01:14:15, flag: 1
---group1.badJob1,完成时间:2017-11-15 01:14:15
---group1.badJob1,执行时间:2017-11-15 01:14:25, flag: 1
---group1.badJob1,完成时间:2017-11-15 01:14:25
---group1.badJob1,执行时间:2017-11-15 01:14:35, flag: 1
---group1.badJob1,完成时间:2017-11-15 01:14:35
---group1.badJob1,执行时间:2017-11-15 01:14:45, flag: 1
---group1.badJob1,完成时间:2017-11-15 01:14:45
---group1.badJob1,执行时间:2017-11-15 01:14:55, flag: 1
---group1.badJob1,完成时间:2017-11-15 01:14:55
---group1.badJob1,执行时间:2017-11-15 01:15:05, flag: 1
---group1.badJob1,完成时间:2017-11-15 01:15:05
INFO  QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutting down.
INFO  QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED paused.
INFO  QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutdown complete.
~~~~~~~~~~  执行了 8 个 jobs.

示例说明

job1:在抛出异常后,然后将flag设置成1,也就是说只有第一次会有异常抛出,以后都正常代码

   setRefireImmediately(true);它设置了 job 类抛出异常后的处理方式,此处意为发生异常后立即重新执行

job2:和job1不同,它没有判断,执行一次就抛出一次异常

   e2.setUnscheduleAllTriggers(true);设置了去掉它的触发器,也就意味着 BadJob2 如果发生异常,就没有机会再执行了

说明

在 job1.java 和 job2.java 中的异常如果不抛出(注释掉),会有什么结果发生呢?

// throw e2;-- 注释掉这一行后执行 

job1如果不抛出:执行正常,因为在异常处理中有重启job语句

job2如果不抛出:任务每次都执行,每次都进入异常。相当于后续的任务没有停止。

示例源码

代码已托管到Github—> https://github.com/yangshangwei/SpringMaster

作者:yangshangwei 发表于2017/11/15 13:18:17 原文链接
阅读:0 评论:0 查看评论

数据结构与算法分析(Java语言描述)(20)—— 二叉搜索树指定key的前驱、后继

$
0
0

前驱


// --------------------------------------------------------------------
    // 查找 key 的前驱

    public Key predecessor(Key key){

        // 在二叉搜索树中查找 key 对应的节点
        Node node = search(root, key);

        // 如果 key 对应的节点不存在,则 key 没有前驱,返回 null
        if (node == null)
            return null;

        // 如果 key 对应节点的左子树不为空,则返回左子树最大 key
        if (node.left != null)
            return maximum(node.left).key;

        // 否则,key 的前驱在从根节点到 key 的路径上,在这个路径上寻找到比 key 小的最大值
        // 即为 key 的前驱
        Node preNode = predecessorFromAncestor(root, key);
        return preNode == null ? null : preNode.key;
    }

    // 在以node为根的二叉搜索树中, 寻找key的祖先中,比key小的最大值所在节点, 递归算法
    // 算法调用前已保证key存在在以node为根的二叉树中
    private Node predecessorFromAncestor(Node node, Key key){
        if (key.compareTo(node.key) == 0)
            return null;
        Node maxNode;
        if (key.compareTo(node.key) < 0){
            // 如果当前节点 > key,则当前节点不可能是比 key 小的最大值
            // 向下搜索到的结果直接返回
            return predecessorFromAncestor(node.left, key);
        }else { // key > node.key
            // 如果当前节点 < key,则当前节点可能是比 key 小的最大值
            // 向下搜索结果存储到 maxNode 中
            maxNode = predecessorFromAncestor(node.right, key);
            if (maxNode != null)
                // maxNode 和当前节点 node 取最大值返回
                return maxNode.key.compareTo(node.key) > 0 ? maxNode: node;
            else
                // maxNode 为,返回 node
                return node;
        }
    }

后继


 // --------------------------------------------------------------------
    // 查找 key 的后继

    // 查找key的后继, 递归算法
    // 如果不存在key的后继(key不存在, 或者key是整棵二叉树中的最大值), 则返回 null
    public Key successor(Key key){
        Node node = search(root, key);

        // 如果key所在的节点不存在, 则key没有前驱, 返回 null
        if (node == null)
            return null;

        // 如果key所在的节点右子树不为空,则其右子树的最小值为key的后继
        if (node.right != null)
            return minimum(node.right).key;

        // 否则, key的后继在从根节点到key的路径上, 在这个路径上寻找到比key大的最小值, 即为key的后继
        Node sucNode = successorFromAncestor(root, key);
        return sucNode == null ? null : sucNode.key;
    }

    // 在以node为根的二叉搜索树中, 寻找key的祖先中,比key大的最小值所在节点, 递归算法
    // 算法调用前已保证key存在在以node为根的二叉树中
    private Node successorFromAncestor(Node node, Key key){
        if (key.compareTo(node.key) == 0){
            return null;
        }
        Node minNode = null;
        if (key.compareTo(node.key) > 0 ){
            // 如果当前节点小于key, 则当前节点不可能是比key大的最小值
            // 向下搜索到的结果直接返回
            return successorFromAncestor(node.right, key);
        }else { // key < node.key
            // 如果当前节点大于key, 则当前节点有可能是比key大的最小值
            // 向下搜索结果存储到minNode中
            minNode = successorFromAncestor(node.left, key);
            if (minNode != null)
                // minNode和当前节点node取最小值返回
                return minNode.key.compareTo(node.key) > 0 ? node : minNode;
            else
                // 如果minNode为空, 则当前节点即为结果
                return node;
        }
    }
作者:HeatDeath 发表于2017/11/15 13:38:40 原文链接
阅读:56 评论:0 查看评论

Dynamics CRM 关于页面事件绑定函数时无需指定库的验证

$
0
0

      在我们表单开发页面事件如onload、onsave、onchange等时,都会填上函数名称,并选择这个函数对应的库。


     但在实际的操作中发现,这个库指定的意义不大,因为这个函数只要存在当面页面关联的任何一个库中即可。我的验证过程如下,在上述截图中的contractPage.js中有一个叫accountChange的函数,而在account字段的change时间绑定上,我把库选择了contractinfo.js,而实际contractinfo.js中是没有accountChange这个函数的,函数存在于contractPage.js中,而页面的事件依旧可以执行,断点调试能正确的进入函数体。

     这么看下来,不知道这个库的存在意义是什么。

     然后又带出另外一个问题,也就是在同一个窗体下的多个库中,同样名字的函数不能存在于不同的库里,不然调用的时候就会出现差错,当然你可以使用命名空间来规避这个问题。

作者:woniu1104913 发表于2017/11/15 13:56:56 原文链接
阅读:32 评论:0 查看评论

Java基础学习总结(118)——单元测试的必要性和重要性

$
0
0
大部分程序员有两个特点:一不愿意写文档和注释,二不愿意写单测。单元测试是黑盒测试的基础,基本的准入测试,既能验证逻辑的准确性,又能给后续的接口重构提供基础。总之就是『单元测试很重要』,在敏捷迭代开发过程中,开发人员往往对单元测试不够重视,主要原因还是排期紧,比如我们团队初期对单测的要求是所有的 dao 层都要进行单测覆盖,到后来时间充裕才在 service 层进行单元测试的补充。controller 层的单元测试由于经常采用 mock 对象,很多人嫌繁琐就索性直接跟前端联调,不去管了。那为什么要写单元测试?单元测试能以更细的粒度,构造更多的业务场景,模拟更多的输入行为,在此过程中保证程序行为如程序员所想。单元测试可以驱动重构,通过重构进而优化代码本身,为什么这么说呢?因为单元测试对于被测试代码是有要求的,那什么是好的代码呢?援引一个我认识的架构师的观点,好的代码有以下特点:可读性。代码整洁有序;命名规范,见名知意(get/load, create/build), 在方法没有好到见名知意或者有特殊业务逻辑时,加必要注释。扩展性,易维护性。合理规划变和不变(核心业务逻辑不变);抽取模块、服务、方法;NO 复制粘贴效率。避免一个请求对远程服务/数据库重复请求可测性。避免大而全的方法,一个方法只做一件事(大方法抽取出多个可测小方法);相似的事情尽量合并成一个方法(比如 dao,sql 不要写死,尽量一个 sql 可以做多件事情)其中可测性是单元测试对代码的基本要求,所以当你发现代码不可测试了,是时候重构了,这也是一个代码重构时机的判断标准。
那么如何写好单元测试:单一职责,每个 case 测试一件事情,原子无歧义,失败无需 debug;case 按照业务场景拆分,注释保证覆盖全面,追加删除方便;测试的 case 间没有依赖;case 之间无冗余;case 干净。(数据库、磁盘、程序状态等)前后保持一致
最近负责一个复杂改动逻辑较多的迭代,对较多业务模块进行了重构,写了很多单元测试。所以顺便做个总结,单元测试是自己代码逻辑的检查员,是别人阅读你代码的入口,也是重构的保障

最近看 Eureka 的源码,看源码除了从任何一个函数作为入口着手外,有个很好的技巧就是从单元测试入手,本章最后来两张图来看下 Eureka 源码的单元测试,这种测试驱动的思想和做法值得我们学习。


图1可以清晰看到 eureka server 初始化过程:初始化配置、启动服务、创建 eureka server 加载配置,通过该入口进去可以看到 eureka 初始化的所有细节。


上图是服务的注册、发送心跳(renew 续约)以及失去心跳的入口,比直接找源码简单很多。

作者:u012562943 发表于2017/11/15 14:00:56 原文链接
阅读:15 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live


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