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

Dynamics CRM 用openEntityForm打开新窗体传lookup字段时的注意事项

$
0
0

     在利用openEntityForm打开新的实体页面时,如果需要传值则在对应的参数位上,传个参数集object即可。

var redeem = {};
redeem["new_productid"] = Xrm.Page.data.entity.getId().replace('{', '').replace('}', '');
redeem["new_productidname"] = Xrm.Page.getAttribute("new_productname").getValue();

Xrm.Utility.openEntityForm("new_redeem", null, redeem);

    但参数集中含有lookup字段时,则赋值的方式和我们在form开发时给页面上的lookup字段的赋值是不一样的,这块要注意下,正确的赋值方式如上代码。

    下面这种赋值方式是错的

var redeem = {};
redeem["new_productid"] = [{
    id: Xrm.Page.data.entity.getId().replace('{', '').replace('}', ''),
    name: Xrm.Page.getAttribute("new_productname").getValue(), entityType: "new_product"
}];


Xrm.Utility.openEntityForm("new_redeem", null, redeem);

     而当我们遇到openEntityForm打开的新页面报错时,并没有具体的报错信息,只能一行行代码注释来查找问题。

作者:woniu1104913 发表于2017/11/15 15:00:54 原文链接
阅读:1 评论:0 查看评论

第17章 ioctl操作

$
0
0
#include <sys/ioctl.h>

int ioctl(int fd, unsigned long request, ...);


/usr/src/linux-headers-4.10.0-35/include/uapi/linux/if.h

/*
 * INET		An implementation of the TCP/IP protocol suite for the LINUX
 *		operating system.  INET is implemented using the  BSD Socket
 *		interface as the means of communication with the user level.
 *
 *		Global definitions for the INET interface module.
 *
 * Version:	@(#)if.h	1.0.2	04/18/93
 *
 * Authors:	Original taken from Berkeley UNIX 4.3, (c) UCB 1982-1988
 *		Ross Biro
 *		Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
 *
 *		This program is free software; you can redistribute it and/or
 *		modify it under the terms of the GNU General Public License
 *		as published by the Free Software Foundation; either version
 *		2 of the License, or (at your option) any later version.
 */
#ifndef _LINUX_IF_H
#define _LINUX_IF_H

#include <linux/libc-compat.h>          /* for compatibility with glibc */
#include <linux/types.h>		/* for "__kernel_caddr_t" et al	*/
#include <linux/socket.h>		/* for "struct sockaddr" et al	*/
#include <linux/compiler.h>		/* for "__user" et al           */

#if __UAPI_DEF_IF_IFNAMSIZ
#define	IFNAMSIZ	16
#endif /* __UAPI_DEF_IF_IFNAMSIZ */
#define	IFALIASZ	256
#include <linux/hdlc/ioctl.h>

/* For glibc compatibility. An empty enum does not compile. */
#if __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO != 0 || \
                                                            __UAPI_DEF_IF_NET_DEVICE_FLAGS != 0
/**
 * enum net_device_flags - &struct net_device flags
 *
 * These are the &struct net_device flags, they can be set by drivers, the
 * kernel and some can be triggered by userspace. Userspace can query and
 * set these flags using userspace utilities but there is also a sysfs
 * entry available for all dev flags which can be queried and set. These flags
 * are shared for all types of net_devices. The sysfs entries are available
 * via /sys/class/net/<dev>/flags. Flags which can be toggled through sysfs
 * are annotated below, note that only a few flags can be toggled and some
 * other flags are always preserved from the original net_device flags
 * even if you try to set them via sysfs. Flags which are always preserved
 * are kept under the flag grouping @IFF_VOLATILE. Flags which are volatile
 * are annotated below as such.
 *
 * You should have a pretty good reason to be extending these flags.
 *
 * @IFF_UP: interface is up. Can be toggled through sysfs.
 * @IFF_BROADCAST: broadcast address valid. Volatile.
 * @IFF_DEBUG: turn on debugging. Can be toggled through sysfs.
 * @IFF_LOOPBACK: is a loopback net. Volatile.
 * @IFF_POINTOPOINT: interface is has p-p link. Volatile.
 * @IFF_NOTRAILERS: avoid use of trailers. Can be toggled through sysfs.
 *	Volatile.
 * @IFF_RUNNING: interface RFC2863 OPER_UP. Volatile.
 * @IFF_NOARP: no ARP protocol. Can be toggled through sysfs. Volatile.
 * @IFF_PROMISC: receive all packets. Can be toggled through sysfs.
 * @IFF_ALLMULTI: receive all multicast packets. Can be toggled through
 *	sysfs.
 * @IFF_MASTER: master of a load balancer. Volatile.
 * @IFF_SLAVE: slave of a load balancer. Volatile.
 * @IFF_MULTICAST: Supports multicast. Can be toggled through sysfs.
 * @IFF_PORTSEL: can set media type. Can be toggled through sysfs.
 * @IFF_AUTOMEDIA: auto media select active. Can be toggled through sysfs.
 * @IFF_DYNAMIC: dialup device with changing addresses. Can be toggled
 *	through sysfs.
 * @IFF_LOWER_UP: driver signals L1 up. Volatile.
 * @IFF_DORMANT: driver signals dormant. Volatile.
 * @IFF_ECHO: echo sent packets. Volatile.
 */
enum net_device_flags {
    /* for compatibility with glibc net/if.h */
#if __UAPI_DEF_IF_NET_DEVICE_FLAGS
    IFF_UP				= 1<<0,  /* sysfs */
    IFF_BROADCAST			= 1<<1,  /* volatile */
    IFF_DEBUG			= 1<<2,  /* sysfs */
    IFF_LOOPBACK			= 1<<3,  /* volatile */
    IFF_POINTOPOINT			= 1<<4,  /* volatile */
    IFF_NOTRAILERS			= 1<<5,  /* sysfs */
    IFF_RUNNING			= 1<<6,  /* volatile */
    IFF_NOARP			= 1<<7,  /* sysfs */
    IFF_PROMISC			= 1<<8,  /* sysfs */
    IFF_ALLMULTI			= 1<<9,  /* sysfs */
    IFF_MASTER			= 1<<10, /* volatile */
    IFF_SLAVE			= 1<<11, /* volatile */
    IFF_MULTICAST			= 1<<12, /* sysfs */
    IFF_PORTSEL			= 1<<13, /* sysfs */
    IFF_AUTOMEDIA			= 1<<14, /* sysfs */
    IFF_DYNAMIC			= 1<<15, /* sysfs */
#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS */
#if __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO
    IFF_LOWER_UP			= 1<<16, /* volatile */
    IFF_DORMANT			= 1<<17, /* volatile */
    IFF_ECHO			= 1<<18, /* volatile */
#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO */
};
#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO != 0 || __UAPI_DEF_IF_NET_DEVICE_FLAGS != 0 */

/* for compatibility with glibc net/if.h */
#if __UAPI_DEF_IF_NET_DEVICE_FLAGS
#define IFF_UP				IFF_UP
#define IFF_BROADCAST			IFF_BROADCAST
#define IFF_DEBUG			IFF_DEBUG
#define IFF_LOOPBACK			IFF_LOOPBACK
#define IFF_POINTOPOINT			IFF_POINTOPOINT
#define IFF_NOTRAILERS			IFF_NOTRAILERS
#define IFF_RUNNING			IFF_RUNNING
#define IFF_NOARP			IFF_NOARP
#define IFF_PROMISC			IFF_PROMISC
#define IFF_ALLMULTI			IFF_ALLMULTI
#define IFF_MASTER			IFF_MASTER
#define IFF_SLAVE			IFF_SLAVE
#define IFF_MULTICAST			IFF_MULTICAST
#define IFF_PORTSEL			IFF_PORTSEL
#define IFF_AUTOMEDIA			IFF_AUTOMEDIA
#define IFF_DYNAMIC			IFF_DYNAMIC
#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS */

#if __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO
#define IFF_LOWER_UP			IFF_LOWER_UP
#define IFF_DORMANT			IFF_DORMANT
#define IFF_ECHO			IFF_ECHO
#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO */

#define IFF_VOLATILE	(IFF_LOOPBACK|IFF_POINTOPOINT|IFF_BROADCAST|IFF_ECHO|\
                         IFF_MASTER|IFF_SLAVE|IFF_RUNNING|IFF_LOWER_UP|IFF_DORMANT)

#define IF_GET_IFACE	0x0001		/* for querying only */
#define IF_GET_PROTO	0x0002

/* For definitions see hdlc.h */
#define IF_IFACE_V35	0x1000		/* V.35 serial interface	*/
#define IF_IFACE_V24	0x1001		/* V.24 serial interface	*/
#define IF_IFACE_X21	0x1002		/* X.21 serial interface	*/
#define IF_IFACE_T1	0x1003		/* T1 telco serial interface	*/
#define IF_IFACE_E1	0x1004		/* E1 telco serial interface	*/
#define IF_IFACE_SYNC_SERIAL 0x1005	/* can't be set by software	*/
#define IF_IFACE_X21D   0x1006          /* X.21 Dual Clocking (FarSite) */

/* For definitions see hdlc.h */
#define IF_PROTO_HDLC	0x2000		/* raw HDLC protocol		*/
#define IF_PROTO_PPP	0x2001		/* PPP protocol			*/
#define IF_PROTO_CISCO	0x2002		/* Cisco HDLC protocol		*/
#define IF_PROTO_FR	0x2003		/* Frame Relay protocol		*/
#define IF_PROTO_FR_ADD_PVC 0x2004	/*    Create FR PVC		*/
#define IF_PROTO_FR_DEL_PVC 0x2005	/*    Delete FR PVC		*/
#define IF_PROTO_X25	0x2006		/* X.25				*/
#define IF_PROTO_HDLC_ETH 0x2007	/* raw HDLC, Ethernet emulation	*/
#define IF_PROTO_FR_ADD_ETH_PVC 0x2008	/*  Create FR Ethernet-bridged PVC */
#define IF_PROTO_FR_DEL_ETH_PVC 0x2009	/*  Delete FR Ethernet-bridged PVC */
#define IF_PROTO_FR_PVC	0x200A		/* for reading PVC status	*/
#define IF_PROTO_FR_ETH_PVC 0x200B
#define IF_PROTO_RAW    0x200C          /* RAW Socket                   */

/* RFC 2863 operational status */
enum {
    IF_OPER_UNKNOWN,
    IF_OPER_NOTPRESENT,
    IF_OPER_DOWN,
    IF_OPER_LOWERLAYERDOWN,
    IF_OPER_TESTING,
    IF_OPER_DORMANT,
    IF_OPER_UP,
};

/* link modes */
enum {
    IF_LINK_MODE_DEFAULT,
    IF_LINK_MODE_DORMANT,	/* limit upward transition to dormant */
};

/*
 *	Device mapping structure. I'd just gone off and designed a 
 *	beautiful scheme using only loadable modules with arguments
 *	for driver options and along come the PCMCIA people 8)
 *
 *	Ah well. The get() side of this is good for WDSETUP, and it'll
 *	be handy for debugging things. The set side is fine for now and
 *	being very small might be worth keeping for clean configuration.
 */

/* for compatibility with glibc net/if.h */
#if __UAPI_DEF_IF_IFMAP
struct ifmap {
    unsigned long mem_start;
    unsigned long mem_end;
    unsigned short base_addr; 
    unsigned char irq;
    unsigned char dma;
    unsigned char port;
    /* 3 bytes spare */
};
#endif /* __UAPI_DEF_IF_IFMAP */

struct if_settings {
    unsigned int type;	/* Type of physical device or protocol */
    unsigned int size;	/* Size of the data allocated by the caller */
    union {
        /* {atm/eth/dsl}_settings anyone ? */
        raw_hdlc_proto		__user *raw_hdlc;
        cisco_proto		__user *cisco;
        fr_proto		__user *fr;
        fr_proto_pvc		__user *fr_pvc;
        fr_proto_pvc_info	__user *fr_pvc_info;

        /* interface settings */
        sync_serial_settings	__user *sync;
        te1_settings		__user *te1;
    } ifs_ifsu;
};

/*
 * Interface request structure used for socket
 * ioctl's.  All interface ioctl's must have parameter
 * definitions which begin with ifr_name.  The
 * remainder may be interface specific.
 */

/* for compatibility with glibc net/if.h */
#if __UAPI_DEF_IF_IFREQ
struct ifreq {
#define IFHWADDRLEN	6
    union
    {
        char	ifrn_name[IFNAMSIZ];		/* if name, e.g. "en0" */
    } ifr_ifrn;

    union {
        struct	sockaddr ifru_addr;
        struct	sockaddr ifru_dstaddr;
        struct	sockaddr ifru_broadaddr;
        struct	sockaddr ifru_netmask;
        struct  sockaddr ifru_hwaddr;
        short	ifru_flags;
        int	ifru_ivalue;
        int	ifru_mtu;
        struct  ifmap ifru_map;
        char	ifru_slave[IFNAMSIZ];	/* Just fits the size */
        char	ifru_newname[IFNAMSIZ];
        void __user *	ifru_data;
        struct	if_settings ifru_settings;
    } ifr_ifru;
};
#endif /* __UAPI_DEF_IF_IFREQ */

#define ifr_name	ifr_ifrn.ifrn_name	/* interface name 	*/
#define ifr_hwaddr	ifr_ifru.ifru_hwaddr	/* MAC address 		*/
#define	ifr_addr	ifr_ifru.ifru_addr	/* address		*/
#define	ifr_dstaddr	ifr_ifru.ifru_dstaddr	/* other end of p-p lnk	*/
#define	ifr_broadaddr	ifr_ifru.ifru_broadaddr	/* broadcast address	*/
#define	ifr_netmask	ifr_ifru.ifru_netmask	/* interface net mask	*/
#define	ifr_flags	ifr_ifru.ifru_flags	/* flags		*/
#define	ifr_metric	ifr_ifru.ifru_ivalue	/* metric		*/
#define	ifr_mtu		ifr_ifru.ifru_mtu	/* mtu			*/
#define ifr_map		ifr_ifru.ifru_map	/* device map		*/
#define ifr_slave	ifr_ifru.ifru_slave	/* slave device		*/
#define	ifr_data	ifr_ifru.ifru_data	/* for use by interface	*/
#define ifr_ifindex	ifr_ifru.ifru_ivalue	/* interface index	*/
#define ifr_bandwidth	ifr_ifru.ifru_ivalue    /* link bandwidth	*/
#define ifr_qlen	ifr_ifru.ifru_ivalue	/* Queue length 	*/
#define ifr_newname	ifr_ifru.ifru_newname	/* New name		*/
#define ifr_settings	ifr_ifru.ifru_settings	/* Device/proto settings*/

/*
 * Structure used in SIOCGIFCONF request.
 * Used to retrieve interface configuration
 * for machine (useful for programs which
 * must know all networks accessible).
 */

/* for compatibility with glibc net/if.h */
#if __UAPI_DEF_IF_IFCONF
struct ifconf  {
    int	ifc_len;			/* size of buffer	*/
    union {
        char __user *ifcu_buf;
        struct ifreq __user *ifcu_req;
    } ifc_ifcu;
};
#endif /* __UAPI_DEF_IF_IFCONF */

#define	ifc_buf	ifc_ifcu.ifcu_buf		/* buffer address	*/
#define	ifc_req	ifc_ifcu.ifcu_req		/* array of structures	*/

#endif /* _LINUX_IF_H */


作者:gongluck93 发表于2017/11/15 15:33:58 原文链接
阅读:9 评论:0 查看评论

Unity Shader 学习笔记(9) 渐变纹理、遮罩纹理

$
0
0

Unity Shader 学习笔记(9) 渐变纹理、遮罩纹理

参考书籍:《Unity Shader 入门精要》


渐变纹理

没有使用纹理,和使用两种不同渐变纹理对比:

就是通过一张梯度变换的纹理,取样赋值。可实现一些卡通风格。
使用半兰伯特模型:

v2f vert(a2v v) {
    ...
    o.uv = TRANSFORM_TEX(v.texcoord,_RampTex);  // _RampTex为外部输入的渐变纹理,宏为计算缩放、偏移后uv值。
    return o;
}

fixed4 frag(v2f i) : SV_TARGET {
    ...

    // 半兰伯特模型
    fixed halfLambert = 0.5 * dot(worldNormal,worldLightDir) + 0.5;
    // 通过halfLambert值大小对渐变纹理取样。因为变换范围在[0,1],所以可以直接作为纹理坐标值,值越大坐标越往右上。
    fixed3 diffuseColor = tex2D(_RampTex,fixed2(halfLambert,halfLambert)).rgb * _Color.rgb;

    fixed3 diffuse = _LightColor0.rbg * diffuseColor;

    ...
}

遮罩纹理

单张纹理,单张+法线纹理,单张+法线+遮罩纹理对比:

类似PhotoShop的蒙板,用于控制部分区域的纹理,值越大越白越用于受影响。配合法线纹理使用。这里控制的是高光反射的值。

切线空间下计算:

Properties {
    ...
    _SpecularMask ("Specular Mask",2D) = "white" {}     // 高光反射遮罩
    _SpecularScale ("Specular Scale",Float) = 1.0       // 遮罩影响度系数
}
fixed4 frag(v2f i) : SV_TARGET {
    // 计算环境光、切线空间下的法线值、漫反射等等
    ...

    // 获取该顶点遮罩值
    fixed specularMask = tex2D(_SpecularMask,i.uv).r * _SpecularScale;  

    // 高光反射最后乘多一个遮罩值,控制光照细节,值越小越黑越不受影响
    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(tangentNormal,halfDir)),_Gloss) * specularMask;  

    return fixed4(ambient + diffuse + specular,1.0);
}
作者:l773575310 发表于2017/11/15 16:28:40 原文链接
阅读:72 评论:0 查看评论

错误处理(4)—— “{000208D5-0000-0000-C000-000000000046}”的接口的 COM 组件调用 QueryInterface 因以下错误而失败

$
0
0

错误信息:

中文版——
   无法将类型为“Microsoft.Office.Interop.Excel.ApplicationClass”的 
COM 对象强制转换为接口类型“Microsoft.Office.Interop.Excel._Application”。此操作失败的原因是对 IID 
为“{000208D5-0000-0000-C000-000000000046}”的接口的 COM 组件调用 QueryInterface 因以下错误而失败: 
库没有注册。 (异常来自 HRESULT:0x8002801D 
(TYPE_E_LIBNOTREGISTERED))。

英文版——
    Unable to 
cast COM object of type 'Microsoft.Office.Interop.Excel.ApplicationClass' to 
interface type 'Microsoft.Office.Interop.Excel._Application'. This operation 
failed because the QueryInterface call on the COM component for the interface 
with IID '{000208D5-0000-0000-C000-000000000046}' failed due to the following 
error:  could not be found. (Exception from HRESULT: 0x80030002 
(STG_E_FILENOTFOUND)).

错误原因:

        Office的版本和SharePoint的版本不一致。


解决方案:


步骤一:

        对于Excel,删除注册表项:
HKEY_CLASSES_ROOT\TypeLib\{00020813-0000-0000-C000-000000000046}\1.7
1.7对应的版本应该是office2010以上,如果你用的office版本低于2010,在程序中调用Excel时就可能会出现该类异常,正常情况下删除该项即可。


Word对应的注册表项为:
HKEY_CLASSES_ROOT\TypeLib\{00020905-0000-0000-C000-000000000046}\1.7


如果进行完第一步该错误仍出现,则需要进行步骤二


步骤二:
        删除注册表项(Excel)HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{00020812-0000-0000-C000-000000000046}\InprocServer32\14.0.0.0


Word对应为:
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{ {00020906-0000-0000-C000-000000000046}}\InprocServer32\14.0.0.0


        如果进行完上述两部出现异常,删除完之后重新安装office。

作者:lzhui1987 发表于2017/11/15 16:40:54 原文链接
阅读:89 评论:0 查看评论

Go语言如何实现遗传算法

$
0
0
原文:Go with Genetic Algorithms 
作者:5280incode 
翻译:Diwei

出于好玩的心态,我决定学习一下Go语言。我认为学习新语言最好的方法就是深入学习,并且尽可能多犯错误。这样做虽然可能会很慢,但是可以确保在后面的过程中再也不会出现编译的错误。

Go语言与我习惯的其他语言不同。Go更喜欢自己单独实现,而其他像Java这类语言更喜欢继承。其实在Go语言里面根本没有继承这种概念,因为它压根就没有对象这一说法。比如说C语言,它有结构体,但是没有类。但是这样它还是可以有像“构造者”这样的常见思想和设计模式(一种在这种情况下有序地产生结构体的方式)。

Go语言坚决拥护组合(composition),同时也很反对继承的做法,在网络上引起了强烈的讨论,同时也让人们重新思考了语言该往哪个方向发展。所以,从这个角度来看,Go语言与其它语言的差别可能也没有那么大。

本文将重点介绍如何用Go语言实现遗传算法。如果你还没有参加过GoLang Tour,我还建议你快速看一下这门语言的介绍。

话不多说,让我们开始从代码说起吧!第一个例子与我以前做过的很类似:找到一个二次的最小值。

type GeneticAlgorithmSettings struct {
  PopulationSize int
  MutationRate int
  CrossoverRate int
  NumGenerations int
  KeepBestAcrossPopulation bool
}

type GeneticAlgorithmRunner interface {
  GenerateInitialPopulation(populationSize int) []interface{}
  PerformCrossover(individual1, individual2 interface{}, mutationRate int) interface{}
  PerformMutation(individual interface{}) interface{}
  Sort([]interface{})
}

我立马定义了一组设置,以便在稍后启动的算法中用到。

第二部分的GeneticAlgorithmRunner这个看起来有点奇怪。GeneticAlgorithmRunner是一个接口,询问如何生成初始种群,执行corssovers和mutataions,并对答案进行排序,以便在Population中保持最好的个体,这样下一代才会更加优秀。我认为这看起来很奇怪,因为“接口”通常用于面向对象的语言,通常会要求对象实现某些特性和方法。这里没有什么差别。这一小段代码实际上是在说,它正在请求一些东西来定义这些方法的细节。我是这样做的:

type QuadraticGA struct {}

func (l QuadraticGA) GenerateInitialPopulation(populationSize int) []interface{}{
  initialPopulation := make([]interface{}, 0, populationSize)
  for i:= 0; i < populationSize; i++ {
    initialPopulation = append(initialPopulation, makeNewEntry())
  }
  return initialPopulation
}

func (l QuadraticGA) PerformCrossover(result1, result2 interface{}, _ int) interface{}{
  return (result1.(float64) + result2.(float64)) / 2
}

func (l QuadraticGA) PerformMutation(_ interface{}, _ int) interface{}{
  return makeNewEntry()
}

func (l QuadraticGA) Sort(population []interface{}){
  sort.Slice(population, func(i, j int) bool {
    return calculate(population[i].(float64)) > calculate(population[j].(float64))
  })
}

更奇怪的是,我从来没有提到过这些方法的接口。请记住,因为没有对象,也没有继承。QuadraticGA结构体是一个空白对象,隐式地作为GeneticAlgorithmRunner。每个必需的方法都在括号中绑定到该结构体,就像Java中的“@ override”。现在,结构体和设置需要传递给运行该算法的模块。

settings := ga.GeneticAlgorithmSettings{
   PopulationSize: 5,
   MutationRate: 10,
   CrossoverRate: 100,
   NumGenerations: 20,
   KeepBestAcrossPopulation: true,
}

best, err := ga.Run(QuadraticGA{}, settings)

if err != nil {
   println(err)
}else{
   fmt.Printf("Best: x: %f  y: %f\n", best, calculate(best.(float64)))
}

很简单,对吧?“QuadraticGA {}”只是简单地创建了该结构的一个新实例,其余的则由Run()方法完成。该方法返回搜索结果和发生的任何错误,因为Go不相信try / catch——另一场战争作者采取了严格的设计立场。

现在来计算每个项的性能,以求二次函数求出的二次函数来求出一个新的X值的方法:

func makeNewEntry() float64 {
   return highRange * rand.Float64()
}

func calculate(x float64) float64 {
   return  math.Pow(x, 2) - 6*x + 2 // minimum should be at x=3
}

既然已经为二次实现创建了接口,那么GA本身需要完成:

func Run(geneticAlgoRunner GeneticAlgorithmRunner, settings GeneticAlgorithmSettings) (interface{}, error){

   population := geneticAlgoRunner.GenerateInitialPopulation(settings.PopulationSize)

   geneticAlgoRunner.Sort(population)

   bestSoFar := population[len(population) - 1]

   for i:= 0; i < settings.NumGenerations; i++ {

      newPopulation := make([]interface{}, 0, settings.PopulationSize)

      if settings.KeepBestAcrossPopulation {
         newPopulation = append(newPopulation, bestSoFar)
      }

      // perform crossovers with random selection
      probabilisticListOfPerformers := createStochasticProbableListOfIndividuals(population)

      newPopIndex := 0
      if settings.KeepBestAcrossPopulation{
         newPopIndex = 1
      }
      for ; newPopIndex < settings.PopulationSize; newPopIndex++ {
         indexSelection1 := rand.Int() % len(probabilisticListOfPerformers)
         indexSelection2 := rand.Int() % len(probabilisticListOfPerformers)

         // crossover
         newIndividual := geneticAlgoRunner.PerformCrossover(
            probabilisticListOfPerformers[indexSelection1],
            probabilisticListOfPerformers[indexSelection2], settings.CrossoverRate)

         // mutate
         if rand.Intn(101) < settings.MutationRate {
            newIndividual = geneticAlgoRunner.PerformMutation(newIndividual)
         }

         newPopulation = append(newPopulation, newIndividual)
      }

      population = newPopulation

      // sort by performance
      geneticAlgoRunner.Sort(population)

      // keep the best so far
      bestSoFar = population[len(population) - 1]

   }

   return bestSoFar, nil
}

func createStochasticProbableListOfIndividuals(population []interface{}) []interface{} {

   totalCount, populationLength:= 0, len(population)
   for j:= 0; j < populationLength; j++ {
      totalCount += j
   }

   probableIndividuals := make([]interface{}, 0, totalCount)
   for index, individual := range population {
      for i:= 0; i < index; i++{
         probableIndividuals = append(probableIndividuals, individual)
      }
   }

   return probableIndividuals
}

很像以前,一个新的人口被创造出来,人口的成员将会世代交配,而他们的后代可能携带突变。一个人的表现越好,就越有可能交配。随着时间的推移,算法收敛到最好的答案,或者至少是一个相当不错的答案。

那么当它运行时,它返回了什么呢?

Best: x: 3.072833 y: -6.994695

不坏!由于人口规模只有5、20代,而且输入的范围被限制在[0 100],这一搜索就钉在了顶点上。

现在,您可能想知道为什么我定义了所有的接口方法来返回“接口{}”。这就像Go和generics一样。没有对象,因此没有对象类型返回,但是没有描述的大小的数据仍然可以在堆栈上传递。这本质上也是这个返回类型的含义:它传递一些已知的和类似的类型的对象。有了这个“泛型”,我就可以将GA移动到它自己的包中,并将相同的代码移到多个不同类型的数据上。

我们有两个输入的3D二次方程,而不是一个二维二次方程的单个输入。接口方法只需要很小的改变:

type Quad3D struct {
   x, y float64
}
func makeNewQuadEntry(newX, newY float64) Quad3D {
   return Quad3D{
      x: newX,
      y: newY,
   }
}

func calculate3D(entry Quad3D) float64 {
   return math.Pow(entry.x, 2)- 6 * entry.x + math.Pow(entry.y, 2)- 6 * entry.y + 2
}

type Quadratic3dGA struct {
}

func (l Quadratic3dGA) GenerateInitialPopulation(populationSize int)[]interface{}{

   initialPopulation := make([]interface{}, 0, populationSize)
   for i:= 0; i < populationSize; i++ { initialPopulation = append(initialPopulation, makeNewQuadEntry(makeNewEntry(), makeNewEntry())) } return initialPopulation } func (l Quadratic3dGA) PerformCrossover(result1, result2 interface{}, mutationRate int) interface{}{ r1Entry, r2Entry := result1.(Quad3D), result2.(Quad3D) return makeNewQuadEntry((r1Entry.x + r2Entry.x) / 2, (r1Entry.y + r2Entry.y) / 2,) } func (l Quadratic3dGA) PerformMutation(_ interface{}) interface{}{ return makeNewQuadEntry(makeNewEntry(), makeNewEntry()) } func (l Quadratic3dGA) Sort(population []interface{}){ sort.Slice(population, func(i, j int) bool { return calculate3D(population[i].(Quad3D)) > calculate3D(population[j].(Quad3D))
   })
}

func quadratic3dMain(){
   settings := ga.GeneticAlgorithmSettings{
      PopulationSize: 25,
      MutationRate: 10,
      CrossoverRate: 100,
      NumGenerations: 20,
      KeepBestAcrossPopulation: true,
   }

   best, err := ga.Run(Quadratic3dGA{}, settings)
   entry := best.(Quad3D)

   if err != nil {
      println(err)
   }else{
      fmt.Printf("Best: x: %f  y: %f  z: %f\n", entry.x, entry.y, calculate3D(entry))
   }
}

而不是到处都是float64s,任何地方都可以通过Quad3D的条目;每一个都有一个X和一个Y值。对于创建的每个条目,都使用contructor makeNewQuadEntry创建。Run()方法中的代码都没有更改。

当它运行时,我们得到这个输出:

Best: x: 3.891671 y: 4.554884 z: -12.787259

很接近了!

哦,我忘了说走快了!在Java中执行此操作时,即使使用相同的设置,也会有明显的等待时间。在一个相对较小的范围内求解二次方程并不是很复杂,但它对一个人来说是值得注意的。

Go是本地编译的,比如C。当二进制执行时,它似乎马上就吐出一个答案。这里有一个简单的方法来度量每次运行的执行时间:

func main() {
   beforeQuadTime := time.Now()
   quadraticMain()
   afterQuadTime := time.Since(beforeQuadTime)
   fmt.Printf("%d\n", afterQuadTime)

   before3dQuadTime := time.Now()
   quadratic3dMain()
   after3dQuatTime := time.Since(before3dQuadTime)
   fmt.Printf("%d\n", after3dQuatTime)
}

边注:我能说我很高兴我们是一个开发者社区,让他们从过去的错误中走出来,并把综合的时间模块和包构建成一种语言吗?Java 8 +拥有它们,Python拥有它们,并拥有它们。这使我开心。

现在的输出:

Best: x: 3.072833 y: -6.994695
136,876
Best: x: 3.891671 y: 4.554884 z: -12.787259
4,142,778

那“近乎瞬间”的感觉是我想要传达的,现在我们有了很难的数字。136,876看起来很大,但要在纳秒内报告时间。

重申一遍:纳秒。不是几毫秒,我们都习惯了在互联网时代或者其他像Python和Java这样的通用语言;纳秒。1/1,000,000毫秒。

这意味着我们在不到一毫秒的时间里找到了一个使用遗传算法来搜索答案的二次方程的答案。这句话,“该死的瞬间”似乎很合适,不是吗?这包括打印到终端。

那么,要计算更密集的东西呢?在我展示一种寻找好的梦幻足球lineups的方法之前,我在Fanduel上使用。这包括从电子表格中读取数据,制作和过滤lineups,并进行更复杂的交叉和突变。强制寻找最佳解决方案可能需要超过75,000年(至少使用我当时使用的Python)。

我不需要再检查所有的细节,你可以自己去看代码,但我会在这里显示输出:

Best: 121.409960:, $58100
QB: Aaron Rodgers - 23.777778
RB: Latavius Murray - 15.228571
RB: DeMarco Murray - 19.980000
WR: Kelvin Benjamin - 11.800000
WR: Stefon Diggs - 14.312500
WR: Alshon Jeffery - 9.888889
TE: Connor Hamlett - 8.200000
D: Philadelphia Eagles - 10.777778
K: Phil Dawson - 7.444444
16,010,182

哦,是的!现在看来这是一个很好的阵容!它只需要16毫秒就能找到。

现在,这个遗传算法可以改进了。与C一样,当将对象传递给方法时,将在堆栈上复制对象(读取数据)。随着对象大小的增长,最好不要反复复制它们,而是要在堆中创建它们,并在周围传递指针。目前,我将把它作为未来的工作。

Go也被用coroutines和信道的原生支持编写,利用多个内核来解决一个问题,比过去简单多了,相比于单核时代的其他语言来说,这是一个巨大的优势。我想要增强这个算法来使用这些工具,但这也必须留给以后的工作。

我很享受学习的过程。对于我来说,用组合而不是继承来考虑工程解决方案是很困难的,因为我已经习惯了8年以上的时间,也是我学会编程的方式。但是每种语言和方式都有各自的优点和缺点;每一种语言在我的工具中都是不同的工具。对于任何担心尝试的人,不要。有一个驼峰(更像是一个减速带),但你很快就会克服它,走上成功之路。

还有一些我喜欢的东西,我喜欢其他语言,主要是一组基本的函数方法来操作数据。我需要一个lambda函数和方法来映射、减少和筛选数据的数组或部分。设计人员反对功能实现的理由是,代码应该总是简单、易于阅读和编写,并且这与for循环是可实现的。我认为,映射、过滤和减少通常更容易读和写,但这是一场已经在肆虐的战争中的争论。

尽管我与一些开发人员的观点存在分歧,以及我必须考虑解决问题的不同方式,但Go真的是一种很好的语言。我鼓励大家在学习一两门语言后再试一试。它很快就成为了最流行的语言之一,有很多原因可以解释为什么。我期待着在未来更多地使用它。

作者:js_gary 发表于2017/11/15 17:19:06 原文链接
阅读:75 评论:0 查看评论

spring boot整合quartz实现多个定时任务

$
0
0

最近收到了很多封邮件,都是想知道spring boot整合quartz如何实现多个定时任务的,由于本人生产上并没有使用到多个定时任务,这里给个实现的思路。

1、新建两个定时任务,如下:

public class ScheduledJob implements Job{ 
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException { 
        System.out.println("schedule job1 is running…………………………………… "); 
    }
}

public class ScheduledJob2 implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException { 
        System.out.println("schedule job2 is running ……………………………………………………"); 
    }
}

2、配置以上两个任务

@Component
public class SchedulerAllJob {
    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;
    
    /*
     * 此处可以注入数据库操作,查询出所有的任务配置
     */
    
    /**
     * 该方法用来启动所有的定时任务
     * @throws SchedulerException
     */
    public void scheduleJobs() throws SchedulerException {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        
        /**
         *  
         */
        scheduleJob1(scheduler); 
        scheduleJob2(scheduler); 
    }
    
    /**
     * 配置Job1
     * 此处的任务可以配置可以放到properties或者是放到数据库中
     * 如果此时需要做到动态的定时任务,请参考:http://blog.csdn.net/liuchuanhong1/article/details/60873295
     *  博客中的ScheduleRefreshDatabase类
     * @param scheduler
     * @throws SchedulerException
     */
    private void scheduleJob1(Scheduler scheduler) throws SchedulerException{
    	/*
    	 *  此处可以先通过任务名查询数据库,如果数据库中存在该任务,则按照ScheduleRefreshDatabase类中的方法,更新任务的配置以及触发器
    	 *  如果此时数据库中没有查询到该任务,则按照下面的步骤新建一个任务,并配置初始化的参数,并将配置存到数据库中
    	 */
        JobDetail jobDetail = JobBuilder.newJob(ScheduledJob.class) .withIdentity("job1", "group1").build(); 
        // 每5s执行一次
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?"); 
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1") .withSchedule(scheduleBuilder).build(); 
        scheduler.scheduleJob(jobDetail,cronTrigger); 
    }
    
    /**
     * 配置Job
     * @param scheduler
     * @throws SchedulerException
     */
    private void scheduleJob2(Scheduler scheduler) throws SchedulerException{ 
        JobDetail jobDetail = JobBuilder.newJob(ScheduledJob2.class) .withIdentity("job2", "group1").build();
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ?"); 
        // 每10s执行一次
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("trigger2", "group1") .withSchedule(scheduleBuilder).build(); 
        scheduler.scheduleJob(jobDetail,cronTrigger);
    }
}

3、启动两个任务

@Configuration
@EnableScheduling
@Component
public class SchedulerListener {
	
    @Autowired
    public SchedulerAllJob myScheduler;
    
    /**
     *  启动的时候执行该方法,或者是使用ApplicationListener,在启动的时候执行该方法
     *  具体使用见:http://blog.csdn.net/liuchuanhong1/article/details/77568187
     * @throws SchedulerException
     */
    @Scheduled(cron="0 08 18 ? * *")
    public void schedule() throws SchedulerException { 
            myScheduler.scheduleJobs();
     } 
    
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(){
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); 
        return schedulerFactoryBean; 
    }
}

4、测试结果如下

schedule job1 is running…………………………………… 
schedule job2 is running ……………………………………………………
schedule job1 is running…………………………………… 
schedule job1 is running…………………………………… 
schedule job2 is running ……………………………………………………
schedule job1 is running…………………………………… 
schedule job1 is running…………………………………… 
schedule job2 is running ……………………………………………………
schedule job1 is running…………………………………… 


作者:liuchuanhong1 发表于2017/11/15 18:22:24 原文链接
阅读:29 评论:0 查看评论

Java 位运算符和移位运算符

$
0
0

参考:

Bitwise and Bit Shift Operators

《Java 编程思想 第3章 操作符》


今天学习 Java BitSet 类时,发现对于位运算符和移位运算符的操作有些陌生,所以重新复习一下


主要内容:

  1. 位操作浅析
  2. 位运算符
  3. 移位运算符
  4. 优先级
  5. 问题解析
  6. 取值范围

位操作浅析

Java 可在整数类型(integral type)数据上进行位(bit)操作

整数类型:

  • 字节型(byte8 位)
  • 短整型(short16 位)
  • 整型(int32 位)
  • 长整型(long64 位)

原码,反码和补码

参考:

原码,反码和补码的关系?

原码、反码、补码的产生、应用以及优缺点有哪些?

首先原码,反码和补码都是基于二进制数进行的

对于正数而言,其原码,反码和补码一致(无符号数就是正数

Java 中的整数类型都是有符号数,即其最高位为符号位(正数为 0,负数为 1

默认情况下,二进制数就是原码表示,所以将十进制数 15 转换为二进制原码就是(假定为 8 位整数)

00001111

将十进制数 -15 转换为二进制原码就是

10001111

原码和反码相互转换规则:负数保留符号位不变,其它位按位取反

将十进制 -15 转换为二进制反码就是

11110000

原码和补码相互转换规则:负数符号位不变,其余位求反再加 1

将十进制 -15 转换为二进制补码就是

11110001

加减运算

在计算机中,使用补码保存整数类型数据因为补码格式有利于计算机进行移位运算

加减运算时,补码直接相加即可(符号位参与运算

进制转换

Java 不能直接表示二进制整数,但可以表示成八进制(以数字 0 开头),十进制(没有前置)和十六进制(以数字 0 和 字符 x 开头),默认情况使用十进制计算

比如对于整数 15 来说,其八进制表示为 017,十六进制表示为 0xf

通常情况下使用 十六进制 来表示 二进制

一个整型数据占 4 个字节,共 32 位,那么对于整型数 a = 15 来说,其二进制表示如下

// 前面共 28 个 0
0000...0001111

转换为十六进制,就是 0x0000000f


位运算符

Java 提供了 4 种位运算符

  • 位与运算符(bitwise and operator):&
  • 位或运算符(bitwise inclusive or operator):|
  • 位异或运算符(bitwise exclusive or operator):^
  • 位取反运算符(bitwise invert operator):~

这些运算符是在二进制补码上进行操作

测试程序如下:

public static void main(String[] args) {
    byte a = 15;
    byte b = -15;

    System.out.println(a & b);
    System.out.println(a | b);
    System.out.println(a ^ b);
    System.out.println(~a);
    System.out.println(~b);
}

这里写图片描述

一个字节数占 8 位,将 ab 转换为二进制:

a = 0000 1111
b = 1111 0001

Note:计算机使用补码表示

位与运算符:仅当两个操作数同一下标的值均为 1 时,结果才为 1

a & b = 0000 1111 & 1111 0001 = 0000 0001(补) = 0000 0001(原) = 1

位或运算符:只要两个操作数同一下标的值有一个为 1 时,结果就为 1

a | b = 0000 1111 & 1111 0001 = 1111 1111(补) = 1000 0001(原) = -1

位异或运算符:只有两个操作数同意下标的值不相等时,结果才为 1

a ^ b = 0000 1111 ^ 1111 0001 = 1111 1110(补) = 1000 0010(原) = -2

位取反运算符:按位取反每一位

~a = ~0000 1111 = 1111 0000(补) = 1001 0000(原) = -16
~b = ~1111 0001 = 0000 1110(补) = 0000 1110(原) = 14

Note 1:byte 或者 short 类型数值进行位运算后,返回的是 int 类型数值(没有找到资料说明在位运算之前是否已经进行了转换,不过先将 ab 转换为 int 类型二进制再进行计算的结果和上面一致)

Note 2:位运算符的操作不排除符号位


移位运算符

Java 提供了 3 种移位运算符

  • 左移运算符(left shift operator):<<
  • 右移运算符(right shift operator):>>
  • 无符号右移运算符(unsigned right shift operator):>>>

示例程序如下:

public static void main(String[] args) {
    System.out.println("正数移位");
    compute(15);
    System.out.println("负数移位");
    compute(-15);
}

public static void compute(int a) {
    println(a << 3);
    println(a << -61);
    println(a << 35);

    println(a >> 3);
    println(a >> -61);
    println(a >> 35);

    println(a >>> 3);
    println(a >>> -61);
    println(a >>> 35);
}

public static void println(int n) {
    System.out.println(n);
}

对于移位运算符而言,左侧操作数表示要移动的二进制数,右侧操作数表示要移动的位数

进行移位操作时,需要注意以下几点:

  • 对于 byte 或者 short 类型数值,进行移位操作时,会先转换为 int 类型,然后进行移位(如果是 long 类型,则不变

  • 对于右侧操作数而言,在进行移位之前,先转换为二进制数(补码)。如果左侧数是 int 类型,则取右侧操作数最右端 5 位数值进行移动;如果是 long 类型数值,则取右侧操作数最右端 6 位数值进行移动

左移运算符:数值位向左移动指定位数

15 << 3 = 0x0000000f << 3 = 0x00000078(补,原) = 120
15 << -61 = 0x0000000f << 0xffffffc3(左侧是 int 类型,取右侧 5 位) = 0x0000000f << 3 = 0x00000078(补,原) = 120
15 << 35 = 0x0000000f << 0x00000023(左侧是 int 类型,取右侧 5 位) = 0x0000000f << 3 = 0x00000078(补,原) = 120

-15 << 3 = 0xfffffff1 << 3 = 0xffffff88(补) = 0x80000078(原) = -120
-15 << -61 = 0xfffffff1 << 0xffffffc3(左侧是 int 类型,取右侧 5 位) = 0xfffffff1 << 3 = 0xffffff88(补) = 0x80000078(原) = -120
-15 << 35 = 0xfffffff1 << 0x00000023(左侧是 int 类型,取右侧 5 位) = 0xfffffff1 << 3 = 0xffffff88(补) = 0x80000078(原) = -120

右移运算符:数字位向右移动指定位数(如果左操作数是正数,高位补 0 ;如果是负数,高位补 1

15 >> 3 = 0x0000000f >> 3 = 0x00000001 = 1
-15 >> 3 = 0xfffffff1 >> 3 = 0xfffffffe(补) = 0x80000002(原) = -2

无符号右移运算符:功能和右移运算符一样,不过无论正负,高位均补 0

15 >>> 3 = 0x0000000f >>> 3 = 0x00000001 = 1
-15 >> 3 = 0xfffffff1 >>> 3 = 0x1ffffffe(补,原) = 2^29 - 2 = 536870910

Note 1:移位运算时,从符号位开始操作

Note 2:由结果可知,左移一位相当于乘以2,右移一位相当于除以 2


优先级

参考:运算符优先级

Java 运算符优先级如下图所示:

这里写图片描述

由图中可知,位运算符和移位运算符的优先级从左到右如下:

~,<<,>>,>>>,&,^,|


问题解析

之前学习类 BitSet 时遇到了很多的位运算,但是有一些操作没搞明白,下面是我总结的一些问题和解答

  • 问题一:移动位数超过其精度如何解决

    • 解答:在进行移位操作之前,先将右侧数值转换成二进制(其实在计算机内部就是以二进制补码保存的)。如果左侧操作数为 int 类型数值,那么取右侧操作数的最右端 5 位进行移位;或者左侧操作数是 long 类型数值,取右侧操作数的最右端 6 位进行移位
  • 问题二:移动位数为负如何解决

    • 解答:和问题一的解答一样,取右侧操作数的最右端 5/6 位进行移位
  • 问题三:如何解决符号位的问题

    • 解答:计算机以二进制补码形式保存整型数值。
      • 无论是加减 / 位运算 / 移位运算,符号位均参与其中
  • 问题四:已知起始下标 fromIndex 和 终止下标 toIndex,如何在位集中设定这一段连续区间为 true

    • 解答:以 int 类型为例,位集长度为 32 位,假设 fromIndex = 3toIndex = 10,那么示例程序如下:

      public static final int WORD_MASK = 0xffffffff;
      
      public static void main(String[] args) {
          int fromIndex = 3;
          int toIndex = 10;
      
          int firstWordMask = WORD_MASK << fromIndex;
          int lastWordMask = WORD_MASK >>> -toIndex;
          int res = (firstWordMask & lastWordMask);
          System.out.println(Integer.toBinaryString(firstWordMask));
          System.out.println(Integer.toBinaryString(lastWordMask));
          System.out.println(Integer.toBinaryString(res));
      }
      

      这里写图片描述

      要设定位集中连续区间位值为 true,可以设定一个辅助常量 WORD_MASK,保证每个位均为 true

      定义起始下标 fromIndex = 3,结束下标 toIndex = 10

      计算 firstWordMask,使得 WORD_MASK 向左移动 fromIndex 个位置,低位补 0,结果使得区间 [0-fromIndex) 的位值为 0

      计算 lastWordMask,使得 WORD_MASK 向右移动 n 个位置,高位补 0,结果使得区间 (toIndex-32] 的位值为 0

      最后进行位与操作,得到区间 [fromIndex-toIndex] 的位值为 true

      Note:对于 int 值而言,设 a = 3,则取 -3 的后 5 位就是 (32-3)=29;若是 long 值,取 -3 的后 6 位就是 (64-3)=61


取值范围

字节类型占 8 位,其中最高位为符号位,所以其取值范围为 [-2^7-1,2^7-1] = [-127,127],其中 0 有两种表示方式

00000000 或者 10000000

10000000 当作 -128,则 字节类型的取值为 [-128,127]

public static final byte   MIN_VALUE = -128;
public static final byte   MAX_VALUE = 127;

示例程序如下:

public static void main(String[] args) {
    System.out.println(Integer.toBinaryString(Byte.toUnsignedInt(Byte.MAX_VALUE)));
    System.out.println(Integer.toBinaryString(Byte.toUnsignedInt(Byte.MIN_VALUE)));
}

这里写图片描述

同理,短整型的取值范围为 [-2^15,2^15-1],整型的取值范围为 [-2^31,2^31-1],长整型的取值范围为 [-2^63,2^63-1]

// Short.java
public static final short   MIN_VALUE = -32768;
public static final short   MAX_VALUE = 32767;

// Integer.java
@Native public static final int   MIN_VALUE = 0x80000000;
@Native public static final int   MAX_VALUE = 0x7fffffff;

// Long.java
@Native public static final long MIN_VALUE = 0x8000000000000000L;
@Native public static final long MAX_VALUE = 0x7fffffffffffffffL;

示例程序如下:

public static void main(String[] args) {
    System.out.println(Integer.toBinaryString(Short.toUnsignedInt(Short.MAX_VALUE)));
    System.out.println(Integer.toBinaryString(Short.toUnsignedInt(Short.MIN_VALUE)));

    System.out.println(Integer.toBinaryString(Integer.MAX_VALUE));
    System.out.println(Integer.toBinaryString(Integer.MIN_VALUE));

    System.out.println(Long.toBinaryString(Long.MAX_VALUE));
    System.out.println(Long.toBinaryString(Long.MIN_VALUE));
}

这里写图片描述

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

react native学习笔记17——存储篇(2)SQLite

$
0
0

前言

对于存放数据量小且简易的数据我们可以通过AsyncStorage来存储,但对于数据结构复杂、数据量大的数据,我们可以使用移动开发中常用的SQLite来处理。
SQLite是一种轻型的数据库,多用于移动端开发,在原生应用开发中比较常见。

使用

React Native并没有提供使用sqlite的组件,我们可以通过使用第三方组件react-native-sqlite来使用原生的SQLiteDatabase。

Android版配置

1. 安装

在项目根目录下执行cmd命令:

npm install --save react-native-sqlite-storage

2. 修改settings.gradle配置

修改android/settings.gradle的配置

...

include ':react-native-sqlite-storage'
project(':react-native-sqlite-storage').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sqlite-storage/src/android')

3. 修改build.gradle配置

修改android/app/build.gradle的配置

...

dependencies {
    ...
    compile project(':react-native-sqlite-storage')
}

4. 在MainApplication.java注册模块

修改android/app/src/main/java/com/[YourAppName]/MainApplication.java的配置

import org.pgsqlite.SQLitePluginPackage;

public class MainApplication extends Application implements ReactApplication {
  ......

  /**
   * A list of packages used by the app. If the app uses additional views
   * or modules besides the default ones, add more packages here.
   */
    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
        new SQLitePluginPackage(),   // register SQLite Plugin here
        new MainReactPackage());
    }
}

iOS版配置

1. 安装

在项目根目录下执行cmd命令:

npm install --save react-native-sqlite-storage

2. XCode SQLite项目依赖安装

将SQLite项目作为一个库进行依赖到当前项目,如下:

3.XCode SQLite库依赖

将libSQLite.a添加到Libraries and Frameworks中,同时添加sqlite3.0.tbd (XCode 7) 或者libsqlite3.0.dylib (XCode 6 and earlier)到当前位置:

使用实例

新建一个数据库的工具组件SQLite.js,该模块类似于工具类,用于数据库的建表、增删改查等操作,不需要渲染任何界面,所以render return null。

import React from 'react';
import SQLiteStorage from 'react-native-sqlite-storage';

SQLiteStorage.DEBUG(true);
SQLiteStorage.DEBUG(true);
var database_name = "test.db";//数据库文件
var database_version = "1.0";//版本号
var database_displayname = "MySQLite";
var database_size = -1;
var db;
export default class SQLite extends Component {
  render(){
        return null;
    }
}

创建数据表
这里创建了一个用户信息表,表结构如下:

字段 类型 描述
id INTEGER 主键
name VARCHAR 姓名
age VARCHAR 年龄
sex VARCHAR 性别
phone VARCHAR 电话号码
email VARCHAR 邮箱
address VARCHAR 地址
 createTable(){
        if (!db) {
            this.open();
        }
        //创建用户表
        db.transaction((tx)=> {
            tx.executeSql('CREATE TABLE IF NOT EXISTS USER(' +
                'id INTEGER PRIMARY KEY  AUTOINCREMENT,' +
                'name VARCHAR,'+
                'age VARCHAR,' +
                'sex VARCHAR,' +
                'phone VARCHAR,' +
                'email VARCHAR,' +
                'address VARCHAR)'
                , [], ()=> {
                    this._successCB('executeSql');
                }, (err)=> {
                    this._errorCB('executeSql', err);
                });
        }, (err)=> {//所有的 transaction都应该有错误的回调方法,在方法里面打印异常信息,不然你可能不会知道哪里出错了。
            this._errorCB('transaction', err);
        }, ()=> {
            this._successCB('transaction');
        })
    }

定义打开数据open和关闭数据库close的方法

    open(){
        db = SQLiteStorage.openDatabase(
            database_name,
            database_version,
            database_displayname,
            database_size,
            ()=>{
                this._successCB('open');
            },
            (err)=>{
                this._errorCB('open',err);
            });
        return db;
    }
    close(){
        if(db){
            this._successCB('close');
            db.close();
        }else {
            console.log("SQLiteStorage not open");
        }
        db = null;
    }
    _successCB(name){
        console.log("SQLiteStorage "+name+" success");
    }
    _errorCB(name, err){
        console.log("SQLiteStorage "+name);
        console.log(err);
    }

插入数据

insertUserData(userData){
        let len = userData.length;
        if (!db) {
            this.open();
        }
        this.createTable();
        this.deleteData();
        db.transaction((tx)=>{
            for(let i=0; i<len; i++){
                var user = userData[i];
                let name= user.name;
                let age = user.age;
                let sex = user.sex;
                let phone = user.phone;
                let email = user.email;
                let address = user.address;
                let sql = "INSERT INTO user(name,age,sex,phone,email,address)"+
                    "values(?,?,?,?,?,?)";
                tx.executeSql(sql,[name,age,sex,phone,email,address],()=>{

                    },(err)=>{
                        console.log(err);
                    }
                );
            }
        },(error)=>{
            this._errorCB('transaction', error);
        },()=>{
            this._successCB('transaction insert data');
        });
    }

删除数据

deleteData(){
        if (!db) {
            this.open();
        }
        db.transaction((tx)=>{
            tx.executeSql('delete from user',[],()=>{

            });
        });
    }

删除表

dropTable(){
        db.transaction((tx)=>{
            tx.executeSql('drop table user',[],()=>{

            });
        },(err)=>{
            this._errorCB('transaction', err);
        },()=>{
            this._successCB('transaction');
        });
    }

SQLite.js的完整代码如下:

//SQLite.js
import React, { Component } from 'react';
import {
    ToastAndroid,
} from 'react-native';
import SQLiteStorage from 'react-native-sqlite-storage';

SQLiteStorage.DEBUG(true);
var database_name = "test.db";//数据库文件
var database_version = "1.0";//版本号
var database_displayname = "MySQLite";
var database_size = -1;
var db;

export default class SQLite extends Component {

    componentWillUnmount(){
        if(db){
            this._successCB('close');
            db.close();
        }else {
            console.log("SQLiteStorage not open");
        }
    }
    open(){
        db = SQLiteStorage.openDatabase(
            database_name,
            database_version,
            database_displayname,
            database_size,
            ()=>{
                this._successCB('open');
            },
            (err)=>{
                this._errorCB('open',err);
            });
        return db;
    }
    createTable(){
        if (!db) {
            this.open();
        }
        //创建用户表
        db.transaction((tx)=> {
            tx.executeSql('CREATE TABLE IF NOT EXISTS USER(' +
                'id INTEGER PRIMARY KEY  AUTOINCREMENT,' +
                'name varchar,'+
                'age VARCHAR,' +
                'sex VARCHAR,' +
                'phone VARCHAR,' +
                'email VARCHAR,' +
                'address VARCHAR)'
                , [], ()=> {
                    this._successCB('executeSql');
                }, (err)=> {
                    this._errorCB('executeSql', err);
                });
        }, (err)=> {//所有的 transaction都应该有错误的回调方法,在方法里面打印异常信息,不然你可能不会知道哪里出错了。
            this._errorCB('transaction', err);
        }, ()=> {
            this._successCB('transaction');
        })
    }
    deleteData(){
        if (!db) {
            this.open();
        }
        db.transaction((tx)=>{
            tx.executeSql('delete from user',[],()=>{

            });
        });
    }
    dropTable(){
        db.transaction((tx)=>{
            tx.executeSql('drop table user',[],()=>{

            });
        },(err)=>{
            this._errorCB('transaction', err);
        },()=>{
            this._successCB('transaction');
        });
    }
    insertUserData(userData){
        let len = userData.length;
        if (!db) {
            this.open();
        }
        this.createTable();
        this.deleteData();
        db.transaction((tx)=>{
            for(let i=0; i<len; i++){
                var user = userData[i];
                let name= user.name;
                let age = user.age;
                let sex = user.sex;
                let phone = user.phone;
                let email = user.email;
                let address = user.address;
                let sql = "INSERT INTO user(name,age,sex,phone,email,address)"+
                    "values(?,?,?,?,?,?)";
                tx.executeSql(sql,[name,age,sex,phone,email,address],()=>{

                    },(err)=>{
                        console.log(err);
                    }
                );
            }
        },(error)=>{
            this._errorCB('transaction', error);
        },()=>{
            this._successCB('transaction insert data');
        });
    }
    close(){
        if(db){
            this._successCB('close');
            db.close();
        }else {
            console.log("SQLiteStorage not open");
        }
        db = null;
    }
    _successCB(name){
        console.log("SQLiteStorage "+name+" success");
    }
    _errorCB(name, err){
        console.log("SQLiteStorage "+name);
        console.log(err);
    }


    render(){
        return null;
    }
}

调用工具类
在同一目录下新建SQLiteDemo.js调用SQLite.js中封装好的方法,注意使用时先引入SQLite.js

import React, { Component } from 'react';
import {
    AppRegistry,
    Text,
    View,
} from 'react-native';
import SQLite from './SQLite';
var sqLite = new SQLite();
var db;
export default class SQLiteDemo extends Component{
    constructor(props) {
        super(props);
        this.state = {
            name:"",
            age:"",
            phone:"",
            email:"",
            address:"",
        };
    }

    compennetDidUnmount(){
        //关闭数据库
        sqLite.close();
    }
    componentWillMount(){
        //开启数据库
        if(!db){
            db = sqLite.open();
        }
        //建表
        sqLite.createTable();
        //删除数据
        sqLite.deleteData();

        //模拟数据
        var userData = [];
        var user = {};
        user.name = "Mr.Onion";
        user.age = "26";
        user.sex = "男";
        user.phone = "12345678910";
        user.email = "123454321@qq.com";
        user.address = "A市B街111号C室";
        userData.push(user);
        //插入数据
        sqLite.insertUserData(userData);
        //查询
        db.transaction((tx)=>{
            tx.executeSql("select * from user", [],(tx,results)=>{
                var len = results.rows.length;
                for(let i=0; i<len; i++){
                    var u = results.rows.item(i);
                    this.setState({
                        name:u.name,
                        age:u.age,
                        phone:u.phone,
                        email:u.email,
                        address:u.address,
                    });
                }
            });
        },(error)=>{
            console.log(error);
        });
    }
    render(){
        return (
            <View>
                <Text>
                    姓名:{this.state.name}
                </Text>
                <Text>
                    年龄:{this.state.age}
                </Text>
                <Text>
                    电话:{this.state.phone}
                </Text>
                <Text>
                    Email:{this.state.email}
                </Text>
                <Text>
                    地址:{this.state.address}
                </Text>
            </View>
        );
    }
}
作者:teagreen_red 发表于2017/11/15 18:53:00 原文链接
阅读:23 评论:0 查看评论

CCF 通信网络

$
0
0

一、试题

问题描述
  某国的军队由N个部门组成,为了提高安全性,部门之间建立了M条通路,每条通路只能单向传递信息,即一条从部门a到部门b的通路只能由a向b传递信息。信息可以通过中转的方式进行传递,即如果a能将信息传递到b,b又能将信息传递到c,则a能将信息传递到c。一条信息可能通过多次中转最终到达目的地。
  由于保密工作做得很好,并不是所有部门之间都互相知道彼此的存在。只有当两个部门之间可以直接或间接传递信息时,他们才彼此知道对方的存在。部门之间不会把自己知道哪些部门告诉其他部门。

  上图中给了一个4个部门的例子,图中的单向边表示通路。部门1可以将消息发送给所有部门,部门4可以接收所有部门的消息,所以部门1和部门4知道所有其他部门的存在。部门2和部门3之间没有任何方式可以发送消息,所以部门2和部门3互相不知道彼此的存在。
  现在请问,有多少个部门知道所有N个部门的存在。或者说,有多少个部门所知道的部门数量(包括自己)正好是N。
输入格式
  输入的第一行包含两个整数N, M,分别表示部门的数量和单向通路的数量。所有部门从1到N标号。
  接下来M行,每行两个整数a, b,表示部门a到部门b有一条单向通路。
输出格式
  输出一行,包含一个整数,表示答案。
样例输入
4 4
1 2
1 3
2 4
3 4
样例输出
2
样例说明
  部门1和部门4知道所有其他部门的存在。
评测用例规模与约定
  对于30%的评测用例,1 ≤ N ≤ 10,1 ≤ M ≤ 20;
  对于60%的评测用例,1 ≤ N ≤ 100,1 ≤ M ≤ 1000;
  对于100%的评测用例,1 ≤ N ≤ 1000,1 ≤ M ≤ 10000。

二、代码

这题如果使用line[1010][1010]循环遍历会超时。

 for (int i = 1; i <= n; ++i) {
       if (!visited[i] && line[cur][i]){
           dfs(i);
       }
   }

一开始会以为有规律找到起点,结果发现没有规律把某一类别的点当做起点,只能对所有点搜索。

#include <iostream>
#include <cstring>
//#include<map>
#include<vector>
using namespace std;

vector<int> line[1010];
int knows[1010][1010];
int visited[1010]; 
int ans,N;
int top;

void dfs(int cur) {
    knows[top][cur] = knows[cur][top] = visited[cur] = 1;
    for (size_t i = 0; i<line[cur].size(); i++) {
        if ( !visited[line[cur][i]] ){
            dfs(line[cur][i]);
        }
    }

}
int main(){
    int M,l,r;
    cin>>N>>M;
    while(M--){
        cin>>l>>r;
        line[l].push_back(r);
    }

    for (int i = 1; i <= N; ++i) {
        memset(visited, 0, sizeof(int)*1010);
        top = i;
        dfs(i);
    }

    for (int i = 1; i <= N; i++) {
        int j=1;
        for(;j <= N; j++) {
            if(knows[i][j]==0){
                break;
            }
        }
        if (j==N+1)
            ans++;
    }
    cout << ans;
//    for (int i = 1; i <= N; i++) {
//      int total=0;
//        for(int j=1;j <= N; j++) {
//          total += knows[i][j];
//      }
//        if (total==N)
//            ans++;
//    }
//    cout << ans;
    return 0;
}
作者:qq_16234613 发表于2017/11/15 19:10:51 原文链接
阅读:7 评论:0 查看评论

Module (模块) 模式

$
0
0

概念

module 模式 最初被用来定义一个类的私有和共有封装的办法。

在JavaScript中还有降低函数命名冲突的作用。

JS重没有私有共有的概念 不想PHP中定义类就 public 等方法

但是可以通过 函数作用域 使用闭包 的概念来完成

实例

var mynamespace = (function(){
    var myPrivateVar = 0;
    var myPublicVar = "foo"
    var myPrivateMethod = function(foo){
        console.log(foo);
        console.log(myPrivateVar);
        myPublicVar = foo;
    }

    return {
        myPublicVar :myPublicVar,
        myPublicFunction : function(bar){
                myPrivateVar++;
                myPrivateMethod(bar);
        }
    }
})();

在这段代码中,return 回来的两个方法 但是我们的 函数中定义的方法可以看成是私有的方法。

作者:Merciwen 发表于2017/11/15 19:39:23 原文链接
阅读:64 评论:0 查看评论

揭示模式(Revealing Module)

$
0
0

概念

在模块模式的基础上,在返回的私有范围内,重新定义所有的 函数和变量。并返回一个匿名的对象。他拥有所有指向私有函数的指针。

实例

var myRevealingModule = function(){
    var privateVar = "Ben ",
        publicVar = "hello word";

    function privateFunction (){
        console.log("NAME:" + privateVar);
    }

    function publicSetName (strName){
        privateName = strName
    }

    function publicGetName () {
        privateFunction();
    }

    return {
        setName : publicSetName,
        greeting  :privateVar,
        getName : publicGetName
    }
}();

myRevealingModule.setName('mps');

相当于在return的 时候不直接return 会私有属性和方法的名称 而是自己定义一套命名然后全部放在一个匿名函数里面 整体返回 使用的时候你只知道自己定义的名称,不可以使用私有的任何提示信息。

作者:Merciwen 发表于2017/11/15 19:53:14 原文链接
阅读:67 评论:0 查看评论

安卓自定义相机拍照功能全解

$
0
0

全栈工程师开发手册 (作者:栾鹏)

安卓教程全解

安卓实现一个相机的基本功能。

启动和释放相机

由于拍照功能一般需要实时预览,所以比较耗电,因此在窗口的恢复和暂停函数中需要启动和释放相机

  private Camera camera;
//重写窗口函数,重启摄像头
  @Override
  protected void onResume() {
    super.onResume();
    camera = Camera.open();   //打开相机
    init();
    Log.v("拍照", "启动摄像头");
  }
  //重写窗口函数,释放摄像头
  @Override
  protected void onPause() {
    super.onPause();
    camera.release();
    Log.v("拍照", "释放摄像头");
  }

实时预览

预览视图需要使用SurfaceView,因此在你的xml布局文件中要定义一个SurfaceView。
并且要实现SurfaceHolder.Callback预览回调接口,当然一般你看到的教程都是在当前activity中实现这个接口。

public class Camera_Activity extends Activity implements SurfaceHolder.Callback {

  private Camera camera;   //定义一个相机

@Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.camera_activity);

    SurfaceView surface = (SurfaceView)findViewById(R.id.surfaceView);
    SurfaceHolder holder = surface.getHolder();
    holder.addCallback(this);   //在当前窗口中实现回调接口
    holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    holder.setFixedSize(400, 300);
    }

 //实现接口函数
  public void surfaceCreated(SurfaceHolder holder) { 
    try {
      camera.setPreviewDisplay(holder);  //设置使用哪个SurfaceView来显示取景图片
      camera.startPreview();   //开始预览取景
      camera.startFaceDetection();   //启动人脸识别
      //必要时在预览上进行绘制
    } catch (IOException e) {
      Log.v("拍照", e.getMessage());
    }
  }
  //实现接口函数
  public void surfaceDestroyed(SurfaceHolder holder) {
    camera.stopFaceDetection();  //停止人脸识别检测
    camera.stopPreview();    //停止预览取景
  }
  //实现接口函数
  public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
  }

相机的基本操作函数

camera.setDisplayOrientation(90);//设置方向
camera.setErrorCallback(cb);   //设置错误回调函数
camera.setFaceDetectionListener(listener);  //设置人脸检测监听
camera.setPreviewCallback(cb); //设置预览回调
camera.takePicture(shutter, raw, jpeg);  //拍照
camera.autoFocus(cb);  //自动聚焦
camera.setPreviewDisplay(holder);  //设置预览视图对象
camera.startPreview();//启动预览
camera.startFaceDetection();//启动人脸识别
camera.stopFaceDetection();//停止人脸识别
camera.stopPreview();  //停止预览
camera.getParameters();  //获取参数
camera.setParameters(params); //设置参数

拍照

拍照函数使用takePicture,需要传输的参数分别是快门按下回调函数,获得拍照的原始图片数据后的回调函数,获得压缩后的jpeg图片数据回调函数。

//拍照函数
  private void takePicture() {
    camera.takePicture(shutterCallback, rawCallback, jpegCallback);   //拍照,参数:快门函数、获取原始图片函数、获取压缩后图片函数
  }

  ShutterCallback shutterCallback = new ShutterCallback() {
    public void onShutter() {
      //快门关闭时执行的函数
        Log.v("拍照", "快门关闭");
    }
  };

  //拍照后raw原始图像数据
  PictureCallback rawCallback = new PictureCallback() {
    public void onPictureTaken(byte[] data, Camera camera) {
      //对图像的原始数据做一些处理
        Log.v("拍照", "获取拍照后的原始数据");
    }
  };

  //拍照后jpeg编码图像数据
  PictureCallback jpegCallback = new PictureCallback() {
    public void onPictureTaken(byte[] data, Camera camera) {
      // 将图像的jpeg数据保存到sd卡中
      FileOutputStream outStream = null;
      try {
        String path = Environment.getExternalStorageDirectory() + "\test.jpg";

        outStream = new FileOutputStream(path);
        outStream.write(data);
        outStream.close();
        Log.v("拍照", "获取拍照后的jpeg压缩数据");
      } catch (FileNotFoundException e) {
        Log.v("拍照", "没有发现文件", e);
      } catch (IOException e) {
        Log.v("拍照", "IO接口出错", e);
      }
    }
  };

自动聚焦

设置自动聚焦使用autoFocus函数,参数为聚焦完成回调函数,包含聚焦成功或失败。

  //聚焦函数
  private void focus()
  {
      camera.autoFocus(mycallfun);
  }
  //自定义聚焦完成回调函数
  AutoFocusCallback mycallfun = new AutoFocusCallback(){
      @Override
      public void onAutoFocus(boolean success, Camera camera)
      {
          if (success)
          {
              // success为true表示对焦成功,改变对焦状态图像
              Log.v("拍照", "聚焦成功");
          }
      }
  };

读取并修改EXIF数据

拍照后获取的jpeg图片,不仅包含像素信息,还包含拍摄日期,时间,摄像头设置,图片设置,以及图像描述和位置等信息。这些信息被叫做EXIF数据

  private void modifyExif() {
    File file = new File(Environment.getExternalStorageDirectory(),"test.jpg");

    try {
      ExifInterface exif = new ExifInterface(file.getCanonicalPath());
      //读取摄像头模型和位置属性
      String model = exif.getAttribute(ExifInterface.TAG_MODEL);
      Log.v("拍照", "Model: " + model);
      //设置摄像头的品牌
      exif.setAttribute(ExifInterface.TAG_MAKE, "My Phone");
    } catch (IOException e) {
      Log.v("拍照", "IO Exception", e);
    }
  }
作者:luanpeng825485697 发表于2017/11/15 20:45:44 原文链接
阅读:228 评论:0 查看评论

安卓自定义相机录像功能全解

$
0
0

全栈工程师开发手册 (作者:栾鹏)

安卓教程全解

安卓录制视频需要使用MediaRecorder来完成。并且在录制中一般还需要SurfaceHolder进行实时预览,所以在布局文件中进行预览的SurfaceView控件

启动和释放相机

 private Camera camera;

//重写窗口函数,启动摄像头,并直接设置到录像模式
  @Override
  protected void onResume() {
    super.onResume();
    camera = Camera.open();        //启动相机
    camera.setDisplayOrientation(90);   //设置方向
    //使用摄像头录制提示,告诉摄像头你只想录制音频/视频,而不是拍摄静态图片,可以缩短启动时间
    Camera.Parameters parameters = camera.getParameters();
    parameters.setRecordingHint(true);
    camera.setParameters(parameters);
  }

  //重写窗口函数,释放 mediaRecorder
  @Override
  protected void onPause() {
    super.onPause();
    //重置和释放 mediaRecorder
    mediaRecorder.reset();
    mediaRecorder.release();
    camera.lock();

    //释放摄像头
    camera.release();
  }

MediaRecorder准备工作、SurfaceView预览录像

由于MediaRecorder负责接收相机数据源。与拍照预览不同,要实现录像的实时预览,必须将SurfaceView绑定到MediaRecorder的数据流中,而不是绑定到相机。

所以实现SurfaceHolder.Callback接口函数surfaceCreated执行以后才可以创建MediaRecorder,并将其绑定到相机,设置视频源、音频源、画质,输出地址等参数,在设置SurfaceView为预览控件,并准备录制

public class VideoCameraActivity extends Activity implements SurfaceHolder.Callback {
 private Camera camera;
  private SurfaceHolder holder;
  private MediaRecorder mediaRecorder;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.videocamera);

    SurfaceView surface = (SurfaceView)findViewById(R.id.video_surfaceView);
    SurfaceHolder holder = surface.getHolder();
    holder.addCallback(this);
    holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    holder.setFixedSize(400, 300);
    }

//创建MediaRecorder绑定到相机,设置视频源、音频源、画质,输出地址等参数,并准备录制
  private void prepareVideoCamera() throws IllegalStateException, IOException {
    //创建一个新的MediaRecorder
    mediaRecorder = new MediaRecorder();

    //录制前的准备工作

    //解锁摄像头并将其分配给MediaRecorder
    camera.unlock();
    mediaRecorder.setCamera(camera);

    //指定用于录制的输入源
    mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
    mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

    //设置配置文件,或者定义输出格式,音频视频编码器,帧速以及输出尺寸
    CamcorderProfile profile = null;

    if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P))
      profile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P);
    else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P))
      profile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P);
    else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P))
      profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
    else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_HIGH))
      profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);

    if (profile != null)
      mediaRecorder.setProfile(profile); 

    //指定一个输出文件
    mediaRecorder.setOutputFile("/sdcard/myvideorecording.mp4");

    //预览视频流,在指定了录制源和输出文件后,在prepare前设置
    mediaRecorder.setPreviewDisplay(holder.getSurface());

    //准备录制
    mediaRecorder.prepare();
  }



  //接口函数
  public void surfaceCreated(SurfaceHolder holder) { 
    this.holder = holder;
    try {
      prepareVideoCamera();   //准备好MediaRecorder接收相机视频音频源
      Log.v("相机录像", "启动录制");
    } catch (IllegalStateException e) {
      Log.e("相机录像", "Illegal State Exception", e);
    } catch (IOException e) {
      Log.e("相机录像", "I/O Exception", e);
    }
  }

  //接口函数
  public void surfaceDestroyed(SurfaceHolder holder) {
    this.holder = null;
  }
  //接口函数
  public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
  }

}

mediaRecorder开始录制

准备工作做好以后,开始录制就非常简单了。


  //开始录制
  private void startRecording() {
    try {
      mediaRecorder.start();
      Log.v("相机录像", "开始录制");
    } catch (IllegalStateException e) {
      mediaRecorder.release();
      camera.lock();
      Log.d("相机录像", "Illegal State Exception", e);
    }
  }

mediaRecorder停止录制

  //停止录像
  private void stopRecording() {
    mediaRecorder.stop();    
    //重置和释放 mediaRecorder
    mediaRecorder.reset();
    mediaRecorder.release();
    camera.lock();
    Log.v("相机录像", "停止录制,文件已保存");
  }
作者:luanpeng825485697 发表于2017/11/15 20:47:57 原文链接
阅读:82 评论:0 查看评论

Quartz-中断正在执行的任务

$
0
0

概述

由于业务需要,停止Quartz中正在执行的任务

  1. 任务类只需要实现InterruptableJob类,然后实现interrupt()方法。

  2. 在这个方法中进行标记的改变,在执行中进行这个标记判断,就可实现中断任务了

  3. 另外在调度器上调用方法:sched.interrupt(job.getKey())


示例

job类

package com.xgj.quartz.quartzItself.interruptableJob;

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

import org.quartz.InterruptableJob;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.UnableToInterruptJobException;

/**
 * 
 * 
 * @ClassName: DumbInterruptableJob
 * 
 * @Description: 个可执行的中断可执行程序,用于单元测试。
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年11月15日 上午9:26:36
 */

public class DumbInterruptableJob implements InterruptableJob {

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private boolean _interrupted = false; // job 是否中断
    private JobKey _jobKey = null; // job name

    private static int counts = 0; // 中断执行次数

    @Override
    public void execute(JobExecutionContext context)
            throws JobExecutionException {

        _jobKey = context.getJobDetail().getKey();

        System.out.println("【开始执行】任务Key:" + _jobKey + ",执行时间: "
                + sdf.format(new Date()));

        try {

            for (int i = 0; i < 4; i++) {
                try {
                    Thread.sleep(1000L);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                // 查看是否中断
                if (_interrupted) {
                    counts++;
                    System.out.println("被外界因素停止了这个任务key:" + _jobKey
                            + ",中断累计次数: " + counts + "\n");
                    return; // 也可以选择抛出一个JobExecutionException,根据业务需要指定行为
                }
            }

        } finally {
            System.out.println("【完成任务】key:" + _jobKey + " 完成时间:"
                    + sdf.format(new Date()));
        }

    }

    @Override
    public void interrupt() throws UnableToInterruptJobException {
        System.out.println("\n—————— 【中断】外界正在调用调度器停止这个任务key:" + _jobKey
                + " ————————");
        _interrupted = true;

    }

}

调度类

package com.xgj.quartz.quartzItself.interruptableJob;

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: InterruptExample
 * 
 * @Description: 调度类
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年11月15日 上午9:28:21
 */

public class InterruptExample {

    public void run() throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        System.out.println("------- 初始化 ----------------------");

        SchedulerFactory sf = new StdSchedulerFactory();
        Scheduler sched = sf.getScheduler();

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

        // 当前时间15秒后,每间隔5秒执行一次任务
        JobDetail job = newJob(DumbInterruptableJob.class).withIdentity(
                "interruptableJob1", "group1").build();
        SimpleTrigger trigger = newTrigger()
                .withIdentity("trigger1", "group1")
                .startAt(startTime)
                .withSchedule(
                        simpleSchedule().withIntervalInSeconds(5)
                                .repeatForever()).build();

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

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

        System.out.println("------- 每7秒钟启动一次中断任务(10次中断) ----------");
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(7000L);

                // 手动中断调度器中的job
                sched.interrupt(job.getKey());

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        System.out.println("------- 关闭调度 ---------------------");

        sched.shutdown(true);

        System.out.println("------- 关闭调度器完成 -----------------");
        SchedulerMetaData metaData = sched.getMetaData();

        System.out.println("~~~~~~~~~~  执行了 "
                + metaData.getNumberOfJobsExecuted() + " 个 jobs.");

    }

    public static void main(String[] args) throws Exception {
        InterruptExample example = new InterruptExample();
        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
group1.interruptableJob1 将运行于:2017-11-15 09:29:45 并重复:-1 次,间隔 5 秒
INFO  QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
------- 开始调度 (调用.start()方法) ----------------
------- 每7秒钟启动一次中断任务(10次中断) ----------
【开始执行】任务Key:group1.interruptableJob1,执行时间: 2017-11-15 09:29:45

—————— 【中断】外界正在调用调度器停止这个任务key:group1.interruptableJob1 ————————
被外界因素停止了这个任务key:group1.interruptableJob1,中断累计次数: 1

【完成任务】key:group1.interruptableJob1 完成时间:2017-11-15 09:29:49
【开始执行】任务Key:group1.interruptableJob1,执行时间: 2017-11-15 09:29:50
【完成任务】key:group1.interruptableJob1 完成时间:2017-11-15 09:29:54
【开始执行】任务Key:group1.interruptableJob1,执行时间: 2017-11-15 09:29:55

—————— 【中断】外界正在调用调度器停止这个任务key:group1.interruptableJob1 ————————
被外界因素停止了这个任务key:group1.interruptableJob1,中断累计次数: 2

【完成任务】key:group1.interruptableJob1 完成时间:2017-11-15 09:29:56
【开始执行】任务Key:group1.interruptableJob1,执行时间: 2017-11-15 09:30:00

—————— 【中断】外界正在调用调度器停止这个任务key:group1.interruptableJob1 ————————
被外界因素停止了这个任务key:group1.interruptableJob1,中断累计次数: 3

【完成任务】key:group1.interruptableJob1 完成时间:2017-11-15 09:30:03
【开始执行】任务Key:group1.interruptableJob1,执行时间: 2017-11-15 09:30:05
【完成任务】key:group1.interruptableJob1 完成时间:2017-11-15 09:30:09
【开始执行】任务Key:group1.interruptableJob1,执行时间: 2017-11-15 09:30:10
【完成任务】key:group1.interruptableJob1 完成时间:2017-11-15 09:30:14
【开始执行】任务Key:group1.interruptableJob1,执行时间: 2017-11-15 09:30:15

—————— 【中断】外界正在调用调度器停止这个任务key:group1.interruptableJob1 ————————
被外界因素停止了这个任务key:group1.interruptableJob1,中断累计次数: 4

【完成任务】key:group1.interruptableJob1 完成时间:2017-11-15 09:30:17
【开始执行】任务Key:group1.interruptableJob1,执行时间: 2017-11-15 09:30:20

—————— 【中断】外界正在调用调度器停止这个任务key:group1.interruptableJob1 ————————
被外界因素停止了这个任务key:group1.interruptableJob1,中断累计次数: 5

【完成任务】key:group1.interruptableJob1 完成时间:2017-11-15 09:30:24
【开始执行】任务Key:group1.interruptableJob1,执行时间: 2017-11-15 09:30:25
【完成任务】key:group1.interruptableJob1 完成时间:2017-11-15 09:30:29
【开始执行】任务Key:group1.interruptableJob1,执行时间: 2017-11-15 09:30:30

—————— 【中断】外界正在调用调度器停止这个任务key:group1.interruptableJob1 ————————
被外界因素停止了这个任务key:group1.interruptableJob1,中断累计次数: 6

【完成任务】key:group1.interruptableJob1 完成时间:2017-11-15 09:30:31
【开始执行】任务Key:group1.interruptableJob1,执行时间: 2017-11-15 09:30:35

—————— 【中断】外界正在调用调度器停止这个任务key:group1.interruptableJob1 ————————
被外界因素停止了这个任务key:group1.interruptableJob1,中断累计次数: 7

【完成任务】key:group1.interruptableJob1 完成时间:2017-11-15 09:30:38
【开始执行】任务Key:group1.interruptableJob1,执行时间: 2017-11-15 09:30:40
【完成任务】key:group1.interruptableJob1 完成时间:2017-11-15 09:30:44
【开始执行】任务Key:group1.interruptableJob1,执行时间: 2017-11-15 09:30:45
【完成任务】key:group1.interruptableJob1 完成时间:2017-11-15 09:30:49
【开始执行】任务Key:group1.interruptableJob1,执行时间: 2017-11-15 09:30:50

—————— 【中断】外界正在调用调度器停止这个任务key:group1.interruptableJob1 ————————
------- 关闭调度 ---------------------
INFO  QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutting down.
INFO  QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED paused.
被外界因素停止了这个任务key:group1.interruptableJob1,中断累计次数: 8

【完成任务】key:group1.interruptableJob1 完成时间:2017-11-15 09:30:52
INFO  QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutdown complete.
------- 关闭调度器完成 -----------------
~~~~~~~~~~  执行了 14 个 jobs.

示例源码

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

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

pytorch学习笔记(十四): DataLoader源码阅读

$
0
0

pytorch 数据加载部分的 接口可以说是现存 深度学习框架中设计的最好的, 给了我们足够的灵活性。本博文就对 pytorch 的多线程加载 模块(DataLoader) 进行源码上的注释。

输入流水线

pytorch 的输入流水线的操作顺序是这样的:

  • 创建一个 Dataset 对象
  • 创建一个 DataLoader 对象
  • 不停的 循环 这个 DataLoader 对象
dataset = MyDataset()
dataloader = DataLoader(dataset)
num_epoches = 100
for epoch in range(num_epoches):
    for data in dataloader:
        ....

在之前文章也提到过,如果现有的 Dataset 不能够满足需求,我们也可以自定义 Dataset,通过继承 torch.utils.data.Dataset。在继承的时候,需要 override 三个方法。

  • __init__: 用来初始化数据集
  • __getitem__
  • __len__

从本文中,您可以看到 __getitem____len__DataLoader 中是如何被使用的。

DataLoader

DataLoader 看起,下面是源码。为了方便起见,采用在源码中添加注释的形式进行解读。

class DataLoader(object):
    def __init__(self, dataset, batch_size=1, shuffle=False, sampler=None, 
                 batch_sampler=None,
                 num_workers=0, collate_fn=default_collate, pin_memory=False, 
                 drop_last=False):
        self.dataset = dataset
        self.batch_size = batch_size
        self.num_workers = num_workers
        self.collate_fn = collate_fn
        self.pin_memory = pin_memory
        self.drop_last = drop_last

        if batch_sampler is not None:
            if batch_size > 1 or shuffle or sampler is not None or drop_last:
                raise ValueError('batch_sampler is mutually exclusive with '
                                 'batch_size, shuffle, sampler, and drop_last')

        if sampler is not None and shuffle:
            raise ValueError('sampler is mutually exclusive with shuffle')

        if batch_sampler is None:
            if sampler is None:
                if shuffle:
                    # dataset.__len__() 在 Sampler 中被使用。
                    # 目的是生成一个 长度为 len(dataset) 的 序列索引(随机的)。
                    sampler = RandomSampler(dataset)
                else:
                    # dataset.__len__() 在 Sampler 中被使用。
                    # 目的是生成一个 长度为 len(dataset) 的 序列索引(顺序的)。
                    sampler = SequentialSampler(dataset)
            # Sampler 是个迭代器,一次之只返回一个 索引
            # BatchSampler 也是个迭代器,但是一次返回 batch_size 个 索引
            batch_sampler = BatchSampler(sampler, batch_size, drop_last)

        self.sampler = sampler
        self.batch_sampler = batch_sampler

    def __iter__(self):
        return DataLoaderIter(self)

    def __len__(self):
        return len(self.batch_sampler)
# 以下两个代码是等价的
for data in dataloader:
    ...
# 等价与
iterr = iter(dataloader)
while True:
    try:
        next(iterr)
    except:
        break

DataLoader 中,iter(dataloader) 返回的是一个 DataLoaderIter 对象, 这个才是我们一直 next的 对象。

下面会先介绍一下 几个 Sampler, 然后介绍 核心部分 DataLoaderIter

RandomSampler, SequentialSampler, BatchSampler

首先,是 RandomSampleriter(randomSampler) 会返回一个可迭代对象,这个可迭代对象 每次 next 都会输出当前要采样的 indexSequentialSampler也是一样,只不过她产生的 index顺序

class RandomSampler(Sampler):

    def __init__(self, data_source):
        self.data_source = data_source

    def __iter__(self):
        return iter(torch.randperm(len(self.data_source)).long())

    def __len__(self):
        return len(self.data_source)

BatchSampler 是一个普通 Samplerwrapper, 普通Sampler 一次仅产生一个 index, 而 BatchSampler 一次产生一个 batchindices

class BatchSampler(object):
    def __init__(self, sampler, batch_size, drop_last):
        # 这里的 sampler 是 RandomSampler 或者 SequentialSampler
        # 他们每一次吐出一个 idx
        self.sampler = sampler
        self.batch_size = batch_size
        self.drop_last = drop_last

    def __iter__(self):
        batch = []
        for idx in self.sampler:
            batch.append(idx)
            if len(batch) == self.batch_size:
                yield batch
                batch = []
        if len(batch) > 0 and not self.drop_last:
            yield batch

    def __len__(self):
        if self.drop_last:
            return len(self.sampler) // self.batch_size
        else:
            return (len(self.sampler) + self.batch_size - 1) // self.batch_size

DataLoaderIter

  1. self.index_queue 中存放是 (batch_idx, sample_indices) ,其中 batch_idx 是个 int 值, sample_indices 是个 list , 存放了 组成 batchsample indices
  2. self.data_queue 中存放的是 (batch_idx, samples), 其中 samples 是 一个 mini-batch 的样本
  3. self.send_idx 表示:这次 放到 self.index_queue 中的 batch_id
  4. self.rcvd_idx 表示:这次要取的 batch_id
  5. self.batches_outstanding 表示:
class DataLoaderIter(object):
    "Iterates once over the DataLoader's dataset, as specified by the sampler"

    def __init__(self, loader):
        # loader 是 DataLoader 对象
        self.dataset = loader.dataset
        # 这个留在最后一个部分介绍
        self.collate_fn = loader.collate_fn
        self.batch_sampler = loader.batch_sampler
        # 表示 开 几个进程。
        self.num_workers = loader.num_workers
        # 是否使用 pin_memory
        self.pin_memory = loader.pin_memory
        self.done_event = threading.Event()

        # 这样就可以用 next 操作 batch_sampler 了
        self.sample_iter = iter(self.batch_sampler)

        if self.num_workers > 0:
            # 用来放置 batch_idx 的队列,其中元素的是 一个 list,其中放了一个 batch 内样本的索引
            self.index_queue = multiprocessing.SimpleQueue()
            # 用来放置 batch_data 的队列,里面的 元素的 一个 batch的 数据
            self.data_queue = multiprocessing.SimpleQueue()

            # 当前已经准备好的 batch 的数量(可能有些正在 准备中)
            # 当为 0 时, 说明, dataset 中已经没有剩余数据了。
            # 初始值为 0, 在 self._put_indices() 中 +1,在 self.__next__ 中减一
            self.batches_outstanding = 0 
            self.shutdown = False
            # 用来记录 这次要放到 index_queue 中 batch 的 idx
            self.send_idx = 0
            # 用来记录 这次要从的 data_queue 中取出 的 batch 的 idx
            self.rcvd_idx = 0
            # 因为多线程,可能会导致 data_queue 中的 batch 乱序
            # 用这个来保证 batch 的返回 是 idx 升序出去的。
            self.reorder_dict = {}
            # 这个地方就开始 开多进程了,一共开了 num_workers 个进程
            # 执行 _worker_loop , 下面将介绍 _worker_loop
            self.workers = [
                multiprocessing.Process(
                    target=_worker_loop,
                    args=(self.dataset, self.index_queue, self.data_queue, self.collate_fn))
                for _ in range(self.num_workers)]

            for w in self.workers:
                w.daemon = True  # ensure that the worker exits on process exit
                w.start()

            if self.pin_memory:
                in_data = self.data_queue
                self.data_queue = queue.Queue()
                self.pin_thread = threading.Thread(
                    target=_pin_memory_loop,
                    args=(in_data, self.data_queue, self.done_event))
                self.pin_thread.daemon = True
                self.pin_thread.start()

            # prime the prefetch loop
            # 初始化的时候,就将 2*num_workers 个 (batch_idx, sampler_indices) 放到 index_queue 中。
            for _ in range(2 * self.num_workers):
                self._put_indices()

    def __len__(self):
        return len(self.batch_sampler)

    def __next__(self):
        if self.num_workers == 0:  # same-process loading
            indices = next(self.sample_iter)  # may raise StopIteration
            batch = self.collate_fn([self.dataset[i] for i in indices])
            if self.pin_memory:
                batch = pin_memory_batch(batch)
            return batch

        # check if the next sample has already been generated
        if self.rcvd_idx in self.reorder_dict:
            batch = self.reorder_dict.pop(self.rcvd_idx)
            return self._process_next_batch(batch)

        if self.batches_outstanding == 0:
            # 说明没有 剩余 可操作数据了, 可以停止 worker 了
            self._shutdown_workers()
            raise StopIteration

        while True:
            # 这里的操作就是 给 乱序的 data_queue 排一排 序
            assert (not self.shutdown and self.batches_outstanding > 0)
            idx, batch = self.data_queue.get()
            # 一个 batch 被 返回,batches_outstanding -1
            self.batches_outstanding -= 1
            if idx != self.rcvd_idx:
                # store out-of-order samples
                self.reorder_dict[idx] = batch
                continue
            # 返回的时候,再向 indice_queue 中 放下一个 (batch_idx, sample_indices)
            return self._process_next_batch(batch)

    next = __next__  # Python 2 compatibility

    def __iter__(self):
        return self

    def _put_indices(self):
        assert self.batches_outstanding < 2 * self.num_workers
        indices = next(self.sample_iter, None)
        if indices is None:
            return
        self.index_queue.put((self.send_idx, indices))
        self.batches_outstanding += 1
        self.send_idx += 1

    def _process_next_batch(self, batch):
        self.rcvd_idx += 1
        # 放下一个 (batch_idx, sample_indices)
        self._put_indices()
        if isinstance(batch, ExceptionWrapper):
            raise batch.exc_type(batch.exc_msg)
        return batch

    def __getstate__(self):
        # TODO: add limited pickling support for sharing an iterator
        # across multiple threads for HOGWILD.
        # Probably the best way to do this is by moving the sample pushing
        # to a separate thread and then just sharing the data queue
        # but signalling the end is tricky without a non-blocking API
        raise NotImplementedError("DataLoaderIterator cannot be pickled")

    def _shutdown_workers(self):
        if not self.shutdown:
            self.shutdown = True
            self.done_event.set()
            for _ in self.workers:
                # shutdown 的时候, 会将一个 None 放到 index_queue 中
                # 如果 _worker_loop 获得了这个 None, _worker_loop 将会跳出无限循环,将会结束运行
                self.index_queue.put(None)

    def __del__(self):
        if self.num_workers > 0:
            self._shutdown_workers()

__worker_loop

这部分是 多进程 执行的代码:他从index_queue 中 取索引,然后处理数据,然后再将 处理好的 batch 数据放到 data_queue 中。

def _worker_loop(dataset, index_queue, data_queue, collate_fn):
    global _use_shared_memory
    _use_shared_memory = True

    torch.set_num_threads(1)
    while True:
        r = index_queue.get()
        if r is None:
            # 想 data_queue 中放 None
            data_queue.put(None)
            break
        idx, batch_indices = r
        try:
            # 这里就可以看到 dataset.__getiterm__ 的作用了。
            # 传到 collate_fn 的数据是 list of ...
            samples = collate_fn([dataset[i] for i in batch_indices])
        except Exception:
            data_queue.put((idx, ExceptionWrapper(sys.exc_info())))
        else:
            data_queue.put((idx, samples))

collate_fn

  • 我们 __getiterm__ 经常返回的是 (img_tensor, label),

  • 所以 放入 collate_fn 的 参数就是 [(img_tensor, label), ....] .

  • batch[0] 就是 (img_tensor, label) , 也就是 collections.Sequence 类型。
def default_collate(batch):
    "Puts each data field into a tensor with outer dimension batch size"
    if torch.is_tensor(batch[0]):
        out = None
        if _use_shared_memory:
            # If we're in a background process, concatenate directly into a
            # shared memory tensor to avoid an extra copy
            # 计算 batch 中所有 元素的个数 
            numel = sum([x.numel() for x in batch])
            # 没有找到对应的 api 。。。。。。
            storage = batch[0].storage()._new_shared(numel)
            out = batch[0].new(storage)
        return torch.stack(batch, 0, out=out)
    elif type(batch[0]).__module__ == 'numpy':
        elem = batch[0]
        if type(elem).__name__ == 'ndarray':
            return torch.stack([torch.from_numpy(b) for b in batch], 0)
        if elem.shape == ():  # scalars
            py_type = float if elem.dtype.name.startswith('float') else int
            return numpy_type_map[elem.dtype.name](list(map(py_type, batch)))
    elif isinstance(batch[0], int):
        return torch.LongTensor(batch)
    elif isinstance(batch[0], float):
        return torch.DoubleTensor(batch)
    elif isinstance(batch[0], string_classes):
        return batch
    elif isinstance(batch[0], collections.Mapping):
        return {key: default_collate([d[key] for d in batch]) for key in batch[0]}
    elif isinstance(batch[0], collections.Sequence):
        transposed = zip(*batch)
        return [default_collate(samples) for samples in transposed]

    raise TypeError(("batch must contain tensors, numbers, dicts or lists; found {}"
                     .format(type(batch[0]))))

总结

  • data_queue 中最多有 2*num_workerbatch

Queue的特点

  • 当里面没有数据时: queue.get() 会阻塞, 阻塞的时候,其它 进程/线程 如果有 queue.put() 操作,本 线程/进程 会被通知, 然后就可以 get 成功。
  • 当数据满了: queue.put() 会阻塞
作者:u012436149 发表于2017/11/15 22:18:59 原文链接
阅读:59 评论:0 查看评论

Quartz-Cron表达式统计最近几次的执行时间

$
0
0

概述

使用quartz做为后台任务调度框架,cron表达式设置时间,需要根据cron表达式计算出最近n次的执行具体时间–这个通常在开放给用户修改任务执行时间给出提示时非常有用

解决:使用quartz的jar包中提供的TriggerUtils类来计算


示例

1、先根据corn算出执行时间

例如:获取着一个月内 每天早上10:15触发的日期

package com.xgj.quartz.quartzItself.executeTimesCount;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.quartz.TriggerUtils;
import org.quartz.impl.triggers.CronTriggerImpl;

/**
 * 
 * 
 * @ClassName: CountExecuteTimes
 * 
 * @Description: 使用quartz做为后台任务调度框架,cron表达式设置时间,需要根据cron表达式计算出最近n次的执行具体时间--
 *               这个通常在开放给用户修改任务执行时间给出提示时非常有用.
 * 
 *               方法:使用quartz的jar包中提供的TriggerUtils类来计算
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年11月15日 上午11:24:03
 */
public class CountExecuteTimes {

    public static void main(String[] args) {

        try {

            CronTriggerImpl cronTriggerImpl = new CronTriggerImpl();

            // 每天早上10:15触发
            cronTriggerImpl.setCronExpression("0 15 10 * * ?");

            Calendar calendar = Calendar.getInstance();
            Date now = calendar.getTime();
            calendar.add(Calendar.MONTH, 1);// 把统计的区间段设置为从现在到1月后的今天(主要是为了方法通用考虑)

            // 这里的时间是根据corn表达式算出来的值
            List<Date> dates = TriggerUtils.computeFireTimesBetween(
                    cronTriggerImpl, null, now,
                    calendar.getTime());
            System.out.println(dates.size());

            SimpleDateFormat dateFormat = new SimpleDateFormat(
                    "yyyy-MM-dd HH:mm:ss");
            for (Date date : dates) {
                System.out.println(dateFormat.format(date));
            }

        } catch (ParseException e) {
            e.printStackTrace();
        }

    }
}

运行结果

30
2017-11-16 10:15:00
2017-11-17 10:15:00
2017-11-18 10:15:00
2017-11-19 10:15:00
2017-11-20 10:15:00
2017-11-21 10:15:00
2017-11-22 10:15:00
2017-11-23 10:15:00
2017-11-24 10:15:00
2017-11-25 10:15:00
2017-11-26 10:15:00
2017-11-27 10:15:00
2017-11-28 10:15:00
2017-11-29 10:15:00
2017-11-30 10:15:00
2017-12-01 10:15:00
2017-12-02 10:15:00
2017-12-03 10:15:00
2017-12-04 10:15:00
2017-12-05 10:15:00
2017-12-06 10:15:00
2017-12-07 10:15:00
2017-12-08 10:15:00
2017-12-09 10:15:00
2017-12-10 10:15:00
2017-12-11 10:15:00
2017-12-12 10:15:00
2017-12-13 10:15:00
2017-12-14 10:15:00
2017-12-15 10:15:00

2、然后加上一层for循环,就可以得到指定个数的执行日期了

for (int i = 0; i < dates.size(); i++) {
    if (i >= 10) { //这个是提示的日期个数
        break;
    }
    System.out.println(dateFormat.format(dates.get(i)));
}

示例源码

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

作者:yangshangwei 发表于2017/11/16 0:02:47 原文链接
阅读:23 评论:0 查看评论

【树莓派自动化应用实例】整点提醒自己休息五分钟

$
0
0

背景介绍

效果图

我有一个习惯,定闹钟每隔60分钟左右,提醒自己休息一次。我发现自己有时候长时间思考,很容易拘泥于细节之中。适当的简单休息过后,往往会对正在解决和处理的问题有新的认识和发现,有事半功倍的奇效。

不过大部分手机闹钟都不支持这种以小时为单位的周期闹铃。所以,我以前每次都是都手动调整闹钟时间。总感觉有点 Low!于是,我就写了个简单的发邮件的 Lua 脚本,放到树莓派上作为一个shell命令使用;然后在每周一到周五的9点至23点整点各执行一次发邮件的操作。邮件是发到了我的 QQ 邮箱。收到QQ邮件后,左上角会有一个通知悬浮窗,体验比手机的震动声好了很多。

另外,之所以会选择使用 Lua 语言,只是最近自己刚好在看 Lua 而已,用其他语言也是可以的。

在树莓派上配置必要的 Lua 环境

安装 LuaJIT

树莓派的 debian 官方推荐定制系统,内置有 Lua 5.1.5.但是 LuaJIT 使用了 JIT 技术,执行效率更高,所以更推荐安装和使用。LuaJIT 对应的也是 Lua 5.1 的语法。

你可以在 LuaJIT 下载页 右键查看最新的 LuaJIT 稳定版本,然后参照执行:

wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz
tar xzf LuaJIT-2.0.5.tar.gz
cd LuaJIT-2.0.5
make && sudo make install

验证是否安装成功,请执行:

luajit -v

安装成功,会输出:

LuaJIT 2.0.5 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/

安装 luarocks

luarocks 是 Lua 的包管理工具。在树莓派上需要从源码安装 luarocks 。这需要一些技巧。

你可以到 luarocks下载页 查看最新的luarocks版本,然后参考执行以下命令:

wget http://luarocks.github.io/luarocks/releases/luarocks-2.4.3.tar.gz
tar xzf luarocks-2.4.3.tar.gz
cd luarocks-2.4.3

luarocks 在编译前,需要先进行配置。完整的配置内容,参见:Customizing your settings。基于 LuaJIT 的配置命令如下:

./configure --lua-suffix="jit"

然后编译安装:

make build
sudo make install

验证是否安装成功:

luarocks

安装 luasocket

luasocket 这个库,下面的代码会用到,需要提前安装下。 如果 luarocks 安装成功,那其他的 lua 库安装就非常简单了:

sudo luarocks install luasocket

简单的 Lua 脚本: 发送邮件提示自己注意休息

新建一个 rest_reminder.lua 文件

vi rest_reminder.lua

然后输入以下 Lua 代码:

#!/usr/bin/env luajit
local smtp = require("socket.smtp")
from = "username@exapmle.com" --发件人
--收件人列表
rcpt = {
    "your_qq_number@qq.com"
}
mesgt = {
    headers = { -- 只是文字显示内容.
        to = "your_qq_number@qq.com", --收件人
        subject = "请休息五分钟!" --主题
    },
    body = "您已连续思考一小时,请先休息五分钟吧!"
}
r, e = smtp.send{
    from = from,
    rcpt = rcpt,
    source = smtp.message(mesgt),
    server = "smtp.example.com",
    user = "username@exapmle.com",
    password = "username_pwd"
}
if not r then
    print(e)
else
    print("发送成功!")
end

如果你想实现一些更复杂的操作,最好在常用电脑上配置一个 Lua 完整的开发环境

想要文件能执行当做命令执行,还需要给文件添加可执行权限:

chmod +x ./rest_reminder.lua

测试代码是否有效:

./rest_reminder.lua

正常执行的话,你的收件人邮箱(或邮件垃圾箱),应该会收到一封邮箱。

test

注意:

  • headers 中的收件人会显示在邮件头,但是真正决定发送给谁的是 rcpt
  • smtp.example.com 要改为发信邮箱的 smtp 服务器。
  • username@exapmle.comusername_pwd 要改为真实的邮件用户名和密码
  • 部分邮箱的 smtp 功能,可能需要单独开通。

使用 crontab 来定时执行

crontab 简单实用,如果不是很熟悉,可以把它简单当做一个高度自定义的定时器。

假设脚本的完整路径是 /home/pi/task/rest_reminder.lua

执行:

crontab -e

在打开的内容区末尾新增:

00  9-23  *  *  *  /home/pi/task/rest_reminder.lua

保存退出,正常应该会看到提示:

crontab: installing new crontab

此时,我们的定时脚本已经生效了。即使重启电脑,这个脚本依然可以正常定时执行。

crontab 借助于 cron 服务。在必要时,你可以使用 sytemed 的命令来操作 cron 服务:

# 查看状态
sudo systemctl status cron
# 激活服务
sudo systemctl enable cron

参考文档

作者:sinat_30800357 发表于2017/11/16 1:03:30 原文链接
阅读:94 评论:0 查看评论

以太坊重放攻击

$
0
0

引言

以太坊硬分叉后出现了大量的“重放攻击”,有交易所声称丢了币,用户丢币的事更多。那到底什么是重放攻击呢?

计算机术语里的“重放攻击”

以太坊硬分叉后发生的“重放攻击”和传统计算机术语不是一回事。

传统术语“重放攻击”:指的是身份欺诈。在维基百科上定义很清晰,如下,

假设Alice向Bob认证自己。Bob要求她提供密码作为身份信息。同时,Eve窃听两人的通讯,并记录密码。在Alice和Bob完成通讯后,Eve联系Bob,假装自己为Alice,当Bob要求密码时,Eve将Alice的密码发出,Bob认可和自己通讯的人是Alice。
以太坊硬分叉后产生的“重放攻击”并不是身份欺诈,是一条链上的交易在另一条链也往往是合法的,交易可以重新在另一链上广播,所以才被称为“重放攻击”,但这本质上并不是一种“攻击”。

以太坊硬分叉后发重的“重放攻击”

以太坊在192万区块高度发生了硬分叉,产生了两条链,分别称为ETH chain和ETH Classic chain,上面的代币分别称为ETH和ETHc。这两条链上的地址和私钥生产算法相同,交易格式也完全相同,导致在其中一条链上的交易在另一条链上很可能是完全合法的。所以你在其中一条链上发起的交易,就可以到另一条链上去重新广播,可能也会得到确认。这就是“重放攻击”。

我们使用例子来说,这次以太坊硬分叉后发生的“重放攻击”是这样的:

1.以太坊在第1920000区块高度上硬分叉出来两条链,分别称为ETH chain和ETH Classic chain,上面的代币分别称为ETH和ETHc。

2.在硬分叉高度前的所有ETH都在分叉后的两条链上有用,即持有分叉前的ETH都自动被赠送等额ETHc。

3.某个用户持有在硬分叉高度前的ETH,该用户将自己的硬分叉高度前的ETH通过自己控制私钥的本地钱包(这个无论是ETH chain还是ETH Classic chain钱包都是一样的)发一笔交易到交易所充值到其账户的ETH。但ETH chain和ETH Classic chain都能够识别这一次交易,都是合法的交易,都会打包交易。也就是本来用户是在一条链上广播交易,但可以在另一条链上被“重放”广播了。(你可以自己用钱包去广播,也可能会有人或程序找到你的交易信息帮你去广播)

4.因为用户充值账户是交易所的,对用户来说是一个offchain钱包。如果交易所不给你,那你就丢掉了本来应该属于你的ETHc。

5.如果用户在硬分叉高度前是将ETH存放在交易所,原则上交易所应该是给用户两种币。当用户从交易所提取ETH到本地钱包时,刚好交易被提取的币是在硬分叉高度前的币。而且你又在你自己的电脑上装了两个钱包,分别是ETH chain钱包和ETH Classic chain钱包,而且提币地址在ETH chain钱包上生成,然后通过导入私钥的形式钱地址导进ETH Classic chain钱包上。那你提币的同时,可以在两条链上都广播你的提币交易,这样你的两个本地钱包都能收到等额的币。

6.步骤5里,如果用户只装了一个钱包比如ETH chain钱包,那另一份ETHc就会收不到,但并不会丢,因为你的收币私钥是和你的ETH chain一样的,只要你提取出来这个私钥,然后导入ETH Classic chain钱包就可以了。

7.步骤5里,如果用户提走ETH,是充值到了另一个交易所,比如从yunbi提ETH到P网。这时候P网又只给用户一种币即ETH,那用户就会丢失掉ETHc,丢掉的在谁手上呢,在P网手上。如果用户又回去问yunbi交易所要一份ETHc,yunbi很人品好有担当,真给了,那云币就相当于赔了一份ETHc币。

8.任何持有920万区块高度后的币,情况要更复杂。比如有一笔币从硬分叉前发到硬分叉后的一个地址1上,这个地址1同时在ETH和ETHc都是有效的。那你在ETH chain上收到的币从地址1再次发送到地址2,我们记这笔交易为交易2。这笔交易在ETH Classic chain也是有效的,也是可以被重放的,我们记被重放的这笔交易为交易2′。但如果有办法让交易2发送有效,但让交易2′无效,即ETH chain上的币从地址1成功发送到了地址2,但在ETH Classic chain上从地址1发送到地址2失败了。这种情况下,ETH chain的地址2的币再次交易时就无法被重放到ETH Classic chain上了。

9.步骤8里,什么情况下能让交易2′发送失败呢?也就是怎么样才能解决掉重放攻击呢?其中一个办法就是在交易2′发送后,但0确认前,对交易2′发起双花攻击,即使用相当的私钥再签一次名发起交易2”,将币发到另外一个地址3,如果交易2”成功了,而交易2′失败了,从此ETH chain上的地址2的币和ETH Classic chain上的地址3的币都无法被重放了。

能不能将交易2′在ETH Classic chain上不广播呢?不能,你不广播,会有人帮你广播的,交易信息不是加密的。谁知道哪个狗日的坏人找到你的交易信息后给你广播一下,你的这条分支的币就被发到了这条链的地址2了。

但要想把所有ETH地址和ETHc地址(这两种地址是完全一样的,ETH地址也就是ETHc地址)做到你有币我就没有,那几乎是不可能做到的,地址太多了。也就是想用这个办法彻底解决掉重放问题是不可能的了。

但对于用户来说,你可以使用这个办法分离你的ETH和ETHc,使这两者分另处在不同的地址上,这样你以后就不需要想着在一条链上做了交易我是否需要重放到另一条链了。不过问题又来了,谁会发起双花攻击0确认的交易呢?好像很难啊,所以我认为普通用户基本上是做不到的。交易所应该会。

10.还有一种办法,对其中一条链展开51%攻击,直接消灭掉其中一条链就可以了,让其算力跌到零,从此高度不再上涨,也就是这种链死了。这样问题就彻底解决掉了。

或者让其中一条链的价格跌到很低很低,大家都不关心了,也就懒去理重放交易了。

以太坊经济生态圈各节点受重放攻击的影响

对用户来说以太坊目前的问题很大了,因为ETH和ETHc都有很好的经济量,而用户如果无法解决掉自己的币被重放的可能,他想卖其中一个币的同时保留另一个币,就很难实现了。要么就只能在良心交易所的协助下才能完成。

如果用户可以无视其中一种币,只钟情另一种币。比如只用ETH,不用ETHc,那对该用户来说,重放攻击就想不存在一样。但有几个人能做到呢,明知道通过重放交易还可以多卖一份钱,谁能不在意!

如果是新用户到没什么困惑的了,新用户去买其中一笔币,那他很难有机会通过重放交易的方式搞到另一种币。本来用户是免费得到一份ETHc的,现在他们不得不小心处理自己的币,免得被重放丢失。

目前ETH和ETHc的经济活动基本上还是保留在交易所内的,从新闻报道来看,现在交易所基本上有能力解决重放交易了。之前损失的也和用户做好赔偿和责任划分了。任何ETH和ETHc只要经过了交易所后,对用户来说就是只有一种币了。但问题是交易所如何解决重放交易的,可以有不作恶和作恶两种解决方式。可怕的是作恶也是合法(合以太坊区块链的法)。

好的交易所会在收到用户的ETH或ETHc时尝试将交易重放到另一条链,如果重放成功,就给用户充值两种币,如果不成功就给用户充值一种币。然后在交易所内部将币彻底分离到只存在其中一条链。坏的交易所是,用户充值什么币就只入账什么币,并且将交易去重放,如果成功了就私吞。

交易所还会分聪明的和笨的,聪明的交易所会将用户充值的两种币彻底分离。当用户买入其中一种币并且提币时,交易无法被重放到另一条链。而笨的交易所不会分离两种币,用户提一种币时,将交易重放到另一条链,如果成功就白得一笔钱。

而矿工和矿池呢?无所谓哦,他们挖了ETH就不能挖ETHc,对他们来说重放攻击问题不大,新挖到的币也没法重放。通过难度和价格一合算,哪个更挣钱挖哪个,无所谓。

以太坊经济生态圈里还有开发者,目前以太坊基金会态度竟然是包容ETH Classic。

就以上分析,在以太坊经济生态圈里,因为重放攻击的存在,用户麻烦最大;交易所要受到良心和技术上的考验;而矿工和开发者好像影响不大。

作者:wo541075754 发表于2017/11/16 8:01:04 原文链接
阅读:81 评论:1 查看评论

C++ 二维数组详解

$
0
0

严格来说,C++中没有多维数组,通常所说的多维数组实际上是数组的数组


多维数组的初始化

int a[2][2] = {{1,2},{3,4}};//最正常的初始化,谨记多维数组是数组的数组
int a[2][2] = {1,2,3,4};//与上等价,但这样看起来不是很方便
int a[2][2] = {{1},{2}};//只初始化每一行的第一个元素,其它执行默认初始化
int a[2][2] = {1,2,3};//逐个放进去


多维数组作为函数的形参

由于多维数组其实就是数组的数组,而传递一个以为数组的形参时,传递的是一个指向其第一个元素的指针,那么传递多维数组时,传递的也是一个指向其第一个元素的指针,而这个元素是一个数组。

综上,传递多维数组的参数时,是传递一个指向数组的指针(数组的后面维度的大小都是数组类型的一部分,不可省略)。

int *a[10];//10个int指针构成的数组
int (*a)[10];//指针a,指向一个10个int元素的数组

int a[][10];//参数形式~,但即使将第一维参数加上,编译器也会自动忽略,所以,通常函数还需要一个参数来表示其第一维度的大小


作者:misayaaaaa 发表于2017/11/16 9:17:45 原文链接
阅读:65 评论:0 查看评论

程序员如何赚「睡后」收入?

$
0
0

前两天,有读者问我这么一个问题:「张哥,我是一个工作两年左右的程序员,但是想多赚点钱,就想到接点外包单子做做,不知道可行不?求张哥指点下。」

我个人觉得想多赚点钱这种想法非常好,我也非常支持,绝大部分情况下,赚钱会促使你进步。但是接私活是不是一种比较好的赚钱方式呢?且听我来给你分析下。

我们上班打工,本质上是出售自己时间换取的收入。区别就是,有人把时间卖的单价高,有人卖的单价低,但是,所有人一天都是 24 小时啊,如果仅仅但是想靠这种方式,你拼搏努力可以衣食无忧,甚至过上小康生活,但是靠这种方式发财致富很难。

但是也有人说了,我不想发财致富,我就想上班多赚点,提升下自己的生活质量就好了。如果你是这种心态,完全没问题,想靠出售时间多赚点,那么只有以下两种方式:

  1. 提升自己的单位时间价格。这个很好理解,高级工程师比初级工程师工资高,本质上就是因为高级工程师的单价比初级工程师的高,所以提升自己在专业领域的能力,积累经验,以后不管你升值加薪还是跳槽,都可以达到提升单位时间价格的目的。

  2. 增加自己可出售的时间。什么意思呢?一样的单价,别人工作 8 小时,业余时间 3 小时,你也工作 8 小时,业余时间 3 小时拿出 2 小时继续工作,那总收入就会比别人高,这种靠堆时间,靠拼来增加收入。

说到这里,我们再回头看看接私活这种,毫无疑问,接私活就属于第二种「增加自己可出售的时间」,所以,程序员想接私活完全可以的,但是,这里有个性价比的问题。

如果现在的单位时间价格不高,还有很大的提升潜力,那么接私活的时间用在提升自己能力,从而可以提升自己的单位时间价格,那么才是最优策略。而如果你接私活的单位时间价格还可以,那么就可以考虑接,而且我也建议,接私活的单价一定要比你上班的单价高,这种从经济学的角度来说,才是比较划算的。

但是接私活不属于「睡后」收入,那么,什么是「睡后」收入呢?顾名思义,就是,让你睡觉都能赚钱。毫无疑问,接私活不属于这种,你必须做完一个接一个,才能持续赚到这份辛苦钱。那么,什么样的工作才算得上「睡后」收入呢?

程序员比较能接受的比如写书、录制课程,这种就属于「睡后」收入了,花一段时间写完一本书、或者录制一套收费课程,之后,就靠渠道帮你推广,只要一直有人付费购买,那你就永远可以睡着赚钱。当然了,这里只是举个例子,写书、录制课程钱也没那么好赚,你写的够不够好,有没有影响力,有没有渠道资源帮你推广等等,都决定了你是否可以睡着赚大钱,但它们是属于「睡后」收入的一种方式。

还有,早几年移动互联网刚起步的几年,流量红利时期,很多聪明的程序员就做了一些 App,集成一些广告,然后就躺着赚钱了,早期做的,而且还能赚到不少钱,再早几年的网站、博客等,抓点聚合内容,放点广告,都是属于「睡后」收入的方式。

你会发现,「睡后」收入不一定可以赚大钱,但是只要你写的书火了,你做的 App 用的人多了,你做的课程口碑不错,有渠道帮你推荐宣传,那么这种方式赚钱很容易,后期你几乎不用再投入多少时间,真的是睡着就把钱给赚了,相比于出售时间来赚钱,「睡后」收入潜力很大,想象空间更大。

除以上之外,还有一种适合所有人的「睡后」收入方式,这就是我之前一直跟大家普及的投资理财,投资理财说白了,就是用钱来帮你赚钱,仔细想想,那些有钱人之所以越来越有钱,就是因为他们懂得钱生钱,有人投资公司、有人玩股票、玩期货,有人买基金、理财、国债等,可能有些人一听我说投资理财就反感,过来人的经验告诉你,投资理财真的很重要,而且这是每个人都可以增加「睡后」收入最简单直接的方式,就说一句,我这一两年内的理财与定投,可以让我少奋斗两年。不过,我们普通人是不具备金融方面的专业知识的,公开场合我不推荐大家玩一些高风险的投资品种,有兴趣的可以了解些基金和理财方面的知识,很值得去尝试下。。

总之,如果大家想赚一些外快,优先推荐选择「睡后」收入的方式,其次再选择提升自己单位时间价格的方式,最后再选择堆时间来赚钱的方式,别看很可能一开始没多大差别,但是时间长了,你所选择的方式会导致差距千差万别。

PS:本文原创发布于微信公众号「googdev」,欢迎关注,帮忙程序员提升认知!

作者:googdev 发表于2017/11/16 9:38:41 原文链接
阅读:170 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live


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