好库文摘 http://doc.okbase.net/ macOS下python3通过scrapy框架重新生成不得姐网站视频采集过程日志 http://doc.okbase.net/it-tsz/archive/294472.html 中国人醒来了 2018/4/21 18:55:42

1.搭建虚拟python3环境(Virtualenvwrapper)

参考http://www.cnblogs.com/it-tsz/p/pyhton.html

2.安装scrapy

前提先安装好pip,setuptools,然后安装以下模块

 pip install lxml

pip install twisted

pip install pyopenssl

windows下需要安装pywin32(pip install pywin32)

最后安装scrapy

pip install scrapy

 

3.通过scrapy生成scrapy spider 工程模版

 

scrapy startproject <project_name> [project_dir]

如:

scrapy startproject budejie

4.生成spider模块

scrapy genspider [options] <name> <domain>

如:

cd budejie 

scrapy genspider getbudejievideo budejie.com

5.修改spider模块(getbudejievideo.py)

  

# -*- coding: utf-8 -*-
import scrapy
import os
import urllib
from lxml import etree


# urlretrieve()的回调函数,显示当前的下载进度
# a为已经下载的数据块
# b为数据块大小
# c为远程文件的大小


def jindu(a, b, c):
if not a:
print("连接打开")
if c < 0:
print("要下载的文件大小为0")
else:

per = 100 * a * b / c

if per > 100:
per = 100
print("\r当前下载进度为:" + '%.2f%%' % per, end='')
if per == 100:
return True


''' def __init__(self):
self.headers = {
# 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
# 'Accept-Encoding': 'gzip, deflate',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36'
}
'''


class GetbudejievideoSpider(scrapy.Spider):
name = 'getbudejievideo'
allowed_domains = ['budejie.com']
start_urls = ['http://budejie.com/video']

# 获取视频当前页视频url列表
def getVideoList(self, html):
try:
data = etree.HTML(html)
video_urls = data.xpath('//div[@class="j-video-c"]/div[@data-mp4]')
# print(type(video_urls[0]))
# print(dir(video_urls[0]))
# <a href="2" class="pagenxt">下一页</a>
next_page = data.xpath('//a[@class="pagenxt"]')
if next_page:
next_page = next_page[0].get('href')

# videos[0].get('data-mp4')
return video_urls, next_page
# t(video_urls[0].get('data-mp4'))
except Exception:
print('lxml parse failed')
return None, None

__init_url = "http://www.budejie.com/video"

cur_page = 1

def parse(self, response):
print('*' * 100)
print(type(response))
# print(response.text)

# 创建video文件保持目录
path = os.path.join(os.path.abspath(os.path.curdir), 'videos')
if not os.path.exists(path):
os.mkdir(path)
# 获取当前页所有video 的url

try:
data = etree.HTML(response.text)
video_urls = data.xpath('//div[@class="j-video-c"]/div[@data-mp4]')

# <a href="2" class="pagenxt">下一页</a>
nextpage = data.xpath('//a[@class="pagenxt"]')
if nextpage:
nextpage = nextpage[0].get('href')

except Exception:
print('lxml parse failed------------------------------')
return
if not video_urls:
return
# 下载当前页下所有video url对应的视频文件
for v in video_urls:
# if v:
video_url = v.get('data-mp4')
print('下载:{}'.format(video_url))
p = os.path.join(path, v.get('data-mp4').split('/')[-1])

print(p)

if not os.path.exists(p):
try:
urllib.request.urlretrieve(video_url, p, jindu)
except Exception:
print("\n下载文件:{}失败".format(video_url))

# 检测是否有下一页
if nextpage:
if nextpage == '1':
return

nextpage_url = self.__init_url + '/' + nextpage

self.cur_page += 1
print('\n下载第{}页视频数据:{}'.format(self.cur_page, nextpage_url))
#通过生成器对新生成的url继续回调parse
yield scrapy.Request(nextpage_url, callback=self.parse)

else:
return
4.修改配置文件settings.py以下参数选项
。。。
#以下为模拟浏览器验证
# Crawl responsibly by identifying yourself (and your website) on the user-agent
# USER_AGENT = 'budejie (+http://www.budejie.com)'
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36'

# Obey robots.txt rules 跳过robotsy验证
ROBOTSTXT_OBEY = False
。。。
5.通过scrapy开启爬虫数据采集
scrapy crawl getbudejievideo
6.测试成功。

 

 

]]>
Diango + uwsgi + nginx 项目部署(可外网访问) http://doc.okbase.net/0x0101010/archive/294471.html 不懂得小白 2018/4/21 18:35:08

自己通过nginx uwsgi 部署django项目,查询了很多资料,遇到了很多问题,最终完成了部署,趁着心情愉悦,写个随笔,为曾像我一样苦寻解决方案的小伙伴们提供些思路。

安装Nginx:

 1 #安装nginx
 2 sudo apt-get install nginx
 3 
 4 #一些有用的命令
 5 #启动nginx
 6 sudo /etc/init.d/nginx start 
 7 #重启nginx
 8sudo /etc/init.d/nginx restart
 9 #停止nginx
10 sudo /etc/init.d/nginx stop
11 
12 #很暴力的方法,我喜欢
13 sudo killall nginx

安装uwsgi:

1 pip install uwsgi
2 
3 #注意uwsgi需要在虚拟环境中运行

配置uwsgi:

#在项目目录中建立个conf文件夹,将nginx和uwsgi文件都放进去,不是必须#但是个好习惯

#my_uwsgi.ini
ite_uwsgi.ini file
[uwsgi]

# Django-related settings
# the base directory (full path)
chdir           = /to/your/project/#这个是项目的路径
# Django's wsgi file
module          = project.wsgi#这个project要换成自己的项目名,也就是uwsgi.py所在的文件夹名
# the virtualenv (full path)
home            = /home/ubuntu/.virtualenvs/虚拟环境名#这个就是虚拟环境的路径

# process-related settings
# master
master          = true
# maximum number of worker processes
processes       = 10
# the socket (use the full path to be safe
socket          = 127.0.0.1:8080#这个用来和Nginx对接,端口号可以改,本人项目将uwsgi作为本地服务,外网不能直接访问,用nginx作为代理,所以用本地的地址。
# ... with appropriate permissions - may be needed
# chmod-socket    = 664
# clear environment on exit
vacuum          = true
~                          

配置nginx

 1 #以下内容在mysite_nginx.conf中,这个文件名也可以随意起
 2 # mysite_nginx.conf
 3 
 4 # the upstream component nginx needs to connect to
 5 upstream django {
 6     # server unix:///path/to/your/mysite/mysite.sock; # for a file socket
 7     server 127.0.0.1:8080; #这个是用来跟uwsgi对接的,要和my_uwsgi.ini中写一致
 8 }
 9 
10 # configuration of the server
11 server {
12     # the port your site will be served on
13     listen      8000;#这个端口是nginx用来监听uwsgi的,默认的是80,总之项目是通过下面的server_name:8000来访问的
14     # the domain name it will serve for
15     server_name xxx.xxx.xx.xx ; #这个ip就是服务器的ip
16     charset     utf-8;
17 
18     # max upload size
19     client_max_body_size 75M;   # adjust to taste
20 
21     # Django media
22     location /media  {
23         alias /your/project/media;  #这个目录是项目的meda目录
24     }
25    location /static {
26         alias /your/project/static; # 这个目录是项目的static目录
27     }
28 
29     # Finally, send all non-media requests to the Django server.
30     location / {
31         uwsgi_pass  django;#这个是对接uwsgi的
32         include     uwsgi_params; # 这个参数按我这样写nginx就能找到的
33     }
34 }
35   

将nginx配置文件链接到启动配置目录:

#注意修改下面的路径及文件名,哈哈不要只复制粘贴啊
sudo
ln -s ~/path/to/your/mysite/mysite_nginx.conf /etc/nginx/sites-enabled/
修改django项目中的setting.py文件,添加
#要将STATICFILES_DIRS =[os.path.join(BASE_DIR, 'static')]注释掉,Debug在生产模式也要改成False

STATIC_ROOT = os.path.join(BASE_DIR, "static/")

将静态文件打包,让nginx代理:

python manage.py collectstatic

启动nginx,uwsgi

 sudo /etc/init.d/nginx restart
#进入conf文件夹,或者说配置的uwsgi.ini文件所在目录
#uwsgi.ini改成自己的名字
uwsgi -i uwsgi.ini

访问:

ip:port(端口为nginx.conf中配置的)

总结:

写到这也差不多了,项目可以跑起来了,nginx,uwsgi高级配置还在学习中,希望本文对你有所帮助,谢谢。

最后再提醒下,网上有很多配置文件的模板,将我写注释的地方对比修改下,别遗漏。

 

参考文档:https://uwsgi.readthedocs.io/en/latest/tutorials/Django_and_nginx.html

     http://uwsgi-docs.readthedocs.io/en/latest/Nginx.html

 



]]>
java设计模式之代理模式 http://doc.okbase.net/jimoer/archive/294470.html 纪莫 2018/4/21 18:35:00

代理模式

代理模式是常见设计模式的一种,代理模式的定义是:为其他对象提供一种代理以控制对这个对象的访问

在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

静态代理

理解设计模式是比较枯燥的,所以还是以举例子的方式来进行理解,

例如:公司开年会想找个明星来表演,那么并不会直接联系明星(主要还是联系不上),而是会联系明星的经纪人,明星就是被代理的对象,而经纪人就是代理对象。明星只需要准备来参加年会时应该表演什么节目就可以,其他的出场费之类的事情就交给经纪人来处理就好了。代理对象可以理解为被代理对象的扩展,能做被代理对象不能做的事情,也可以调用代理对象做事情。

那么用代码实现这个场景是什么样子的呢?

执行合作方法的接口

/**
 * @Description: 经纪公司接口,代理对象和被代理对象都需要实现的接口
 */
public interface Company {
    /** 合作 */
    void cooperation();
}

被代理对象

/**
 * @Description: 目标对象-明星(被代理对象)
 */
public class Start implements Company {

    @Override
    public void cooperation() {
        System.out.println("is show time");
    }
}

代理对象

/**
 * @Description: 经纪人(代理对象)
 */
public class Agent implements Company {

    private Company company;

    public Agent(Company company)
    {
        this.company = company;
    }

    @Override
    public void cooperation()
    {
        System.out.println("收出场费,化妆等等");
        company.cooperation();
        System.out.println("收拾行李,打道回府");
    }
}

测试类

import org.junit.Test;

/**
 * @Description: 测试类
 */
public class ProxyTest {

    @Test
    public void AnnualMeeting()
    {
        //目标对象
        Start start = new Start();
        //构建代理对象,生成代理关系
        Agent agent = new Agent(start);
        //用代理对象执行被代理对象的动作
        agent.cooperation();
        
    }

}

输出结果:

收出场费,化妆等等
is show time
收拾行李,打道回府

静态代理的特点是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。有时候不方便修改别人的代码或者是引入的一个功能,需要进行功能扩展一下才能适用于自己的业务实现,可以使用代理模式来进行设计。

但是静态代理的实现基础是一个目标对象对应一个代理对象,并且在编译时就已经维护好了代理关系,如果目标对象是多个那么就会需要多个代理对象,这样在更新目标的对象的时候还需要更新代理对象,当代理对象持续增加时维护成本就变得非常困难。

针对于这种情况,动态代理应运而生。

动态代理

JDK代理

动态代理的代理对象不需要和目标对象共同实现接口,而是利用JDK的API,动态的在内存中构建代理对象。

动态生成代理对象需要调用JDK中的java.lang.reflect.Proxy类的newProxyInstance方法,这个方法需要三个参数:

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,]Class<?>[] interfaces,InvocationHandler h)
ClassLoader loader:类加载器,用来加载目标对象类,因为是在运行时获得目标对象,所以肯定需要用到反射。
Class<?>[] interfaces:目标对象类实现的接口集合,这些接口中定义目标对象可以执行的方法。
InvocationHandler h:这个参数代表的是动态代理对象在调用方法的时候,会将方法转发到哪一个invocationHandler对象身上,InvocationHandler是个接口,
需要自己实现它,然后定义自己的动态代理执行方法。
创建包含动态代理对象具体执行方法的实现类。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @Description: 包含动态代理对象具体执行方法的实现类
 */
public class MyInvocationHandler implements InvocationHandler {

    private Company company;

    public MyInvocationHandler(Company company)
    {
        this.company = company;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {

        System.out.println("收出厂费,化妆等");
        //具体执行方法
        Object result = method.invoke(company,args);

        System.out.println("收拾现场,卸妆,打道回府");

        return result;
    }
}

测试类

import org.junit.Test;
import java.lang.reflect.Proxy;

/**
 * @Description: 测试类
 */
public class DynamicProxyTest {

    @Test
    public void AnnualMeeting()
    {
        //创建目标对象
        Company start = new Start();
        //创建代理对象需要执行的方法处理对象
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(start);
        //获得目标对象的类加载器
        ClassLoader classLoader = start.getClass().getClassLoader();
        //创建动态代理对象
        Company proxy = (Company) Proxy.newProxyInstance(classLoader,start.getClass().getInterfaces(),myInvocationHandler);
        //用动态代理对象执行目标对象的方法
        proxy.cooperation();
    }
}

输出结果:

收出厂费,化妆等
is show time
收拾现场,卸妆,打道回府

JDK动态代理的特点:代理对象不需要实现接口,但是目标对象必须实现接口。

那么如果在实际的业务中目标对象确实没有实现接口,怎么办呢?

遇到这种情况的时候就需要时cglib动态代理了。

Cglib代理

Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

  • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现。
  • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)。
  • Cglib包的底层是通过使用一个小块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

在实现cglib代理时需要引入cglib的jar包,但是spring核心功能已经包含了cglib的功能,所以引入spring-core的jar包就可以了。

需要注意的是:代理的类不能为final,否则报错,目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。

没有实现接口的目标对象类

/**
 * @Description: 没有经纪公司的明星,就行像最近以个人练习生出道的蔡徐坤
 */
public class AloneStart {
    /** 合作 */
    public void cooperation() {
        System.out.println("is show time");
    }

}

生成Cglib代理对象的类

import org.mockito.cglib.proxy.Enhancer;
import org.mockito.cglib.proxy.MethodInterceptor;
import org.mockito.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * @Description: 生成代理对象的类
 */
public class CglibProxy implements MethodInterceptor {


    private AloneStart aloneStart;

    public CglibProxy(AloneStart aloneStart)
    {
        this.aloneStart = aloneStart;
    }

    /**
     * 创建代理对象
     * @return
     */
    public Object getProxyInstance()
    {
        //动态代理工具类
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(aloneStart.getClass());
        //设置回调函数调用对象
        enhancer.setCallback(this);
        //返回代理对象
        return enhancer.create();

    }

    @Override
    public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable
    {
        System.out.println("收出厂费,化妆等");
        //执行代理方法
        methodProxy.invokeSuper(obj,objects);
        System.out.println("卸妆,回家");
        return null;
    }
}

测试类

import org.junit.Test;

/**
 * @Description: 测试类
 */
public class CglibProxyTest {

    @Test
    public void cglibTest()
    {
        //创建目标对象
        AloneStart aloneStart = new AloneStart();
        //创建代理对象
        AloneStart startProxy = (AloneStart) new CglibProxy(aloneStart).getProxyInstance();
        //用代理对象执行目标对象的方法
        startProxy.cooperation();
    }
}

输出结果:

收出厂费,化妆等
is show time
卸妆,回家

jdk采用反射机制调用委托类的方法,而cglib采用类似索引的方式直接调用委托类方法;

还有需要注意的是:

在Spring的AOP中

如果加入容器的目标对象有实现接口,用JDK代理
如果目标对象没有实现接口,用Cglib代理

 参考:

Java的三种代理模式: https://www.cnblogs.com/cenyu/p/6289209.html

说说代理模式:http://www.importnew.com/26116.html

]]>
【JDK1.8】JUC——LockSupport http://doc.okbase.net/joemsu/archive/294469.html joemsu 2018/4/21 18:34:52

一、前言

Basic thread blocking primitives for creating locks and other synchronization classes.

用于创建锁定和其他同步类的基本线程阻塞原语(基础?)。

上面这段话是Java Doc对LockSupport的描述,表明了该类在实现锁当中的重要意义。因此我们先来查看一下其中的源码,看看它是如何实现的。


二、LockSupport成员变量分析

public class LockSupport {
    private static final sun.misc.Unsafe UNSAFE;
    private static final long parkBlockerOffset;
    private static final long SEED;
    private static final long PROBE;
    private static final long SECONDARY;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception ex) { throw new Error(ex); }
    }
}
  1. 首先要明确的就是sun.misc.Unsafe这个类,它是一个final class,里面有100多个方法,锁的实现也是依赖了这个类,其中基本上都是native方法。Java避免了程序员直接操作内存,但这不是绝对的,通过使用Unsafe类,我们还是能够操作内存。笔者尝试阅读里面的C++代码,奈何已经将知识都还给了老师。源码越看到后面,越觉得C和C++的伟大,膝盖瑟瑟打抖,有兴趣的园友们可以尝试着阅读以下:Unsafe C++源码

  2. parkBlockerOffset。从字面上看就是parkBlocker的偏移量,那么parkBlocker是干嘛的呢,从static代码块中可以看到,它属于Thread类,于是进去看看:

/**
 * The argument supplied to the current call to
 * java.util.concurrent.locks.LockSupport.park.
 * Set by (private) java.util.concurrent.locks.LockSupport.setBlocker
 * Accessed using java.util.concurrent.locks.LockSupport.getBlocker
 */
volatile Object parkBlocker;

从注释上看,就是给LockSupport的setBlocker和getBlocker调用。另外在LockSupport的java doc中也写到:

This object is recorded while the thread is blocked to permit monitoring and diagnostic tools to identify the reasons that threads are blocked. (Such tools may access blockers using method [getBlocker(Thread).) The use of these forms rather than the original forms without this parameter is strongly encouraged. The normal argument to supply as a blockerwithin a lock implementation is this.

大致是说,parkBlocker是当线程被阻塞的时候被记录,以便监视和诊断工具来识别线程被阻塞的原因。

Unsafe类提供了获取某个字段相对 Java对象的“起始地址”的偏移量的方法objectFieldOffset,从而能够获取该字段的值。

那么为什么记录该blocker在对象中的偏移量,而不是直接调用Thread.getBlocker(),这样不是更好,原因其实很好理解,当线程被阻塞(Blocked)的时候,线程是不会响应的。另外通过反射应该也可以拿到。


三、LockSupport的重要方法

类中的方法主要分为两类:park(阻塞线程)和unpark(解除阻塞)。

首先强调的一点事park方法阻塞的是当前的线程,也就是说在哪个线程中调用,那么哪个线程就被阻塞(在没有获得许可的情况下)。

重点讲其中的几个:

3.1 park()解析

public static void park() {
    UNSAFE.park(false, 0L);
}

UNSAFE.park的两个参数,前一个为true的时候表示传入的是绝对时间,false表示相对时间,即从当前时间开始算。后面的long类型的参数就是等待的时间,0L表示永久等待。

根据java doc中的描述,调用park后有三种情况,能使线程继续执行下去:

  1. 有某个线程调用了当前线程的unpark。
  2. 其他线程中断(interrupt)了当前线程
  3. 该调用不合逻辑地(即毫无理由地)返回。

验证一:

public class UnparkTest {
    public static void main(String[] args) throws InterruptedException {
        Thread ut = new Thread(new UnparkThread(Thread.currentThread()));
        ut.start();
        System.out.println("I'm going to call park");
        // Thread.sleep(1000L);
        LockSupport.park();
        System.out.println("oh, I'm running again");

    }
}

class UnparkThread implements Runnable {
    private final Thread t;
    UnparkThread(Thread t) {
        this.t = t;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("I'm in unpark");
        LockSupport.unpark(t);
        System.out.println("I called unpark");
    }
}

结果:

I'm going to call park
I'm in unpark
I called unpark
oh, I'm running again

另外值得一提的是,LockSupport对park和unpark的调用顺序并没有要求,将两个Thread.sleep(1000L);注释切换一下就可以发现,先调用unpark,再调用park,依旧可以获得许可,让线程继续运行。这一点与Object的 wait 和 notify 要求固定的顺序不同,其实现原理可以看这里


验证二:

public class LockSupportInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new InterruptThread());
        t.start();
        Thread.sleep(1000L);
        System.out.println("I'm going to interrupt");
        t.interrupt();
    }
}

class InterruptThread implements Runnable {
    @Override
    public void run() {
        System.out.println("I'm going to park");
        LockSupport.park();
        System.out.println("I'm going to again");
    }
}

运行结果:

I'm going to park
I'm going to interrupt
I'm going to again

LockSupport的park能够能响应interrupt事件,且不会抛出InterruptedException异常。


3.2 park(Object blocker)

park的另一个重载方法需要传入blocker对象:

public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

在理解了parkBlocker的作用后,这个方法里的代码就很好理解了。

  1. 在调用park阻塞当前线程之前,先记录当前线程的blocker。
  2. 调用park阻塞当前线程
  3. 当前面提到的三个让线程继续执行下去的情况时,再将parkBlocker设置为null,因为当前线程已经没有被blocker住了,如果不设置为null,那诊断工具获取被阻塞的原因就是错误的,这也是为什么要有两个setBlocker的原因。

再看一下setBlocker的代码:

private static void setBlocker(Thread t, Object arg) {
    // Even though volatile, hotspot doesn't need a write barrier here.
    UNSAFE.putObject(t, parkBlockerOffset, arg);
}

方法是私有的,嗯,为了保证正确性,肯定不能被其他类调用。

另外就是利用了之前提到的偏移量以及unsafe对象将blocker值设置进了线程t当中。


3.3 unpark(Thread thread)

public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

这就很简单了,判断是否为空,然后调用unsafe的unpark方法。由此更可见unsafe这个类的重要性。


四、各种例子

4.1 jstack查看parkBlocker

前面提到parkBlocker提供了调试工具上面查找原因,所以我们来看一下在jstack上面是什么情况:

public class JstackTest {
    public static void main(String[] args) {
        // 给main线程设置名字,好查找一点
        Thread.currentThread().setName("jstacktest");
        LockSupport.park("block");
    }
}

利用park(blocker)来阻塞main线程,传入string作为parkBlocker。

运行之后,在shell里运行:

> jps
37137 Jps
4860 
37132 Launcher
37133 JstackTest

可以看到我们的java线程的pid,JstackTest这个类对应的是37133,然后再利用jstack来查看:

> jstack -l 37133
"jstacktest" #1 prio=5 os_prio=31 tid=0x00007f7f07001800 nid=0x2903 waiting on condition [0x0000700000901000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000079582f5d0> (a java.lang.String)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at lock.JstackTest.main(JstackTest.java:11)

   Locked ownable synchronizers:
        - None

省略了一部分,可以看到jstacktest线程的状态是Waiting on condition(等待资源,或等待某个条件的发生),同事可以看到这样一句话:parking to wait for <0x000000079582f5d0> (a java.lang.String)。

<0x000000079582f5d0>的类型是String,也就是之前传入park里的block字符串。而0x000000079582f5d0估计就是其地址(待验证)。


4.2 利用LockSupport实现先进先出锁

在来看一下java doc上提供的示例:

class FIFOMutex {
    private final AtomicBoolean locked = new AtomicBoolean(false);
    private final Queue<Thread> waiters
        = new ConcurrentLinkedQueue<Thread>();

    public void lock() {
        boolean wasInterrupted = false;
        Thread current = Thread.currentThread();
        waiters.add(current);
        while (waiters.peek() != current ||
               !locked.compareAndSet(false, true)) {
            LockSupport.park(this);
            if (Thread.interrupted())
                wasInterrupted = true;
        }

        waiters.remove();
        if (wasInterrupted)
            current.interrupt();
    }

    public void unlock() {
        locked.set(false);
        LockSupport.unpark(waiters.peek());
    }
}

先进先出锁就是先申请锁的线程最先获得锁的资源,实现上采用了队列再加上LockSupport.park。

  1. 将当前调用lock的线程加入队列
  2. 如果等待队列的队首元素不是当前线程或者locked为true,则说明有线程已经持有了锁,那么调用park阻塞其余的线程。
  3. 如果队首元素是当前线程且locked为false,则说明前面已经没有人持有锁,删除队首元素也就是当前的线程,然后当前线程继续正常执行。
  4. 执行完后调用unlock方法将锁变量修改为false,并解除队首线程的阻塞状态。此时的队首元素继续之前的判断。


五、总结

到这里,对LockSupport有了简单的认识,如果还想深入了话,就要开始阅读C++里面的代码了。后面有机会再重拾C++。最后谢谢各位园友观看,如果有描述不对的地方欢迎指正,与大家共同进步!




参考:java-LockSupport详解

]]>
Git的安装和使用(托管至GitHub的方法) http://doc.okbase.net/chestnut-egg/archive/294468.html chestnut_egg 2018/4/21 18:11:20

一、下载Git

 

1.下载

下载地址:

https://git-scm.com/download/win

根据你的操作系统选择32位或者64位

2.安装过程一路next

3.检验安装是否成功

在桌面点击右键,如果出现这两个选项代表安装成功

 

二、Git初始化及使用

1.点击右键选择 Git Bash Here

输入以下命令:

git  config -- global  user.name  '你的用户名';
git  config -- global  user.email  '你的邮箱';

2.配置后可以输入此条命令看是否设置成功

git config --list

3.创建本地仓库

 创建一个文件夹,用作你的仓库

打开这个文件夹,在这个目录下右键,选择Git Bash Here

 输入此条命令:

git init

 4.然后文件夹中会出现一个.git的文件夹

该文件夹属性为隐藏

5.将你的项目复制到此文件夹中

6.将文件提交到缓冲区

执行以下命令

git status

发现飘红了,这是因为我们没有将文件提交到缓存区

使用这条命令可将所有文件提交到缓冲区

注意:add 与 . 之间有一个空格,代表目录下的所有文件,如果只提交一个文件,也可以输入这个文件的文件名

git add .

再次使用

git status

绿色就很舒服~

6.将缓冲区文件提交到本地仓库

 输入命令:

git commit -m "你的备注"

 

 二、将项目托管至GitHub

1.创建GitHub账号

地址:https://github.com/

2.点击右上角头像,选择your profile

3.创建项目

 

 

4.回到之前的本地仓库文件夹

执行之前说过的命令

git add . 
git commit -m "你的备注"

然后下面这条使用之前让你记住的地址

git remote add origin https://github.com/....

如果是第一次就执行

git push -u origin master

如果不是第一次,是上传更新后的代码就执行

git push origin master

5.在GitHub上可以看到项目文件和提交记录了

 

最后我把命令都汇总了一下,方便使用

git config --global user.name ""
git config --global user.email ""

git init

git add .      
git status
git commit -m ""

git remote add origin https://github.com/....

git push -u origin master
git push origin master

 

]]>
jenkins构建个人github上的项目 http://doc.okbase.net/clovejava/archive/294467.html 贝克田庄 2018/4/21 18:11:13

最近刚进一家新公司,公司采用的是自动化集成测试工具jenkins进行,构建,部署项目

因为以前,没接触过这类工具,所以打算在自己本机安装一个jenkins进行学习

具体安装步骤,很简单,不做讲解

1、安装完成后进入该界面(注意在安装后进入界面,会提示安装插件,安装它推荐的插件就行,安装过程由于,插件之间的依赖关系,和网络等其它因素的影响下可能会出现失败,多重试几次就行)

2、界面简介:

新建:表示创建需要构建的项目,后面会演示

用户:当前jenkins中用户的列表

任务历史:用于查看构建的列表

系统管理:可以设置系统变量,安装插件等功能(下图是已经安装的插件)

 

3、新建项目

输入项目名,选择构建自由风格的项目,点击确定

简单的添加一些描述信息

从上到下,依次是:github项目地址、github用户名和密码、选择要构建项目的分支、github地址

同时图上的源码库浏览器选择githubweb

点击应用和保存即可

 

 可以看到我们刚才创建的项目,项目现在前面的圆球还是灰色的说明还没有构建

如图点击立即构建

可以看到右侧的地方正在构建

现在小球变成了蓝色,表示已经构建成功了

至此一个简单的通过jenkins进行项目的自动化构建算是完成了

 

]]>
Java 中的时间日期 API http://doc.okbase.net/yangming1996/archive/294466.html Single_Yam 2018/4/21 17:47:37

自从 14 年发布 Java 8 以后,我们古老 java.util.Date 终于不再是我们 Java 里操作日期时间的唯一的选择。

其实 Java 里的日期时间的相关 API 一直为世猿诟病,不仅在于它设计分上工不明确,往往一个类既能处理日期又能处理时间,很混乱,还在于某些年月日期的数值映射存储反人类,例如:0 对应月份一月,11 对应月份十二月,118 对应年份 2018(1900 + 118)等。

往往我们得到某个年月值还需要再做相应的运算才能得到准确的年月日信息,直到我们的 Java 8 ,借鉴了第三方开源库 Joda-Time 的优秀设计,重新设计了一个日期时间 API,相比之前,可以说好用百倍,相关 API 接口全部位于包 java.time 下。

古老的日期时间接口

表示时刻信息的 Date

世界上所有的计算机内部存储时间都使用一个 long 类型的整数,而这个整数的值就是相对于英国格林尼治标准时间(1970年1月1日0时0分0秒)的毫秒数。例如:

public static void main(String[] args){
    //January 1, 1970 00:00:00 GMT.
    Date date = new Date(1000);
    System.out.println(date);
}

输出结果:

//1970-1-1 8:00:01
Thu Jan 01 08:00:01 CST 1970

很多人可能会疑惑,1000 表示的是距离标准时间往后 1 秒,那为什么时间却多走了 八个小时?

这和「时区」有关系,如果你位于英国的格林尼治区,那么结果会如预想一样,但是我们位于中国东八区,时间要早八个小时,所以不同时区基于的基础值不同。

Date 这个类以前真的扮演过很多角色,从它的源码就可以看出来,有可以操作时刻的方法,有可以操作年月日的方法,甚至它还能管时区。可以说,日期时间的相关操作有它一个人就足够了。

但这个世界就是这样,你管的东西多了,自然就不能面面俱到,Date 中很多方法的设计并不是很合理,之前我们也说了,甚至有点反人类。所以,现在的 Date 类中接近百分之八十的方法都已废弃,被标记为 @Deprecated。

sun 公司给 Date 的目前定位是,唯一表示一个时刻,所以它的内部应该围绕着那个整型的毫秒,而不再着重于各种年历时区等信息。

Date 允许通过以下两种构造器实例化一个对象:

private transient long fastTime;

public Date() {
    this(System.currentTimeMillis());
}

public Date(long date) {
    fastTime = date;
}

这里的 fastTime 属性存储的就是时刻所对应的毫秒数,两个构造器还是很简单,如果调用的是无参构造器,那么虚拟机将以系统当前的时刻值对 fastTime 进行赋值。

还有几个为数不多没有被废弃的方法:

  • public long getTime() :返回内部存储的毫秒数
  • public void setTime(long time):重新设置内存的毫秒数
  • public boolean before(Date when):比较给定的时刻是否早于当前 Date 实例
  • public boolean after(Date when):比较给定的时刻是否晚于当前 Date 实例

还有两个方法是 jdk1.8 以后新增的,用于向 Java 8 新增接口的转换,待会介绍。

描述年历的 Calendar

Calendar 用于表示年月日等日期信息,它是一个抽象类,所以一般通过以下四种工厂方法获取它的实例对象。

public static Calendar getInstance()

public static Calendar getInstance(TimeZone zone)

public static Calendar getInstance(Locale aLocale)

public static Calendar getInstance(TimeZone zone,Locale aLocale)

其实内部最终会调用同一个内部方法:

private static Calendar createCalendar(TimeZone zone,Locale aLocale)

该方法需要两个参数,一个是时区,一个是国家和语言,也就是说,构建一个 Calendar 实例最少需要提供这两个参数信息,否则将会使用系统默认的时区或语言信息。

因为不同的时区与国家语言对于时刻和年月日信息的输出是不同的,所以这也是为什么一个 Calendar 实例必须传入时区和国家信息的一个原因。看个例子:

public static void main(String[] args){


    Calendar calendar = Calendar.getInstance();
    System.out.println(calendar.getTime());

    Calendar calendar1 = Calendar.getInstance
            (TimeZone.getTimeZone("GMT"), Locale.ENGLISH);
    System.out.println( calendar1.get(Calendar.YEAR) + ":" +
                        calendar1.get(Calendar.HOUR) + ":" +
                        calendar1.get(Calendar.MINUTE));
    }

输出结果:

Sat Apr 21 10:32:20 CST 2018
2018:2:32

可以看到,第一个输出为我们系统默认时区与国家的当前时间,而第二个 Calendar 实例我们指定了它位于格林尼治时区(0 时区),结果也显而易见了,相差了八个小时,那是因为我们位于东八区,时间早于 0 时区八个小时。

可能有人会疑惑了,为什么第二个 Calendar 实例的输出要如此复杂的拼接,而不像第一个 Calendar 实例那样直接调用 getTime 方法简洁呢?

这涉及到 Calendar 的内部实现,我们一起看看:

protected long          time;

public final Date getTime() {
    return new Date(getTimeInMillis());
}

和 Date 一样,Calendar 的内部也维护着一个时刻信息,而 getTime 方法实际上是根据这个时刻构建了一个 Date 对象并返回的。

而一般我们构建 Calendar 实例的时候都不会传入一个时刻信息,所以这个 time 的值在实例初始化的时候,程序会根据系统默认的时区和当前时间计算得到一个毫秒数并赋值给 time。

所以,所有未手动修改 time 属性值的 Calendar 实例的内部,time 的值都是当时系统默认时区的时刻数值。也就是说,getTime 的输出结果是不会理会当前实例所对应的时区信息的,这也是我觉得 Calendar 设计的一个缺陷所在,因为这样会导致两个不同时区 Calendar 实例的 getTime 输出值只取决于实例初始化时系统的运行时刻。

Calendar 中也定义了很多静态常量和一些属性数组:

public final static int ERA = 0;

public final static int YEAR = 1;

public final static int MONTH = 2;

public final static int WEEK_OF_YEAR = 3;

public final static int WEEK_OF_MONTH = 4;

public final static int DATE = 5;
....
protected int           fields[];

protected boolean       isSet[];
...

有关日期的所有相关信息都存储在属性数组中,而这些静态常量的值往往表示的就是一个索引值,通过 get 方法,我们传入一个属性索引,返回得到该属性的值。例如:

Calendar myCalendar = Calendar.getInstance();
int year = myCalendar.get(Calendar.YEAR);

这里的 get 方法实际上就是直接取的 fields[1] 作为返回值,而 fields 属性数组在 Calendar 实例初始化的时候就已经由系统根据时区和语言计算并赋值了,注意,这里会根据你指定的时区进行计算,它不像 time 始终是依照的系统默认时区

个人觉得 Calendar 的设计有优雅的地方,也有不合理的地方,毕竟是个「古董」了,终将被替代。

DateFormat 格式化转换

从我们之前的一个例子中可以看到,Calendar 想要输出一个预期格式的日期信息是很麻烦的,需要自己手动拼接。而我们的 DateFormat 就是用来处理格式化字符串和日期时间之间的转换操作的。

DateFormat 和 Calendar 一样,也是一个抽象类,我们需要通过工厂方式产生其实例对象,主要有以下几种工厂方法:

//只处理时间的转换
public final static DateFormat getTimeInstance()

//只处理日期的转换
public final static DateFormat getDateInstance()

//既可以处理时间,也可以处理日期
public final static DateFormat getDateTimeInstance()

当然,它们各自都有各自的重载方法,具体的我们待会儿看。

DateFormat 有两类方法,format 和 parse。

public final String format(Date date)

public Date parse(String source)

format 方法用于将一个日期对象格式化为字符串,parse 方法用于将一个格式化的字符串装换为一个日期对象。例如:

public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
    DateFormat dateFormat = DateFormat.getDateTimeInstance();
    System.out.println(dateFormat.format(calendar.getTime()));
}

输出结果:

2018-4-21 16:58:09

显然,使用工厂构造的 DateFormat 实例并不能够自定义输出格式化内容,即输出的字符串格式是固定的,不能满足某些情况下的特殊需求。一般我们会直接使用它的一个实现类,SimpleDateFormat。

SimpleDateFormat 允许在构造实例的时候传入一个 pattern 参数,自定义日期字符的输出格式。例如:

public static void main(String[] args){    
    DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日");
    System.out.println(dateFormat.format(new Date()));
}

输出结果:

2018年04月21日

其中,

  • yyyy:年份用四位进行输出
  • MM:月份用两位进行输出
  • dd:两位表示日信息
  • HH:两位来表示小时数
  • mm:两位表示分钟数
  • ss:两位来表示秒数
  • E:表示周几,如果 Locale 在中国则会输出 星期x,如果在美国或英国则会输出英文的星期
  • a:表示上午或下午

当然,对于字符串转日期也是很方便的,允许自定义模式,但必须遵守自己制定的模式,否则程序将无法成功解析。例如:

public static void main(String[] args){
    String str = "2018年4月21日 17点17分 星期六";
    DateFormat sDateFormat = new SimpleDateFormat("yyyy年M月dd日 HH点mm分 E");
    sDateFormat.parse(str);
    System.out.println(sDateFormat.getCalendar().getTime());
}

输出结果:

Sat Apr 21 17:17:00 CST 2018

显然,程序是正确的解析的我们的字符串并转换为 Calendar 对象存储在 DateFormat 内部的。

总的来说,Date、Calendar 和 DateFormat 已经能够处理一般的时间日期问题了,但是不可避免的是,它们依然很繁琐,不好用。

限于篇幅,我们下篇将对比 Java 8 的新式日期时间 API,你会发现它更加优雅的设计和简单的操作性。


文章中的所有代码、图片、文件都云存储在我的 GitHub 上:

(https://github.com/SingleYam/overview_java)

欢迎关注微信公众号:扑在代码上的高尔基,所有文章都将同步在公众号上。

image

]]>
【笔记】得到-《薛兆丰的经济学课》模块一:人性与稀缺 http://doc.okbase.net/smallmars/archive/294465.html 小火星_Hirsi 2018/4/21 17:00:56

发刊词经济学之所以有如此魅力,原因在于它是以研究陌生人互动规律为己任的学问。人的认知和判断,至今主要还是靠直觉和短距离的人际关系来驱动,但人的身体和际遇,却早就已经暴露在大规模的陌生人的精妙协作之中了。这两者的巨大反差,使得很多聪明绝顶的社会达人,对复杂社会关系和经济运行规律的理解,仍然停留在幼稚的阶段。一个人在他自己的专业领域可以非常成功,但要理解现代社会的运行机制,还需要学习另外一种智慧。
 

人性与稀缺(0-12讲) 

 

一、经济学视角

001讲|战俘营里的经济组织

战俘营肯定不是什么市场经济,但它里面发生的事情,人们所遵循的行为规律,跟外面的世界有多大差别呢?里面的世界是不是很独特呢?
1.有人的地方就有交易
“ 事实上,一个战俘物质享受水平的显著提高,不是依靠自身攫取生活必需品的能力,而是通过商品和服务交换得以实现的。”
即物质的总量哪怕不发生变化,只要人与人之间能够进行交易,幸福就能够无中生有地产生。
2.战俘营中的价格与价格变动
有了市场就会有价格,有了价格就会有价格波动。
面包的价格随着稀缺而变化。
3.战俘营里的货币
有了交易就会产生对货币的需求。战俘营用香烟来做货币。
有了货币,就有所谓的劣币驱逐良币。他们会把烟重拆,混上一些发丝卷的细一点去交易。
4.战俘营里的通胀与通缩
当发生空袭,生命受到威胁,就有今朝有酒今朝醉的心态,疯狂抽烟。第二天就会出现通货紧缩,烟就更值钱,一根香烟可以买更多的面包。
当红十字会送来一批新烟,货币流通量增加,一个面包就卖到两根香烟。
5.战俘营里的跨境贸易
英国人喜欢喝茶,战俘营外面的德国人喜欢喝咖啡,这时候英国人就会把他们分配到的咖啡存起来,偷偷把它运到铁丝网外面去,跨过铁丝网,换外面的茶叶,这叫跨境贸易。
6.战俘营里的户籍制度
在这个战俘营里面,德国人对欧洲人还算是比较友善的,那些来自法国、俄罗斯、意大利、南斯拉夫的战俘,他们可以在不同的营房之间走动。德国人最恨英国人和美国人,英国战俘、美国战俘不能到处走动。
这时候,欧洲战俘做生意的机会就更大一点。当然,英国人和美国人如果给点贿赂,也能走动,但毕竟增加了麻烦,增加了障碍。
7.战俘营里的舆论压力
有些人认为交易不透明,有些人认为应该实施基准定价。
8.战俘营中的仇富情绪
有交易就会有信息不对称,有信息不对称就会有中间商。
对中间商普遍存在敌意,认为中间商不老的,巧取豪夺。
 

002讲|马粪争夺案

鼓励创造财富的人,不鼓励对财富做出标志。
“公正的背后其实是效率的考量。在司法实践里面,各国都有这么一条惯例,就是犯人如果配合司法部门自首认罪,那么他们的量刑就会比较轻。”
只有那些让社会里每个人都有积极性去积累财富的规则,才是公正的规则;只有那些让社会能够存活下来的规则,才是公正的规则。也就是说,效率决定了公平。
 

003讲|看得见的和看不见的

经济学是一门研究比较和选择的学问。
要做出正确选择,你首先得把要比较的东西拿出来,放在天秤两边去对比。经济学则教你,在比较的时候不仅要看见那些看得见的东西,同时也要看见那些看不见的东西。
 

004讲|经济学不关心阴谋论

经济学家关心的,是那些人们出于良好的愿望而产生的有害的经济政策,那些事与愿违的现象。如果你要问,经济学家和普通大众有什么区别的话,其中一个很重要的区别,就是经济学家不关心阴谋论,他只关心事与愿违的因果规律。
比方说最低工资的例子,比方说价格管制的例子,美国禁酒的例子,美国禁枪的例子等等。特别有意思的是,有人还讲到,印度曾经为了鼓励大家消灭眼镜蛇,提出了一个悬赏方案,谁杀的眼镜蛇多谁得奖就多,结果眼镜蛇的数目不减反增。为什么呢?因为大家都去饲养眼镜蛇去了。
 

二、人的本性

005讲|不确定性,进化与经济理论

阿尔钦则说,首先世界是充满不确定性的,从统计学的观点看,只要存在不确定性,咱们就没办法算出所谓的最优解,顶多只有一个最优的概率区间。
经济学关心的是存活条件。一个人,一个组织,甚至一个制度,能不能存活下来,和各种条件、情况的变化有关,而跟人是不是理性的没有关系。
如果一个人不理性的话,他承受的成本、他承受的代价不是那么高的话,人们就宁愿不理性;但是如果他们承担的代价足够高的话,他们就会变得比较理性。
阿尔钦在他1950年的文章里面就有一句警句,他说,"Nothing succeeds like success",译成中文就是“成功是最大的成功”,你真的成功了,说什么都是对的。
检验一流智力的标准,就是看你能不能在头脑中同时存在两种相反的想法,还维持正常行事的能力。—《了不起的盖茨比》
 

006讲|亚当斯密的人性观

斯密的名言我们都记得:“每一个人,不需要自己关心社会福利,他也不知道自己怎么去推动社会的福利。他只需要关心自己,追求他自己的福利就可以了。但是他在追求自己福利的过程中,会有一只看不见的手,让他的努力转变为对公共事业的推动。这只看不见的手,会让他的自私自利推动社会福利的改进。”
人是自私的,但人有同情心。
人的同情心,是随着人与人之间距离的拉远而急速下降的。
市场是一个陌生人跟陌生人之间打交道的地方,是一个陌生人服务陌生人的地方。
人际互动二分法:小圈子靠爱心,大世界靠市场在小圈子里面,我们讲的是爱心;在大圈子里面,在陌生人的范围里面,我们讲的是规则。
 

007讲|铅笔的故事

一支简单的铅笔,在这世界上从来没有人能够掌握生产它的全部知识,它需要成千上万人通力合作,才能生产制造出来。而这些共同合作生产这支铅笔的人,可以互不相识,彼此憎恨,甚至互相敌视,但这并不妨碍他们合作生产一支铅笔。而当这支铅笔被生产出来之后,我们每个人只需要付出很小的代价就能得到它。这个神奇的故事之所以能够发生,是因为有市场机制在协调人们分工与合作。

 

008讲|商业是最大的慈善

由于存在缺乏反馈机制、所托非人、养懒汉效应等问题,行善扶贫效果往往大打折扣,完全无法到达预期目标。而商业行为,则由于有市场机制发挥作用,协调和鼓励人们分工合作,所以能大幅、持续而高效地改进人们的福利。
帮助弱者才叫慈善。
 
 

三、稀缺与选择

009讲|稀缺

我们把经济学建立在“稀缺”这个前提上,但稀缺并不是一个假设。因为只要我们活在世界上,就必须面对稀缺,稀缺是一个基本的事实。稀缺有两个原因:
1)你想要的东西别人也想要。
2)人的需求在不断变化,不断升级。
 

010讲|选择与歧视

稀缺——东西不够——这时候你就必须做选择,一旦做选择,你就必须有选择的标准,而选择的标准就是区别对待,区别对待就是歧视。
稀缺、选择、区别对待和歧视这四个概念,其实是一体的,只要有一个就意味着同时有其他三个。
稀缺必然导致歧视。我们不应该问要不要歧视,而是要问:应该如何歧视。
好的歧视,不损害优秀者的短期利益,不损害弱者的长期利益,要给弱者真正自立自强的竞争机会。
 

011讲|凡歧视必得付出代价

歧视的起源:1.偏好;2.信息不对称;
当你不需要支付多少代价的时候,你会纵容自己歧视的习惯;当你要付出很高代价的时候,就会节制自己歧视的习惯。
小城镇的歧视比大城市严重,因为在小城市,歧视别人不会有多大的损失。市场竞争越激烈的地方,就是越能够做到豁达开放、兼容并包的地方。
 

012讲|歧视的作用和限制歧视的恶果

每当讲起美国的金融危机,很多人就说:这是市场失败,这是资本主义的失败。很少有人明白,其实真正的原因,是政府强迫商业银行改变区别对待贷款申请人的标准造成的。这不是市场失败,而是政府失败。
]]>
深入理解.net - 2.多态 Polymorphsim http://doc.okbase.net/Nuss/archive/294464.html Nuss 2018/4/21 17:00:49

通过上篇文章 继承的本质 深入介绍了继承过程中对象的的创建过程,相信对继承已经有了一个深入的理解,本文则详细剖析一下面向对象设计的另一要素多态(Polymorphsim)

什么是多态

官方MSDN上是这样描述的点此可查看原文连接

Polymorphism is a Greek word that means "many-shaped" and it has two distinct aspects:
At run time, objects of a derived class may be treated as objects of a base class in places such as method parameters and collections or arrays. When this occurs, the object's declared type is no longer identical to its run-time type.
Base classes may define and implement virtual methods, and derived classes can override them, which means they provide their own definition and implementation. At run-time, when client code calls the method, the CLR looks up the run-time type of the object, and invokes that override of the virtual method. Thus in your source code you can call a method on a base class, and cause a derived class's version of the method to be executed.

大体意思就是:

  • 在运行时,对象的类型可以和运行时类型不同,派生类在某些地方可以被替换成基类,如方法的参数,集合或者数组。
  • 基类可以定义或实现虚方法,派生类能够重写父类的虚方法,这就意味派生类可以提供他们自己的定义和实现。在运行时,当客户端代码调用这些方法,CLR查找对象的运行时类型,并调用重写的虚方法。因此,你可以使用基类类型调用方法,从而执行派生类的一个实现。ps:翻译的水平一般,大体意思应该写出来了 :)

总结一下就是:

  • 1.派生类(子类)可以以基类(父类)类型出现;
  • 2.在运行时,派生类(子类)以自己的方式来实现;
  • 3.派生类(子类)以基类(父类)的类型使用时,只能使用父类共有或重写的方法,即特有的属性和方法不可以使用;

通俗简单来说就是,声明的时候是基类的类型,而实例化(new)的是派生类,举个栗子

public class Food
{
    private string name = "Food";

    public virtual string GetName()
    {
        return name;
    }
}

public class Bread : Food
{
    private string name="Bread";

    public override string GetName()
    {
        return name;    
    }
}

public class ButterBread : Bread
{
    private string name = "ButterBread";

    public override string GetName()
    {
        return name;
    }
}

Food food = new Bread(); 当我们调用 food.GetName() 时,执行的是 Bread.GetName() 方法,得到的是 "Bread"。如果我们这么写Food food = new ButterBread();,当调用 food.GetName() 时,执行的则是 ButterBread.GetName() 方法,这就是多态,有没有很简单!允许同一个类型具有不同的行为,通过例子是不是比上文扯的一堆要更好理解。

一张图轻松GET多态本质

相信上图很直观的对比了Food food = new Bread();Bread bread = new Bread();之前的区别:二者唯一的不同是在堆栈上维护的Bread实例的地址引用类型不同,food是Food类型的,bread是Bread类型的,但是二者指向的GCHeap上的实例都是Bread类型的实例。简单说明下,TypeHandle(类型句柄)是指向加载该类型的相关元数据信息包括方发表,静态字段等。CLR再第一次加载类型的时候就会创建该类型的方法表,并按继承关系将所有父类的方法copy一份,重写的方法会使用自己的方法覆盖掉。所以多态实际上在运行时执行的还是具体实例化类型的方法表。分享下我一般这么理解:我们吃的是食物,而它是面包。说到底还是要看吃到的是啥。关于详细的LoaderHeap介绍可以参考文末参考链接。

一些思考

面对日渐复杂多变的业务需求,如何使我们的程序更灵活,易扩展,当然是面向抽象编程了,抽象即稳定的,而多态是我们得以实现这一原则的基础。同时掌握了继承多态之后,我们才能更好的学习和理解设计模式。另外发现:看英文的文章比看中文的更容易理解。大家尽量多看些英文的技术文章,不要怕!手边常备个词典就好。

参考

]]>
用js来实现那些数据结构09(集合01-集合的实现) http://doc.okbase.net/zaking/archive/294463.html Zaking 2018/4/21 17:00:42

  说到集合,第一个想到的就是中学学到的那个数学概念:集合。在我们开始集合相关的js实现前,我们有必要来了解一下什么是集合以及集合的数学概念。

  好吧,我们一起来复习一下早就被我们遗忘的集合。

  集合是由一组无序且唯一的项组成的。集合这个数据结构使用了与有限集合相同的数学概念。在数学中,集合是指具有某种特定性质的具体的或抽象的对象汇总成的集体,这些对象称为该集合的元素。

  比如,一个包括0到9十个数字的集合表示为:N = {0,1,2,3,4,5,6,7,8,9}。集合中的对象列表用{}(大括号)包围。还有一个概念叫做空集,也就是该集合中不包含任何元素,也就是{},空集是任何集合的子集。

  除了集合的基本概念,还有一些简单的集合操作,比如并集、交集、差集和子集等。在后面会详细的介绍这些集合的操作。

  那么集合的数据概念就简单介绍完了。我们看看如何去创建一个集合类(set)。

function Set() {
    let items = {};
}

  嗯,这就是set的骨架,哎??我记得好像ES6中就有set这个东东啊?嗯...是的,我们会在后面(下一篇)简单介绍下ES6原生的set类。

  这里我们使用对象而不是数组来表示集合。其实用数组也是可以的。那么是不是说,前面学过的栈和队列也都可以用对象来实现?是的,不要怀疑可行性。因为其实我们在改进这两个数据结构的时候用的就是weapMap这种ES6新增的结构。

  那么接下来要说一下set类有哪些可用的方法。

  1、add(value):向集合中添加一个新的项。

  2、delete(value):从集合移除一个值。

  3、has(value):如果值在集合中,返回true,否则返回false。

  4、clear():清空集合中的所有元素。

  5、size():返回集合所包含元素的数量。

  6、values():返回一个包含集合中所有值的数组。

function Set() {
    let items = {};
    //判断该set实例中是否存在该value
    this.has = function (value) {
        //检查它(或其原型链)上是否包含具有指定名称的属性的对象。但是in运算符会查找其原型链上的属性。所以我们用下面的方法更好
        //return value in items;
        //hasOwnProperty方法可以用来检测一个对象是否含有特定的自身属性;和 in 运算符不同,该方法会忽略掉那些从原型链上继承到的属性。
        //所以我们也可以用hasOwnProperty来判断一个对象的自身属性是否存在
        return items.hasOwnProperty(value);
    }
    this.add = function (value) {
        //通过我们上面写的has方法来判断这个值是否存在,如果不存在就添加进去,存在就返回false
        if(!this.has(value)) {
            items[value] = value;
            return true;
        }
        return false;
    }
    //同样的道理,判断该set中是否有要删除的对象,如果有就删除,没有就返回false
    this.remove = function (value) {
        if(this.has(value)) {
            delete items[value];
            return true;
        }

        return false;
    }
    //直接充值items为空,就变相的清空了items中的所有属性
    this.clear = function() {
        items = {};
    }
    //Object.keys是ES6中为对象新增的原生方法,它会返回一个数组,其中包含对象的所有元素,这样我们就可以获取其元素的个数了。
    this.size = function () {
        return Object.keys(items).length;
    }
    //上面我们用ES6新方法来获取items的长度,但是或许有些浏览器的兼容性不是很好。所以我们也可以用循环遍历计数的方式来完成这个功能
    this.sizeLegacy = function () {
        let count = 0;
        for(let key in items) {
            if(items.hasOwnProperty(key))
            ++count;
        }
        return count;
    }

    this.values = function () {
        let values = [];
        for(let i = 0,keys = Object.keys(items);i < keys.length; i++) {
            values.push(items[keys[i]]);
        }
        return values;
    }

    this.valuesLegacy = function () {
        let values = [];
        for(let key in items) {
            if(items.hasOwnProperty(key)) {
                values.push(items[key])
            }
        }
        return values;
    }

}

var set = new Set();
set.add(1);
console.log(set.values());//[1]
set.add(2);
console.log(set.values());//[1, 2]
console.log(set.size());//2
set.remove(2);
console.log(set.values());//[1]

  这样我们就完整的实现了我们自定义的set类,发没发现我的注释越来越少了,越到后面的学习也就越简单了。因为其实很多东西都是类似的,有它共同的point。

  好了,集合的实现我们已经完成了。下一篇文章会介绍一下集合的几种操作方法以及ES6原生set的一些简单用法介绍。

  

  最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!

]]>