首页 文章 精选 留言 我的

精选列表

搜索[提高],共10005篇文章
优秀的个人博客,低调大师

(3)面向对象提高

几乎任何高级编程语言,均支持面向对象。面向对象属于一种思想,需要程序员为此持续锻炼,巩固。Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的 这里详细介绍Python的面向对象编程,如果以前没有接触过面向对象的编程语言,首先要了解下面向对象的一些基本特征:面向对象技术简介 1、概念定义 类(Class): 用来描述具有相同的属性和方法的对象集合。它定义了该集合中每个对象所共有的属性和方法。 对象(object):通过类定义的数据结构实例。对象包括两个数据成员==属性和方法 实例化:创建一个类的实例,类的具体对象体现,将类通过构造函数转化为实例的过程 2、类的成员 属性:类变量或者实例变量,用于处理类及其实例对象的相关的数据,比如字符串、数组、字典等 类变量:它定义在类中且在方法之外,通常不作为实例变量使用。类变量在整个实例化的对象中是公用的。 实例变量:它定义在方法中指令序列的变量,作用于当前实例。每个实例都有自己的实例变量 方法:类中定义的函数体,即指令序列,往往可以操作实例变量或者完成某个特定的功能 形参:方法名使用的参数,特殊的是self形参,是指类实例指针,构造方法会初始化self,用来指示对象的内存地址 实参:方法体使用的参数,是指令序列可以实际操作和修改的参数,一般用[self.实参]表示 理解self:调用类的构造方法,会初始化self为类实例的对象标识,所以一般也称它为 ==> 类实例指针 3、类的创建 使用 class 语句来创建一个新类,class 之后为类的名称并以冒号结尾: class ClassName: '类的帮助信息' #类文档字符串 class_suite #类体 例子理解 #!/usr/bin/python # -*- coding: UTF-8 -*- class Employee: '所有员工的基类’ empCount = 0 #类变量有点类似全局变量 def __init__(self, name, salary): self.Name = name self.Salary = salary Employee.empCount += 1 def displayCount(self): print "Total Employee %d" % Employee.empCount def displayEmployee(self): print "Name : ", self.name, ", Salary: ", self.salary 1)empCount 是一个类变量,它的值将在类的所有实例之间共享。你可以在内部类或外部类使用 Employee.empCount 访问2)第一种方法init()方法是类的构造函数或实例化方法,当类被实例化为对象时,默认就会调用该方法3)self 代表类的实例,self 在定义类的方法时是必须有的,虽然在调用时不必传入相应的参数。4)构造函数的变量称为局部变量,self.Name才是实例变量 4、类实例化 类的实例化方法:init() 实例化方法:实例化时调用[类()],称为类的构造方法,会初始化[self]为实例标识,然后再调用init()初始化方法初始化方法:执行初始化方法,它的形参也是构造方法的形参,形参传递给实例变量 ==> self.valume形式 核心:实例化方法用来初始化实例变量 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖override也称为方法的重写。即:需要从子类中重新改写方法。如果要调用父亲同名的方法,使用super() 比如: class Father: def bor(self): pass class Son(Father): def bor(self #重写) super(Son, self).bor() #调用父类方法其一 Father.bor(self, ...) #调用父类方法其二 pass 5、类的三大特征 封装:将事物共有的属性或者共有的方法,集成到类里,可以实例化为不同的对象,就称为封装 封装有点类似函数,可以将共同的属性和方法集成一次,以对象标识区分不同的对象,而求同存异 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。可以调用基类的方法 继承也允许把一个派生类的对象作为一个基类对象对待。 多继承:默认的情况下,所有的面向对象编程中,一个派生类只能继承一个基类, 但是再C++和Python中,支持一个派生类继承多个基类;这个时候基类中相同的方法,派生类会按照继承顺序,依次使用方法;但是,如果两个继承基类,又继承同一个根类,那么根类中的方法,最后才执行 多态:Python原生多态,即变量不区分类型 ======================面向对象中高级进阶============================== 6、面向对象的高级知识 a、类的属性字段 1)类的帮助信息可以通过ClassName.doc属性查看2)class_suite 类成员是由,类中的方法,数据属性组成 (其一)数据属性:也叫做字段,或者叫变量 实例变量(普通字段) ==> 实例化方法中初始化的变量,称为实例变量,作用域是对象中,一般形式时self.valume访问方式:必须初始化为对象,通过[对象名.实例变量],定义在self后面 形式变量 ==> 方法名(包括构造方法)的参数列表中的变量,称为形式变量,或者形参,只给编译器使用访问方式:无(执行对象方法时,编译器会将传递的参数给实例变量) 类变量(静态字段) ==> 类方法中初始化的变量,称为类变量,在所有实例化对象之间共享,保存再类中访问方式:[类名.类变量名](也可以通过对象访问,[对象名.类变量名]) b、类的实例化和方法 实例化类其他编程语言中一般用关键字 new,但是在 Python 中类的实例化类似函数调用方式 以下使用类的名称 Employee 来实例化,并通过 init 方法接受参数类方法init() 是类的构造函数或初始化方法,类被实例化时就会调用这个方法 1)"创建 Employee 类的第一个对象" emp1 = Employee("Zara", 2000) 2)"创建 Employee 类的第二个对象" emp2 = Employee("Manni", 5000) 注意:访问属性 1)您可以使用点(.)来访问对象的属性和方法。使用如下类的名称访问类变量: emp1.displayEmployee() emp2.displayEmployee() print "Total Employee %d" % Employee.empCount。 2)你也可以使用以下内嵌函数的方式来访问属性: getattr(obj, name[, default]) : 访问对象的属性。 hasattr(obj,name) : 检查是否存在一个属性。 setattr(obj,name,value) : 设置一个属性。如果属性不存在,会创建一个新属性。 delattr(obj, name) : 删除属性。 注意:执行动作 很清楚:类是由数据成员和方法构成的;数据成员即类属性:方法是类中定义的动作 (其二)方法:也叫做类中的函数 类的实例化方法:init() 实例化方法:实例化类用[类()],将初始化[self]为实例标识(指针)和实例变量内部先调用new()构造方法,然后调用init()初始化方法,传递形参 实例方法: 使用 def 关键字定义在类内部,以self为首形参,并通过实例对象调用的方法。==> 称为实例方法实例方法必须包含参数self,调用有通过[对象名.方法名]执行,往往集成类的实现功能 类方法: 使用 def 关键字定义在类内部,以cls为首形参,并通过类直接调用的方法。 ==> 称为类方法类方法前加:装饰器 @classmethod ,必须包含参数cls,调用通过[类名.类方法]执行 类的静态方法: 使用 def 关键字定义在类内部,不以self和cls为参数,并通过类直接调用的方法。 ==> 称为静态方法静态方法前加:装饰器 @staticmethod ,不使用self、cls作为参数,属于类,调用通过[类名.静态方法]执行 不伦不类的获取属性: 在类的内部,使用 def 关键字定义一个方法。需要方法前加:装饰器 @property ==> 用户获取方法值特殊属性包含参数self,通过对象名.方法名调用,但不需要括号了 不伦不类的设置属性: 在类的内部,使用 def 关键字定义一个方法。需要方法前加:装饰器 @获取方法.setter ==> 用于设置方法值, 特殊属性包含参数 self,通过[对象名.方法名]=value调用 不伦不类的删除属性: 在类的内部,使用 def 关键字定义一个方法。需要方法前加:装饰器 @获取方法.delete ==> 用于删除方法, 特殊属性包含参数 self,通过[del 对象名.方法名]调用,不是真正的删除,只是一种调用方式 然后:================== 很明显:@property 装饰器 ==> 把类中动态方法的访问形式,转化为动态字段的访问方式 比如:调用 emp1.displayEmployee()动态方法调用,可以转化为 emp1.displayEmployee 动态字段调用 还有另一个写法:def bor(): property(fget=bor,fset=xxx, fdel=zzz) ,property方法三个参数,和上面是一样的 c、类的成员修饰符 公有字段 == 能够在类内定义,类外访问的属性或变量 定义:self.value = 'liujian' ; 访问:print(obj.value)默认定义的实例变量、类变量等字段,都是公有字段 私有字段 == 在类内定义,类内访问,类外不可以访问的属性或变量 __private_attrs:两个下划线开头,声明该属性为类的私有字段,不能在类的外部被使用或直接访问。在类内部通过定义方法,使用时return self.__private_attrs。 公有方法 == 能够在类内定义,类外执行的方法或者函数 定义:def func(): pass ; 执行: obj.func()默认定义的动态方法、静态方法等,都是公有方法 私有方法 == 在类内定义、类内调用,类外不可以调用执行的方法 __private_method:两个下划线开头,声明该方法为私有方法,不能在类地外部调用执行。在类的内部调用 self.__private_methods 注意:私有属性 ==> 是指,仅仅能够在定义的类中,访问或者调用的属性,不能继承 Python不允许实例化的类访问私有数据,但你可以使用 object._className__attrName 访问属性, 将如下代码替换以上代码的最后一行代码: ......................... print counter._JustCounter__secretCount 注意:通常你需要在单独的文件中定义一个类 类名()==》 调用init()方法对象名()==》 调用call()方法 特殊的内部方法: def __init__(self): 构造方法,当类被初始化为对象时,自动执行 def __call__(self): 特殊方法,当对象以方法的形式调用时,自动执行,比如obj() def __int__(self): 数值转化方法,直接打印,将对象转化为数字 def __str__(self): 字符串转化方法,直接打印,将对象转化为字符串 def __del__(self): 析构方法,在对象销毁的时候被调用,当对象不再被使用时,__del__方法运行 def __add__(self): 对象之间操作,默认会执行第一个对象obj1.__add__()方法,并传递其他对象为参数other 当两个对象相加时,默认执行第一个对象的__add__()方法,并将第二个对象作为参数传递进去 ==> 在python中,每种类型都是一个类,这种类型的操作支持,都是都是因为类中存在很多特殊方法 <== 比如:list[val] ==> 对应方法如下: def getitem(self, itme) 切片或者索引 ; def set(self, key, value) ; def delitem(self, value) 另外一个重要的特殊修饰符: def iter(self) ==> 执行对象的iter方法,获取一个迭代器对象供给for循环迭代(迭代器是另一个类) 注意:Python内置类属性 __dict__ : 类的自身属性(这是一个字典,由类的数据属性和结构组成) __doc__ :类的文档字符串(主要对类进行说明,或者作者简介) __name__: 类名,本类的名称 __module__: 类定义所在的模块(类的全名是'__main__.className',如果类位于一个导入模块mymod中,那么className.__module__ 等于 mymod) __bases__ : 类的所有父类构成元素(包含了一个由所有父类组成的元组) 单下划线、双下划线、头尾双下划线说明: _foo: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于 from module import * __foo: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了 __foo__: 定义的是特殊方法,一般是系统定义名字 ,类似 __init__() 之类的 类在实例化时,可以传递变量参数;如果类在实例化后,也可给对象赋值 1)默认情况里,对象被实例化后,不可以修改,也就是只读,但是有个前提:继承object基类 2)装饰器 @function_name.setter ,表示对象没实例化后,可写 注意:self代表类的实例,而非类 类的方法与Py函数有一个特别的区别——类方法必须有一个额外的第一个参数名称, 按照惯例它的名称是 self class Test: def prt(self): print(self) print(self.__class__) t = Test() t.prt() 从执行结果可以很明显的看出:self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类。 self 不是 python 关键字,我们把他换成 runoob 也是可以正常执行的: d、对象的销毁 又称垃圾回收。Python 使用了引用计数这一简单技术来跟踪和回收垃圾,它内部记录着所有使用中的对象各有多少引用 原理:一个内部跟踪变量,称为一个引用计数器。 1)当对象被创建时, 就创建了一个引用计数;2)当这个对象不再需要时, 也就是说, 这个对象的引用计数变为0 时, 它就会被垃圾回收 但是回收不是"立即"的, 由解释器在适当的时机,将垃圾对象占用的内存空间回收 垃圾回收机制不仅针对引用计数为0的对象,同样也可以处理循环引用的情况。 循环引用指的是,两个对象相互引用,但是没有其他变量引用他们。 这种情况下,Python 的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器。 作为引用计数的补充,垃圾收集器也会留心被分配的总量很大的对象(及未通过引用计数销毁的那些)。 在这种情况下, 解释器会暂停下来, 试图清理所有未引用的循环 析构函数 del ,del在对象销毁的时候被调用,当对象不再被使用时,del方法运行 所以一个完整的类被定义为: class Point: def __init__( self, x=0, y=0): self.x = x self.y = y def __del__(self): class_name = self.__class__.__name__ print class_name, "销毁" 6、类实继承 面向对象的编程带来的主要好处之一是代码重用,因为类往往能封装一些功能,实现这种重用的方法之一是通过继承机制。 继承完全可以理解成类之间的类型和子类型关系。需要注意的地方: 继承语法 : class 派生类名(基类名): //... 基类名写在括号里,基类是在类定义的时候,在元组之中指明的 在python中继承中的一些特点: 1:在继承中基类的构造(init()方法)不会被自动调用,它需要在其派生类的构造中亲自专门调用2:调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。区别在类中调用普通函数时并不需要带上self参数3:Py总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。 (先在本类中查找调用的方法,找不到才去基类中找)。 如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。 语法:派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示: class SubClassName (ParentClass1[, ParentClass2, ...]): 'Optional class documentation string' class_suite 可以使用issubclass()或者isinstance()方法来检测,类是否继承于某个父类。 issubclass() - 布尔函数判断一个类是另一个类的子类或者子孙类,语法:issubclass(sub,sup)isinstance(obj, Class) 布尔函数如果obj是Class类的实例对象或者是一个Class子类的实例对象则返回true。 类的继承本质: 面向对象的编程思想,主要体现在类的封装;类 ==> 又称为类型定义,正如str、tune、list、int等“类型class”继承为“type”类;换句话说《类的继承,本质上是父类的实例化过程》,推理出类型class,也是实例对象 那么: Class A(B) ; 类A,继承 类B ==> 类A,是类B的实例化对象,即A,也是对象;(主要是Type的对象) 此时,解释器立刻执行 Type.__init__(self, *args, **kwargs),目的是初始化对象A,也就是类A obj = A() ; 实例化类A,生成真正想要的对象obj ==> 就是对象A的方法调用,A() ,此刻导致 类A的父类Type.__call__(self, *args, **kwargs)特殊方法的执行,这里面又会呈现如下调用: >a) self.__new__(self, *args, **kwargs) 方法, ==》这是什么,这是类B的实例化对象A的__new__()函数 >b) self.__init__(self, *argv, **kwargs) 方法,==》这是什么,这是类B的实例化对象A的__init__()函数 由此可见: 创建对象obj时,通过类A的父类Type.call()方法,依次执行了两步: 1)使用A.new()生成一下新对象2)使用A.init()初始化这个新对象 那么Class A(B)这个步骤,其实也会再次递归到B的继承,B.new()方法和B.init()方法的执行 7、方法重写 如果父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法: #!/usr/bin/python # -*- coding: UTF-8 -*- class Parent: # 定义父类 def myMethod(self): print '调用父类方法' class Child(Parent): # 定义子类 def myMethod(self): print '调用子类方法' c = Child() # 子类实例 c.myMethod() # 子类调用重写方法 列出了一些通用的功能,你可以在自己的类重写: 1) init ( self [,args...] )构造函数 , 简单的调用方法: obj = className(args) 2) del( self )析构方法, 删除一个对象, 简单的调用方法 : del obj 3) repr( self )转化为供解释器读取的形式,简单的调用方法 : repr(obj) 4) str( self )用于将值转化为适于人阅读的形式,简单的调用方法 : str(obj) 5) cmp ( self, x )对象比较,简单的调用方法 : cmp(obj, x)运算符重载, Python同样支持运算符重载,实例如下: #!/usr/bin/python class Vector: def __init__(self, a, b): self.a = a self.b = b def __str__(self): return 'Vector (%d, %d)' % (self.a, self.b) def __add__(self,other): return Vector(self.a + other.a, self.b + other.b) v1 = Vector(2,10)v2 = Vector(5,-2)print v1 + v2

优秀的个人博客,低调大师

自学提高:JVM点滴

写在前面 这年头就是得不断地学习. 学什么东西就看需要了. 不学习很难进步. 同时别人也会超过你. 东西都是网上有的。图片也好,文字也好。基本都可以在网上找到。 JAVA运行原理 JVM包括字节码解释程序,执行器,方法区,堆,VM栈,本地方法栈,Program Counter计数器。 垃圾回收算法 GC主要分二类,新生代GC,老年代GC; 新生代GC包括:串行GC、并行GC、并行回收GC 老年代GC包括:串行GC、并行GC、CMS G1比较特殊,同时支持新生代和老年代 GC选择: GC在选择上,主要关注两点,吞吐量优先和暂停时间优先, 对于吞吐量优先的采用server默认的并行GC(Parallel GC)方式(上图蓝色区域), 对于暂停时间优先的选用并发GC(CMS)方式(上图黄色区域),常用场景:互联网、电商类 常用GC开启方式 暂停时间优先: 并行GC + CMS 开启方式[ -XX:+UseConcMarkSweepGC -XX:+UseParNewGC ] 吞吐量优先: 并行回收GC + 并行GC 开启方式 [ -XX:+UseParallelOldGC ] ,此GC也时server模式默认的配置 G1: [ -XX:+UseG1GC ] 适用于服务器端、大内存、多CPU情景的垃圾收集器; G1的目标是在维持高效率回收的同时,提供软实时中断特性 常用场景:hadoop、elasticsearch CMS和G1区别 Cms 堆 -> 年轻代老年代 G1 堆 -> 多个区 -> 每个区里(年轻代老年代) Cms 标记清理算法 G1 压缩复制算法,不产生碎片 G1 时间停顿可设置,相关参数[ -XX:MaxGCPauseMillis=100 -XX:GCPauseIntervalMillis=200 ] 需要注意的是,JDK8中已经用Metaspace(元数据区)完全替代了永久代(即方法区) 而且元数据区内存不在JVM中,而是使用的本地内存,默认情况下受操作系统内存限制。 调整元数据区内存大小的参数 -XX:MetaspaceSize -XX:MaxMetaspaceSize

优秀的个人博客,低调大师

(3)Py面向对象提高

几乎任何高级编程语言,均支持面向对象。面向对象属于一种思想,需要程序员为此持续锻炼,巩固。Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的 这里详细介绍Python的面向对象编程,如果以前没有接触过面向对象的编程语言,首先要了解下面向对象的一些基本特征:面向对象技术简介 1、概念定义 类(Class): 用来描述具有相同的属性和方法的对象集合。它定义了该集合中每个对象所共有的属性和方法。 对象(object):通过类定义的数据结构实例。对象包括两个数据成员==属性和方法 实例化:创建一个类的实例,类的具体对象体现,将类通过构造函数转化为实例的过程 2、类的成员 属性:类变量或者实例变量,用于处理类及其实例对象的相关的数据,比如字符串、数组、字典等 类变量:它定义在类中且在方法之外,通常不作为实例变量使用。类变量在整个实例化的对象中是公用的。 实例变量:它定义在方法中指令序列的变量,作用于当前实例。每个实例都有自己的实例变量 方法:类中定义的函数体,即指令序列,往往可以操作实例变量或者完成某个特定的功能 形参:方法名使用的参数,特殊的是self形参,是指类实例指针,构造方法会初始化self,用来指示对象的内存地址 实参:方法体使用的参数,是指令序列可以实际操作和修改的参数,一般用[self.实参]表示 理解self:调用类的构造方法,会初始化self为类实例的对象标识,所以一般也称它为 ==> 类实例指针 3、类的创建 使用 class 语句来创建一个新类,class 之后为类的名称并以冒号结尾: class ClassName: '类的帮助信息' #类文档字符串 class_suite #类体 例子理解 #!/usr/bin/python # -*- coding: UTF-8 -*- class Employee: '所有员工的基类’ empCount = 0 #类变量有点类似全局变量 def __init__(self, name, salary): self.Name = name self.Salary = salary Employee.empCount += 1 def displayCount(self): print "Total Employee %d" % Employee.empCount def displayEmployee(self): print "Name : ", self.name, ", Salary: ", self.salary 1)empCount 是一个类变量,它的值将在类的所有实例之间共享。你可以在内部类或外部类使用 Employee.empCount 访问2)第一种方法init()方法是类的构造函数或实例化方法,当类被实例化为对象时,默认就会调用该方法3)self 代表类的实例,self 在定义类的方法时是必须有的,虽然在调用时不必传入相应的参数。4)构造函数的变量称为局部变量,self.Name才是实例变量 4、类实例化 类的实例化方法:init() 实例化方法:实例化时调用[类()],称为类的构造方法,会初始化[self]为实例标识,然后再调用init()初始化方法初始化方法:执行初始化方法,它的形参也是构造方法的形参,形参传递给实例变量 ==> self.valume形式 核心:实例化方法用来初始化实例变量 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖override也称为方法的重写。即:需要从子类中重新改写方法。如果要调用父亲同名的方法,使用super() 比如: class Father: def bor(self): pass class Son(Father): def bor(self #重写) super(Son, self).bor() #调用父类方法其一 Father.bor(self, ...) #调用父类方法其二 pass 5、类的三大特征 封装:将事物共有的属性或者共有的方法,集成到类里,可以实例化为不同的对象,就称为封装 封装有点类似函数,可以将共同的属性和方法集成一次,以对象标识区分不同的对象,而求同存异 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。可以调用基类的方法 继承也允许把一个派生类的对象作为一个基类对象对待。 多继承:默认的情况下,所有的面向对象编程中,一个派生类只能继承一个基类, 但是再C++和Python中,支持一个派生类继承多个基类;这个时候基类中相同的方法,派生类会按照继承顺序,依次使用方法;但是,如果两个继承基类,又继承同一个根类,那么根类中的方法,最后才执行 多态:Python原生多态,即变量不区分类型 ======================面向对象中高级进阶============================== 6、面向对象的高级知识 a、类的属性字段 1)类的帮助信息可以通过ClassName.doc属性查看2)class_suite 类成员是由,类中的方法,数据属性组成 (其一)数据属性:也叫做字段,或者叫变量 实例变量(普通字段) ==> 实例化方法中初始化的变量,称为实例变量,作用域是对象中,一般形式时self.valume访问方式:必须初始化为对象,通过[对象名.实例变量],定义在self后面 形式变量 ==> 方法名(包括构造方法)的参数列表中的变量,称为形式变量,或者形参,只给编译器使用访问方式:无(执行对象方法时,编译器会将传递的参数给实例变量) 类变量(静态字段) ==> 类方法中初始化的变量,称为类变量,在所有实例化对象之间共享,保存再类中访问方式:[类名.类变量名](也可以通过对象访问,[对象名.类变量名]) b、类的实例化和方法 实例化类其他编程语言中一般用关键字 new,但是在 Python 中类的实例化类似函数调用方式 以下使用类的名称 Employee 来实例化,并通过 init 方法接受参数类方法init() 是类的构造函数或初始化方法,类被实例化时就会调用这个方法 1)"创建 Employee 类的第一个对象" emp1 = Employee("Zara", 2000) 2)"创建 Employee 类的第二个对象" emp2 = Employee("Manni", 5000) 注意:访问属性 1)您可以使用点(.)来访问对象的属性和方法。使用如下类的名称访问类变量: emp1.displayEmployee() emp2.displayEmployee() print "Total Employee %d" % Employee.empCount。 2)你也可以使用以下内嵌函数的方式来访问属性: getattr(obj, name[, default]) : 访问对象的属性。 hasattr(obj,name) : 检查是否存在一个属性。 setattr(obj,name,value) : 设置一个属性。如果属性不存在,会创建一个新属性。 delattr(obj, name) : 删除属性。 注意:执行动作 很清楚:类是由数据成员和方法构成的;数据成员即类属性:方法是类中定义的动作 (其二)方法:也叫做类中的函数 类的实例化方法:init() 实例化方法:实例化类用[类()],将初始化[self]为实例标识(指针)和实例变量内部先调用new()构造方法,然后调用init()初始化方法,传递形参 实例方法: 使用 def 关键字定义在类内部,以self为首形参,并通过实例对象调用的方法。==> 称为实例方法实例方法必须包含参数self,调用有通过[对象名.方法名]执行,往往集成类的实现功能 类方法: 使用 def 关键字定义在类内部,以cls为首形参,并通过类直接调用的方法。 ==> 称为类方法类方法前加:装饰器 @classmethod ,必须包含参数cls,调用通过[类名.类方法]执行 类的静态方法: 使用 def 关键字定义在类内部,不以self和cls为参数,并通过类直接调用的方法。 ==> 称为静态方法静态方法前加:装饰器 @staticmethod ,不使用self、cls作为参数,属于类,调用通过[类名.静态方法]执行 不伦不类的获取属性: 在类的内部,使用 def 关键字定义一个方法。需要方法前加:装饰器 @property ==> 用户获取方法值特殊属性包含参数self,通过对象名.方法名调用,但不需要括号了 不伦不类的设置属性: 在类的内部,使用 def 关键字定义一个方法。需要方法前加:装饰器 @获取方法.setter ==> 用于设置方法值, 特殊属性包含参数 self,通过[对象名.方法名]=value调用 不伦不类的删除属性: 在类的内部,使用 def 关键字定义一个方法。需要方法前加:装饰器 @获取方法.delete ==> 用于删除方法, 特殊属性包含参数 self,通过[del 对象名.方法名]调用,不是真正的删除,只是一种调用方式 然后:================== 很明显:@property 装饰器 ==> 把类中动态方法的访问形式,转化为动态字段的访问方式 比如:调用 emp1.displayEmployee()动态方法调用,可以转化为 emp1.displayEmployee 动态字段调用 还有另一个写法:def bor(): property(fget=bor,fset=xxx, fdel=zzz) ,property方法三个参数,和上面是一样的 c、类的成员修饰符 公有字段 == 能够在类内定义,类外访问的属性或变量 定义:self.value = 'liujian' ; 访问:print(obj.value)默认定义的实例变量、类变量等字段,都是公有字段 私有字段 == 在类内定义,类内访问,类外不可以访问的属性或变量 __private_attrs:两个下划线开头,声明该属性为类的私有字段,不能在类的外部被使用或直接访问。在类内部通过定义方法,使用时return self.__private_attrs。 公有方法 == 能够在类内定义,类外执行的方法或者函数 定义:def func(): pass ; 执行: obj.func()默认定义的动态方法、静态方法等,都是公有方法 私有方法 == 在类内定义、类内调用,类外不可以调用执行的方法 __private_method:两个下划线开头,声明该方法为私有方法,不能在类地外部调用执行。在类的内部调用 self.__private_methods 注意:私有属性 ==> 是指,仅仅能够在定义的类中,访问或者调用的属性,不能继承 Python不允许实例化的类访问私有数据,但你可以使用 object._className__attrName 访问属性, 将如下代码替换以上代码的最后一行代码: ......................... print counter._JustCounter__secretCount 注意:通常你需要在单独的文件中定义一个类 类名()==》 调用init()方法对象名()==》 调用call()方法 特殊的内部方法: def __init__(self): 构造方法,当类被初始化为对象时,自动执行 def __call__(self): 特殊方法,当对象以方法的形式调用时,自动执行,比如obj() def __int__(self): 数值转化方法,直接打印,将对象转化为数字 def __str__(self): 字符串转化方法,直接打印,将对象转化为字符串 def __del__(self): 析构方法,在对象销毁的时候被调用,当对象不再被使用时,__del__方法运行 def __add__(self): 对象之间操作,默认会执行第一个对象obj1.__add__()方法,并传递其他对象为参数other 当两个对象相加时,默认执行第一个对象的__add__()方法,并将第二个对象作为参数传递进去 ==> 在python中,每种类型都是一个类,这种类型的操作支持,都是都是因为类中存在很多特殊方法 <== 比如:list[val] ==> 对应方法如下: def getitem(self, itme) 切片或者索引 ; def set(self, key, value) ; def delitem(self, value) 另外一个重要的特殊修饰符: def iter(self) ==> 执行对象的iter方法,获取一个迭代器对象供给for循环迭代(迭代器是另一个类) 注意:Python内置类属性 __dict__ : 类的自身属性(这是一个字典,由类的数据属性和结构组成) __doc__ :类的文档字符串(主要对类进行说明,或者作者简介) __name__: 类名,本类的名称 __module__: 类定义所在的模块(类的全名是'__main__.className',如果类位于一个导入模块mymod中,那么className.__module__ 等于 mymod) __bases__ : 类的所有父类构成元素(包含了一个由所有父类组成的元组) 单下划线、双下划线、头尾双下划线说明: _foo: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于 from module import * __foo: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了 __foo__: 定义的是特殊方法,一般是系统定义名字 ,类似 __init__() 之类的 类在实例化时,可以传递变量参数;如果类在实例化后,也可给对象赋值 1)默认情况里,对象被实例化后,不可以修改,也就是只读,但是有个前提:继承object基类 2)装饰器 @function_name.setter ,表示对象没实例化后,可写 注意:self代表类的实例,而非类 类的方法与Py函数有一个特别的区别——类方法必须有一个额外的第一个参数名称, 按照惯例它的名称是 self class Test: def prt(self): print(self) print(self.__class__) t = Test() t.prt() 从执行结果可以很明显的看出:self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类。 self 不是 python 关键字,我们把他换成 runoob 也是可以正常执行的: d、对象的销毁 又称垃圾回收。Python 使用了引用计数这一简单技术来跟踪和回收垃圾,它内部记录着所有使用中的对象各有多少引用 原理:一个内部跟踪变量,称为一个引用计数器。 1)当对象被创建时, 就创建了一个引用计数;2)当这个对象不再需要时, 也就是说, 这个对象的引用计数变为0 时, 它就会被垃圾回收 但是回收不是"立即"的, 由解释器在适当的时机,将垃圾对象占用的内存空间回收 垃圾回收机制不仅针对引用计数为0的对象,同样也可以处理循环引用的情况。 循环引用指的是,两个对象相互引用,但是没有其他变量引用他们。 这种情况下,Python 的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器。 作为引用计数的补充,垃圾收集器也会留心被分配的总量很大的对象(及未通过引用计数销毁的那些)。 在这种情况下, 解释器会暂停下来, 试图清理所有未引用的循环 析构函数 del ,del在对象销毁的时候被调用,当对象不再被使用时,del方法运行 所以一个完整的类被定义为: class Point: def __init__( self, x=0, y=0): self.x = x self.y = y def __del__(self): class_name = self.__class__.__name__ print class_name, "销毁" 6、类实继承 面向对象的编程带来的主要好处之一是代码重用,因为类往往能封装一些功能,实现这种重用的方法之一是通过继承机制。 继承完全可以理解成类之间的类型和子类型关系。需要注意的地方: 继承语法 : class 派生类名(基类名): //... 基类名写在括号里,基类是在类定义的时候,在元组之中指明的 在python中继承中的一些特点: 1:在继承中基类的构造(init()方法)不会被自动调用,它需要在其派生类的构造中亲自专门调用2:调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。区别在类中调用普通函数时并不需要带上self参数3:Py总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。 (先在本类中查找调用的方法,找不到才去基类中找)。 如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。 语法:派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示: class SubClassName (ParentClass1[, ParentClass2, ...]): 'Optional class documentation string' class_suite 可以使用issubclass()或者isinstance()方法来检测,类是否继承于某个父类。 issubclass() - 布尔函数判断一个类是另一个类的子类或者子孙类,语法:issubclass(sub,sup)isinstance(obj, Class) 布尔函数如果obj是Class类的实例对象或者是一个Class子类的实例对象则返回true。 类的继承本质: 面向对象的编程思想,主要体现在类的封装;类 ==> 又称为类型定义,正如str、tune、list、int等“类型class”继承为“type”类;换句话说《类的继承,本质上是父类的实例化过程》,推理出类型class,也是实例对象 那么: Class A(B) ; 类A,继承 类B ==> 类A,是类B的实例化对象,即A,也是对象;(主要是Type的对象) 此时,解释器立刻执行 Type.__init__(self, *args, **kwargs),目的是初始化对象A,也就是类A obj = A() ; 实例化类A,生成真正想要的对象obj ==> 就是对象A的方法调用,A() ,此刻导致 类A的父类Type.__call__(self, *args, **kwargs)特殊方法的执行,这里面又会呈现如下调用: >a) self.__new__(self, *args, **kwargs) 方法, ==》这是什么,这是类B的实例化对象A的__new__()函数 >b) self.__init__(self, *argv, **kwargs) 方法,==》这是什么,这是类B的实例化对象A的__init__()函数 由此可见: 创建对象obj时,通过类A的父类Type.call()方法,依次执行了两步: 1)使用A.new()生成一下新对象2)使用A.init()初始化这个新对象 那么Class A(B)这个步骤,其实也会再次递归到B的继承,B.new()方法和B.init()方法的执行 7、方法重写 如果父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法: #!/usr/bin/python # -*- coding: UTF-8 -*- class Parent: # 定义父类 def myMethod(self): print '调用父类方法' class Child(Parent): # 定义子类 def myMethod(self): print '调用子类方法' c = Child() # 子类实例 c.myMethod() # 子类调用重写方法 列出了一些通用的功能,你可以在自己的类重写: 1) init ( self [,args...] )构造函数 , 简单的调用方法: obj = className(args) 2) del( self )析构方法, 删除一个对象, 简单的调用方法 : del obj 3) repr( self )转化为供解释器读取的形式,简单的调用方法 : repr(obj) 4) str( self )用于将值转化为适于人阅读的形式,简单的调用方法 : str(obj) 5) cmp ( self, x )对象比较,简单的调用方法 : cmp(obj, x)运算符重载, Python同样支持运算符重载,实例如下: #!/usr/bin/python class Vector: def __init__(self, a, b): self.a = a self.b = b def __str__(self): return 'Vector (%d, %d)' % (self.a, self.b) def __add__(self,other): return Vector(self.a + other.a, self.b + other.b) v1 = Vector(2,10)v2 = Vector(5,-2)print v1 + v2

优秀的个人博客,低调大师

如何提高ElasticSearch 索引速度

我Google了下,大致给出的答案如下: 使用bulk API 初次索引的时候,把 replica 设置为 0 增大 threadpool.index.queue_size 增大 indices.memory.index_buffer_size 增大 index.translog.flush_threshold_ops 增大 index.translog.sync_interval 增大 index.engine.robin.refresh_interval 这篇文章会讲述上面几个参数的原理,以及一些其他的思路。这些参数大体上是朝着两个方向优化的: 减少磁盘写入 增大构建索引处理资源 一般而言,通过第二种方式的需要慎用,会对集群查询功能造成比较大的影响。这里还有两种形态的解决方案: 关闭一些特定场景并不需要的功能,比如Translog或者Version等 将部分计算挪到其他并行计算框架上,比如数据的分片计算等,都可以放到Spark上事先算好 上面的参数都和什么有关 其中 5,6 属于 TransLog 相关。 4 则和Lucene相关 3 则因为ES里大量采用线程池,构建索引的时候,是有单独的线程池做处理的 7 的话个人认为影响不大 2 的话,能够使用上的场景有限。个人认为Replica这块可以使用Kafka的ISR机制。所有数据还是都从Primary写和读。Replica尽量只作为备份数据。 Translog 为什么要有Translog? 因为Translog顺序写日志比构建索引更高效。我们不可能每加一条记录就Commit一次,这样会有大量的文件和磁盘IO产生。但是我们又想避免程序挂掉或者硬件故障而出现数据丢失,所以有了Translog,通常这种日志我们叫做Write Ahead Log。 为了保证数据的完整性,ES默认是每次request结束后都会进行一次sync操作。具体可以查看如下方法: org.elasticsearch.action.bulk.TransportShardBulkAction.processAfter 该方法会调用IndexShard.sync 方法进行文件落地。 你也可以通过设置index.translog.durability=async 来完成异步落地。这里的异步其实可能会有一点点误导。前面是每次request结束后都会进行sync,这里的sync仅仅是将Translog落地。而无论你是否设置了async,都会执行如下操作: 根据条件,主要是每隔sync_interval(5s) ,如果flush_threshold_ops(Integer.MAX_VALUE),flush_threshold_size(512m),flush_threshold_period(30m) 满足对应的条件,则进行flush操作,这里除了对Translog进行Commit以外,也对索引进行了Commit。 所以如果你是海量的日志,可以容忍发生故障时丢失一定的数据,那么完全可以设置,index.translog.durability=async,并且将前面提到的flush*相关的参数调大。 而极端情况,你还可以有两个选择: 设置index.translog.durability=async,接着设置index.translog.disable_flush=true进行禁用定时flush。然后你可以通过应用程序自己手动来控制flush。 通过改写ES 去掉Translog日志相关的功能 当然,如果去掉Translog日志有两个风险点: Get最新数据会有点问题。因为根据ID Get最新数据是从Translog里拿的。 我们知道ES通过Shard Replication 保证Node节点出现故障时出现数据的完整性。在Relocating的时候,Replica 从Primary 进行Recover时,Primary会先Snapshot Lucene,然后拷贝数据到Replica,最后通过回放Translog 保证数据的一致性。 Version Version可以让ES实现并发修改,但是带来的性能影响也是极大的,这里主要有两块: 需要访问索引里的版本号,触发磁盘读写 锁机制 目前而言,似乎没有办法直接关闭Version机制。你可以使用自增长ID并且在构建索引时,index 类型设置为create。这样可以跳过版本检查。 这个场景主要应用于不可变日志导入,随着ES被越来越多的用来做日志分析,日志没有主键ID,所以使用自增ID是合适的,并且不会进行更新,使用一个固定的版本号也是合适的。而不可变日志往往是追求吞吐量。 当然,如果有必要,我们也可以通过改写ES相关代码,禁用版本管理。 分发代理 ES是对索引进行了分片(Shard),然后数据被分发到不同的Shard。这样 查询和构建索引其实都存在一个问题: 如果是构建索引,则需要对数据分拣,然后根据Shard分布分发到不同的Node节点上。如果是查询,则对外提供的Node需要收集各个Shard的数据做Merge 这都会对对外提供的节点造成较大的压力,从而影响整个bulk/query 的速度。 一个可行的方案是,直接面向客户提供构建索引和查询API的Node节点都采用client模式,不存储数据,可以达到一定的优化效果。 另外一个较为麻烦但似乎会更优的解决方案是,如果你使用类似Spark Streaming这种流式处理程序,在最后往ES输出的时候,可以做如下几件事情: 获取所有primary shard的信息,并且给所有shard带上一个顺序的数字序号,得到partition(顺序序号) -> shardId的映射关系 对数据进行repartition,分区后每个partition对应一个shard的数据 遍历这些partions,写入ES。方法为直接通过RPC 方式,类似transportService.sendRequest 将数据批量发送到对应包含有对应ShardId的Node节点上。 这样有三点好处: 所有的数据都被直接分到各个Node上直接处理。避免所有的数据先集中到一台服务器 避免二次分发,减少一次网络IO 防止最先处理数据的Node压力太大而导致木桶短板效应 场景 因为我正好要做日志分析类的应用,追求高吞吐量,这样上面的三个优化其实都可以做了。一个典型只增不更新的日志入库操作,可以采用如下方案: 对接Spark Streaming,在Spark里对数据做好分片,直接推送到ES的各个节点 禁止自动flush操作,每个batch 结束后手动flush。 避免使用Version 我们可以预期ES会产生多少个新的Segment文件,通过控制batch的周期和大小,预判出ES Segment索引文件的生成大小和Merge情况。最大可能减少ES的一些额外消耗 总结 大体是下面这三个点让es比原生的lucene吞吐量下降了不少: 为了数据完整性 ES额外添加了WAL(tanslog) 为了能够并发修改 添加了版本机制 对外提供服务的node节点存在瓶颈 ES的线性扩展问题主要受限于第三点,具体描述就是: 如果是构建索引,接受到请求的Node节点需要对数据分拣,然后根据Shard分布分发到不同的Node节点上。如果是查询,则对外提供的Node需要收集各个Shard的数据做Merge 另外,索引的读写并不需要向Master汇报。

优秀的个人博客,低调大师

IEJoin: 提高 Databend range join 性能

作者:王旭东 Databend 研发工程师 https://github.com/xudong963 IEJoin 算法可以高效的处理时序场景中的 Range(ASOF) Join。 Join conditions Equi condition 在 下面 SQL 中 SELECT * FROM employee JOIN department ON employee.DepartmentID = department.DepartmentID AND employee.ID = department.ID; employee.DepartmentID = department.DepartmentID OR employee.ID = department.ID 都是 equi-condition,它们用 AND 连接,这条 SQL 被称为 equi-join。 Non-equi condition condition 可以是任意的 bool 表达式,不局限于 = 和 AND。这类 condition 被称为 non-equi condition, 进一步可以细分为 Range condition 和 Other condition。 Range condition 范围比较,如 employee.DepartmentID > department.DepartmentID 就是 range condition, 这类 condition 在时序场景中非常常见。 Other condition 除了 Range condition 的其他各种奇奇怪怪的 contition, 可以被归为 Other condition, 如 OR 连接的 condition, employee.DepartmentID = department.DepartmentID OR employee.ID = department.ID。 Join condition → Join algorithm 在 Databend 中,我们根据 join condition 的类别选择不同的 join 算法,使 join 能够最高效。 如果包含 equi condition,选择 hash join (即使还包含其他类型的 condition ),hash join 可以高效的利用 equi condition 过滤到一定数量的数据,剩下的数据再利用其他 condition 过滤。 如果至少两个 IE condition,选择 IEJoin,一般数据库会使用 Nested Loop Joins,非常的低效。 如果只有一个 IE condition,选择 merge join。 什么是 IEJoin,它有什么黑魔法? IEJoin 将 join keys 涉及到的 columns 放到 sorted arrays 中,利用 permutation array 来记录一个 sorted array 中 tuples 相对于另一个 sorted array 的位置,通过 bit array 来高效的计算符合两个 IE conditions 的 tuples 的交集。 IEJoin 在整体 pipeline 架构上的设计 IEJoin 算法 mysql> select * from east; +------+------+------+-------+ | id | dur | rev | cores | +------+------+------+-------+ | 101 | 100 | 12 | 8 | | 102 | 90 | 5 | 4 | | 100 | 140 | 12 | 2 | +------+------+------+-------+ mysql> select * from west; +------+------+------+-------+ | t_id | time | cost | cores | +------+------+------+-------+ | 404 | 100 | 6 | 4 | | 498 | 140 | 11 | 2 | | 676 | 80 | 10 | 1 | | 742 | 90 | 5 | 4 | +------+------+------+-------+ SELECT east.id, west.id FROM east, west WHERE east.dur < west.time AND east.rev > west.cost 这条 SQL 在大多数数据库中都会被按照 Cross join 处理(如果数据规模很大,甚至会直接 OOM ),但是如果用 IEjoin 算法来处理,速度会得到数量级的提升 🚀 为了便于理解,首先看一条 SelfJoin 的例子 SELECT s1.t_id, s2.t_id FROM west s1, west s2 WHERE s1.time > s2.time AND s1.cost < s2.cost 对 time 列递增排序,得到 L1 对 cost 列递增排序,得到 L2 通过 L1 和 L2 可以得到 permutation array(P),P 记录了 L2 中 tuple id 在 L1 中位置 如:T4 在 L2 中的位置是1,对应到 L1 是2,所以 P 的第一个元素是 2。 初始化 bit-array,bit-array 是基于 L1 的,初始时全部为 0。 对于 L2,后 visit 的 cost 大于先 visit 的 cost,即满足 s1.cost < s2.cost,如先访问 T4,则 bit-array 的第二个元素被设置为 1,再访问 T3 的时候,对应 bit-array 中第一个元素,它后面的第二个元素已经被设置为 1,说明 T4.cost < T3.cost,则 {T4, T3} 符合条件 s1.cost < s2.cost 对于 L1,由于 bit-array 是基于 L1 的,所以如果 bit-array 中某个位置之后的位置被设置为 1,则表明后面设为 1 的位置的 tuple id 满足 s1.time > s2.time, 如 T1 对应的位置设为 1,当访问 T3 时,T1.time > T3.time,则 {T1, T3} 符合条件 s1.time > s2.time。 bit-array 的作用就是通过标记,来找到同时满足两个 IE conditions 的 tuple pair。 算法流程 遍历 P, P[1] 对应 T4,T4 在 L1 中的位置是 P[1] = 2,将 bit-array 的第二个位置设为 1,由于其后位置都为 0,所以没有满足条件的结果。 P[2] 对应 T1,T1 在 L1 中的位置是 P[1] = 3,将 bit-array 的第三个位置设为 1,由于其后位置都为 0,所以没有满足条件的结果。 P[3] 对应 T3,T3 在 L1 中的位置是 P[3] = 1,将 bit-array 的第一个位置设为1,由于 bit-array 的第二/三位置都为 1,所以 {T1, T3}, {T4, T3} 满足条件。 P[4] 对应 T2,T2 在 L1 中的位置是 P[4] = 4,将 bit-array 的第四个位置设为 1,由于它是最后一个位置,所以没有满足条件的结果。 由 SelfJoin 拓展到不同表 join SELECT east.id, west.id FROM east, west WHERE east.dur < west.time AND east.rev > west.cost 对 dur 排序得到 L1,对 rev 排序得到 L2。 L2 和 L1 比较得到 P。 对 time 排序得到 L1’,对 cost 排序得到 L2’。 L2’ 和 L1’ 比较得到 P’。 将 L1 和 L1’ 合并排序,L2 和 L2’ 合并排序,合并 P 和 P’。 最终得到了合 SelfJoin 类似的数据结构,可以应用 SelfJoin 的算法流程,但是需要处理掉重复的结果。 性能数据 M1 mac (10 core, 32G) SQL: select count() from lineitem join orders on l_orderkey > o_orderkey and l_partkey < o_custkey; TPCH SF 0.01 IEJoin: 0.974s, Cross join: 16.639s TPCH SF 0.1 IEJoin: 79.085s, Cross join: OOM Connect With Us Databend 是一款开源、弹性、低成本,基于对象存储也可以做实时分析的新式数仓。期待您的关注,一起探索云原生数仓解决方案,打造新一代开源 Data Cloud。 Databend Website GitHub Discussions Twitter Slack Channel

优秀的个人博客,低调大师

Python规范:提高可读性

PEP 8 规范 PEP 是 Python Enhancement Proposal 的缩写,翻译过来叫“Python 增强规范”。 缩进规范 PEP 8 规范告诉我们,请选择四个空格的缩进,不要使用 Tab,更不要 Tab 和空格混着用。 第二个要注意的是,每行最大长度请限制在 79 个字符。 空行规范 PEP 8 规定,全局的类和函数的上方需要空两个空行,而类的函数之间需要空一个空行。 空格规范 函数的参数列表中,调用函数的参数列表中会出现逗号,请注意逗号后要跟一个空格,这是英语的使用习惯,也能让每个参数独立阅读,更清晰。 冒号后面也要跟一个空格。 在#后、注释前加一个空格。 操作符,例如+,-,*,/,&,|,=,==,!=,请在两边都保留空格。不过与此对应,括号内的两端并不需要空格。 换行规范 控制每行的最大长度不超过 79 个字符,但是有时候,函数调用逻辑过长而不得不超过这个数字时按以下规范: def solve1(this_is_the_first_parameter, this_is_the_second_parameter, this_is_the_third_parameter, this_is_the_forth_parameter, this_is_the_fifth_parameter, this_is_the_sixth_parameter): return (this_is_the_first_parameter + this_is_the_second_parameter + this_is_the_third_parameter + this_is_the_forth_parameter + this_is_the_fifth_parameter + this_is_the_sixth_parameter) def solve2(this_is_the_first_parameter, this_is_the_second_parameter, this_is_the_third_parameter, this_is_the_forth_parameter, this_is_the_fifth_parameter, this_is_the_sixth_parameter): return this_is_the_first_parameter + this_is_the_second_parameter + this_is_the_third_parameter + \ this_is_the_forth_parameter + this_is_the_fifth_parameter + this_is_the_sixth_parameter (top_secret_func(param1=12345678, param2=12345678, param3=12345678, param4=12345678, param5=12345678).check() .launch_nuclear_missile().wait()) top_secret_func(param1=12345678, param2=12345678, param3=12345678, param4=12345678, param5=12345678).check() \ .launch_nuclear_missile().wait() 1.通过括号来将过长的运算进行封装. 2.通过换行符来实现. 文档规范 import 尽量放在开头. 不要使用 import 一次导入多个模块. from module import func 这样的语句,请确保 func 在本文件中不会出现命名冲突。或者通过 from module import func as new_func 来进行重命名,从而避免冲突。 注释规范 行注释并不是很推荐的方式。 文档描述 docstring 的写法,它是用三个双引号开始、三个双引号结尾。我们首先用一句话简单说明这个函数做什么,然后跟一段话来详细解释;再往后是参数列表、参数格式、返回值格式。 class SpatialDropout2D(Dropout): """Spatial 2D version of Dropout. This version performs the same function as Dropout, however it drops entire 2D feature maps instead of individual elements. If adjacent pixels within feature maps are strongly correlated (as is normally the case in early convolution layers) then regular dropout will not regularize the activations and will otherwise just result in an effective learning rate decrease. In this case, SpatialDropout2D will help promote independence between feature maps and should be used instead. Arguments: rate: float between 0 and 1. Fraction of the input units to drop. data_format: 'channels_first' or 'channels_last'. In 'channels_first' mode, the channels dimension (the depth) is at index 1, in 'channels_last' mode is it at index 3. It defaults to the `image_data_format` value found in your Keras config file at `~/.keras/keras.json`. If you never set it, then it will be "channels_last". Input shape: 4D tensor with shape: `(samples, channels, rows, cols)` if data_format='channels_first' or 4D tensor with shape: `(samples, rows, cols, channels)` if data_format='channels_last'. Output shape: Same as input References: - [Efficient Object Localization Using Convolutional Networks](https://arxiv.org/abs/1411.4280) """ def __init__(self, rate, data_format=None, **kwargs): super(SpatialDropout2D, self).__init__(rate, **kwargs) if data_format is None: data_format = K.image_data_format() if data_format not in {'channels_last', 'channels_first'}: raise ValueError('data_format must be in ' '{"channels_last", "channels_first"}') self.data_format = data_format self.input_spec = InputSpec(ndim=4) 命名规范 变量使用小写,通过下划线串联起来,例如:data_format、input_spec、image_data_set。唯一可以使用单字符的地方是迭代,比如 for i in range(n) 这种,为了精简可以使用。如果是类的私有变量,请记得前面增加两个下划线。 常量,最好的做法是全部大写,并通过下划线连接,例如:WAIT_TIME、SERVER_ADDRESS、PORT_NUMBER。 函数名,同样也请使用小写的方式,通过下划线连接起来,例如:launch_nuclear_missile()、check_input_validation()。 类名,则应该首字母大写,然后合并起来,例如:class SpatialDropout2D()、class FeatureSet()。 代码分解技巧 不写重复代码。 如: if i_am_rich: money = 100 send(money) else: money = 10 send(money) 都有send函数,可改为: if i_am_rich: money = 100 else: money = 10 send(money) 代码嵌套过深: #Python学习交流QQ群:857662006 def send(money): if is_server_dead: LOG('server dead') return else: if is_server_timed_out: LOG('server timed out') return else: result = get_result_from_server() if result == MONEY_IS_NOT_ENOUGH: LOG('you do not have enough money') return else: if result == TRANSACTION_SUCCEED: LOG('OK') return else: LOG('something wrong') return 可改为: def send(money): if is_server_dead: LOG('server dead') return if is_server_timed_out: LOG('server timed out') return result = get_result_from_server() if result == MONET_IS_NOT_ENOUGH: LOG('you do not have enough money') return if result == TRANSACTION_SUCCEED: LOG('OK') return LOG('something wrong') 以一个简单的二分搜索来举例说明。给定一个非递减整数数组,和一个 target,要求找到数组中最小的一个数 x,可以满足 x*x > target。一旦不存在,则返回 -1。 代码实现如果如下所示,那么可以再以一个函数只干一件事情的原则再优化下。 def solve(arr, target): l, r = 0, len(arr) - 1 ret = -1 while l <= r: m = (l + r) // 2 if arr[m] * arr[m] > target: ret = m r = m - 1 else: l = m + 1 if ret == -1: return -1 else: return arr[ret] print(solve([1, 2, 3, 4, 5, 6], 8)) print(solve([1, 2, 3, 4, 5, 6], 9)) print(solve([1, 2, 3, 4, 5, 6], 0)) print(solve([1, 2, 3, 4, 5, 6], 40)) 优化如下: def comp(x, target): return x * x > target def binary_search(arr, target): l, r = 0, len(arr) - 1 ret = -1 while l <= r: m = (l + r) // 2 if comp(arr[m], target): ret = m r = m - 1 else: l = m + 1 return ret def solve(arr, target): id = binary_search(arr, target) if id != -1: return arr[id] return -1 print(solve([1, 2, 3, 4, 5, 6], 8)) print(solve([1, 2, 3, 4, 5, 6], 9)) print(solve([1, 2, 3, 4, 5, 6], 0)) print(solve([1, 2, 3, 4, 5, 6], 40)) 类中属性很多时可以抽出相同特性的单独作为类,如: class Person: def __init__(self, name, sex, age, job_title, job_description, company_name): self.name = name self.sex = sex self.age = age self.job_title = job_title self.job_description = description self.company_name = company_name job_title , job_description , company_name 都与工作有关,表达是同一个意义实体,就可以抽出单独作为类: class Person: def __init__(self, name, sex, age, job_title, job_description, company_name): self.name = name self.sex = sex self.age = age self.job = Job(job_title, job_description, company_name) class Job: def __init__(self, job_title, job_description, company_name): self.job_title = job_title self.job_description = description self.company_name = company_name

优秀的个人博客,低调大师

Android使用ViewStub提高布局性能

在Android开发中,View是我们必须要接触的用来展示的技术.通常情况下随着View视图的越来越复杂,整体布局的性能也会随之下降.这里介绍一个在某些场景下提升布局性能的View,它就是ViewStub. ViewStub是什么 ViewStub是View的子类 它不可见,大小为0 用来延迟加载布局资源 注,关于Stub的解释 A stub is a small program routine that substitutes for a longer program, possibly to be loaded later or that is located remotely 在Java中,桩是指用来代替关联代码或者未实现代码的代码. ViewStub使用场景 如上图所示, 一个ListView包含了诸如 新闻,商业,科技 等Item 每个Item又包含了各自对应的子话题, 但是子话题的View(蓝色区域)只有在点击展开按钮才真正需要加载. 如果默认加载子话题的View,则会造成内存的占用和CPU的消耗 所以,这时候就ViewStub就派上用处了.使用ViewStub可以延迟加载布局资源. ViewStub 怎么用 1.在布局文件中使用ViewStub标签 <?xmlversion="1.0"encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context="com.droidyue.viewstubsample.MainActivity"> <Button android:id="@+id/clickMe" android:text="HelloWorld!" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <ViewStub android:id="@+id/myViewStub" android:inflatedId="@+id/myInflatedViewId" android:layout="@layout/include_merge" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/clickMe" /> </RelativeLayout> 2.在代码中inflate布局 ViewStubmyViewStub=(ViewStub)findViewById(R.id.myViewStub); if(myViewStub!=null){ myViewStub.inflate(); //或者是下面的形式加载 //myViewStub.setVisibility(View.VISIBLE); } 关于ViewStub的事 除了 inflate 方法外,我们还可以调用 setVisibility() 方法加载布局文件 一旦加载布局完成后,ViewStub会从当前布局层级中删除 android:id 指定ViewStub ID,用于查找ViewStub进行延迟加载 android:layout 延迟加载布局的资源id android:inflatedId 加载的布局被重写的id,这里为RelativeLayout的id ViewStub的不足 官方的文档中有这样一段描述 Note: One drawback of ViewStub is that it doesn’t currently support the tag in the layouts to be inflated. 意思是ViewStub不支持标签. 关于不支持标签的程度,我们进行一个简单的验证 验证一:直接 标签 如下,我们有布局文件名为merge_layout.xml <mergexmlns:android="http://schemas.android.com/apk/res/android"> <Buttonandroid:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Yes"/> <Buttonandroid:layout_width="fill_parent" android:layout_height="wrap_content" android:text="No"/> </merge> 替换对应的ViewStub的android:layout属性值之后,运行后(点击Button按钮)得到产生了如下的崩溃 EAndroidRuntime:android.view.InflateException:BinaryXMLfileline#1:<merge/>canbeusedonlywithavalidViewGrouprootandattachToRoot=true EAndroidRuntime:atandroid.view.LayoutInflater.inflate(LayoutInflater.java:551) EAndroidRuntime:atandroid.view.LayoutInflater.inflate(LayoutInflater.java:429) EAndroidRuntime:atandroid.view.ViewStub.inflate(ViewStub.java:259) EAndroidRuntime:atcom.droidyue.viewstubsample.MainActivity$1.onClick(MainActivity.java:20) EAndroidRuntime:atandroid.view.View.performClick(View.java:5697) EAndroidRuntime:atandroid.widget.TextView.performClick(TextView.java:10815) EAndroidRuntime:atandroid.view.View$PerformClick.run(View.java:22526) EAndroidRuntime:atandroid.os.Handler.handleCallback(Handler.java:739) EAndroidRuntime:atandroid.os.Handler.dispatchMessage(Handler.java:95) EAndroidRuntime:atandroid.os.Looper.loop(Looper.java:158) EAndroidRuntime:atandroid.app.ActivityThread.main(ActivityThread.java:7237) EAndroidRuntime:atjava.lang.reflect.Method.invoke(NativeMethod) EAndroidRuntime:atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230) EAndroidRuntime:atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120) EAndroidRuntime:Causedby:android.view.InflateException:<merge/>canbeusedonlywithavalidViewGrouprootandattachToRoot=true EAndroidRuntime:atandroid.view.LayoutInflater.inflate(LayoutInflater.java:491) EAndroidRuntime:...13more 可见,直接的标签,ViewStub是不支持的. 验证二 间接的ViewStub 下面布局间接使用了merge标签.文件名为 include_merge.xml <?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <includelayout="@layout/merge_layout"/> </LinearLayout> 然后修改ViewStub的 android:layout 值,运行,一切正常. 除此之外,本例也验证了ViewStub也是对标签支持良好的. 关于ViewStub的一点代码剖析 inflate vs setVisibility inflate和setVisibility的共同点是都可以实现加载布局 /***Whenvisibilityissetto{@link#VISIBLE}or{@link#INVISIBLE}, *{@link#inflate()}isinvokedandthisStubbedViewisreplacedinitsparent *bytheinflatedlayoutresource. * *@paramvisibilityOneof{@link#VISIBLE},{@link#INVISIBLE},or{@link#GONE}. * *@see#inflate() */ @Override publicvoidsetVisibility(intvisibility){ if(mInflatedViewRef!=null){ Viewview=mInflatedViewRef.get(); if(view!=null){ view.setVisibility(visibility); }else{ thrownewIllegalStateException("setVisibilitycalledonun-referencedview"); } }else{ super.setVisibility(visibility); if(visibility==VISIBLE||visibility==INVISIBLE){ inflate(); } } } setVisibility只是在ViewStub第一次延迟初始化时,并且visibility是非 GONE 时,调用了 inflate 方法. inflate源码 通过阅读下面的inflate方法实现,我们将更加理解 android:inflatedId的用途 ViewStub在初始化后从视图层级中移除 ViewStub的layoutParameters应用 mInflatedViewRef通过弱引用形式,建立ViewStub与加载的View的联系. /***Inflatesthelayoutresourceidentifiedby{@link#getLayoutResource()} *andreplacesthisStubbedViewinitsparentbytheinflatedlayoutresource. * *@returnTheinflatedlayoutresource. * */ publicViewinflate(){ finalViewParentviewParent=getParent(); if(viewParent!=null&&viewParentinstanceofViewGroup){ if(mLayoutResource!=0){ finalViewGroupparent=(ViewGroup)viewParent; finalLayoutInflaterfactory=LayoutInflater.from(mContext); finalViewview=factory.inflate(mLayoutResource,parent, false); if(mInflatedId!=NO_ID){ view.setId(mInflatedId); } finalintindex=parent.indexOfChild(this); parent.removeViewInLayout(this); finalViewGroup.LayoutParamslayoutParams=getLayoutParams(); if(layoutParams!=null){ parent.addView(view,index,layoutParams); }else{ parent.addView(view,index); } mInflatedViewRef=newWeakReference<View>(view); if(mInflateListener!=null){ mInflateListener.onInflate(this,view); } returnview; }else{ thrownewIllegalArgumentException("ViewStubmusthaveavalidlayoutResource"); } }else{ thrownewIllegalStateException("ViewStubmusthaveanon-nullViewGroupviewParent"); } } 本文作者:佚名 来源:51CTO

优秀的个人博客,低调大师

码农如何提高自己的品味

作者:京东科技 文涛 前言 软件研发工程师俗称程序员经常对业界外的人自谦作码农,一来给自己不菲的收入找个不错的说辞(像农民伯伯那样辛勤耕耘挣来的血汗钱),二来也是自嘲这个行业确实辛苦,辛苦得没时间捯饬,甚至没有驼背、脱发加持都说不过去。不过时间久了,行外人还真就相信了程序员就是一帮没品味,木讷的low货,大部分的文艺作品中也都是这么表现程序员的。可是我今天要说一下我的感受,编程是个艺术活,程序员是最聪明的一群人,我们的品味也可以像艺术家一样。 言归正转,你是不是以为我今天要教你穿搭?不不不,这依然是一篇技术文章,想学穿搭女士学陈舒婷(《狂飙》中的大嫂),男士找陈舒婷那样的女朋友就好了。笔者今天教你怎样有“品味”的写代码。  以下几点可提升“品味” 说明:以下是笔者的经验之谈具有部分主观性,不赞同的欢迎拍砖,要想体系化提升编码功底建议读《XX公司Java编码规范》、《Effective Java》、《代码整洁之道》。以下几点部分具有通用性,部分仅限于java语言,其它语言的同学绕过即可。 优雅防重 关于成体系的防重讲解,笔者之后打算写一篇文章介绍,今天只讲一种优雅的方式: 如果你的业务场景满足以下两个条件: 1 业务接口重复调用的概率不是很高 2 入参有明确业务主键如:订单ID,商品ID,文章ID,运单ID等 在这种场景下,非常适合乐观防重,思路就是代码处理不主动做防重,只在监测到重复提交后做相应处理。 如何监测到重复提交呢?MySQL唯一索引 + org.springframework.dao.DuplicateKeyException 代码如下: public int createContent(ContentOverviewEntity contentEntity) { try{ return contentOverviewRepository.createContent(contentEntity); }catch (DuplicateKeyException dke){ log.warn("repeat content:{}",contentEntity.toString()); } return 0; }  用好lambda表达式 lambda表达式已经是一个老生常谈的话题了,笔者认为,初级程序员向中级进阶的必经之路就是攻克lambda表达式,lambda表达式和面向对象编程是两个编程理念,《架构整洁之道》里曾提到有三种编程范式,结构化编程(面向过程编程)、面向对象编程、函数式编程。初次接触lambda表达式肯定特别不适应,但如果熟悉以后你将打开一个编程方式的新思路。本文不讲lambda,只讲如下例子: 比如你想把一个二维表数据进行分组,可采用以下一行代码实现 List<ActionAggregation> actAggs = .... Map<String, List<ActionAggregation>> collect = actAggs.stream() .collect(Collectors.groupingBy(ActionAggregation :: containWoNosStr,LinkedHashMap::new,Collectors.toList()));  用好卫语句 各个大场的JAVA编程规范里基本都有这条建议,但我见过的代码里,把它用好的不多,卫语句对提升代码的可维护性有着很大的作用,想像一下,在一个10层if 缩进的接口里找代码逻辑是一件多么痛苦的事情,有人说,哪有10层的缩进啊,别说,笔者还真的在一个微服务里的一个核心接口看到了这种代码,该接口被过多的人接手导致了这样的局面。系统接手人过多以后,代码腐化的速度超出你的想像。 下面举例说明: 没有用卫语句的代码,很多层缩进 if (title.equals(newTitle)){ if (...) { if (...) { if (...) { } }else{ } }else{ } } 使用了卫语句的代码,缩进很少 if (!title.equals(newTitle)) { return xxx; } if (...) { return xxx; }else{ return yyy; } if (...) { return zzz; } 避免双重循环 简单说双重循环会将代码逻辑的时间复杂度扩大至O(n^2) 如果有按key匹配两个列表的场景建议使用以下方式: 1 将列表1 进行map化 2 循环列表2,从map中获取值 代码示例如下: List<WorkOrderChain> allPre = ... List<WorkOrderChain> chains = ... Map<String, WorkOrderChain> preMap = allPre.stream().collect(Collectors.toMap(WorkOrderChain::getWoNext, item -> item,(v1, v2)->v1)); chains.forEach(item->{ WorkOrderChain preWo = preMap.get(item.getWoNo()); if (preWo!=null){ item.setIsHead(1); }else{ item.setIsHead(0); } }); 用@see @link来设计RPC的API 程序员们还经常自嘲的几个词有:API工程师,中间件装配工等,既然咱平时写API写的比较多,那种就把它写到极致@see @link的作用是让使用方可以方便的链接到枚举类型的对象上,方便阅读 示例如下: @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ContentProcessDto implements Serializable { /** * 内容ID */ private String contentId; /** * @see com.jd.jr.community.common.enums.ContentTypeEnum */ private Integer contentType; /** * @see com.jd.jr.community.common.enums.ContentQualityGradeEnum */ private Integer qualityGrade; } 日志打印避免只打整个参数 研发经常为了省事,直接将入参这样打印 log.info("operateRelationParam:{}", JSONObject.toJSONString(request)); 该日志进了日志系统后,研发在搜索日志的时候,很难根据业务主键排查问题 如果改进成以下方式,便可方便的进行日志搜索 log.info("operateRelationParam,id:{},req:{}", request.getId(),JSONObject.toJSONString(request)); 如上:只需要全词匹配“operateRelationParam,id:111”,即可找到业务主键111的业务日志。 用异常捕获替代方法参数传递 我们经常面对的一种情况是:从子方法中获取返回的值来标识程序接下来的走向,这种方式笔者认为不够优雅。 举例:以下代码paramCheck和deleteContent方法,返回了这两个方法的执行结果,调用方通过返回结果判断程序走向 public RpcResult<String> deleteContent(ContentOptDto contentOptDto) { log.info("deleteContentParam:{}", contentOptDto.toString()); try{ RpcResult<?> paramCheckRet = this.paramCheck(contentOptDto); if (paramCheckRet.isSgmFail()){ return RpcResult.getSgmFail("非法参数:"+paramCheckRet.getMsg()); } ContentOverviewEntity contentEntity = DozerMapperUtil.map(contentOptDto,ContentOverviewEntity.class); RpcResult<?> delRet = contentEventHandleAbility.deleteContent(contentEntity); if (delRet.isSgmFail()){ return RpcResult.getSgmFail("业务处理异常:"+delRet.getMsg()); } }catch (Exception e){ log.error("deleteContent exception:",e); return RpcResult.getSgmFail("内部处理错误"); } return RpcResult.getSgmSuccess(); } 我们可以通过自定义异常的方式解决:子方法抛出不同的异常,调用方catch不同异常以便进行不同逻辑的处理,这样调用方特别清爽,不必做返回结果判断 代码示例如下: public RpcResult<String> deleteContent(ContentOptDto contentOptDto) { log.info("deleteContentParam:{}", contentOptDto.toString()); try{ this.paramCheck(contentOptDto); ContentOverviewEntity contentEntity = DozerMapperUtil.map(contentOptDto,ContentOverviewEntity.class); contentEventHandleAbility.deleteContent(contentEntity); }catch(IllegalStateException pe){ log.error("deleteContentParam error:"+pe.getMessage(),pe); return RpcResult.getSgmFail("非法参数:"+pe.getMessage()); }catch(BusinessException be){ log.error("deleteContentBusiness error:"+be.getMessage(),be); return RpcResult.getSgmFail("业务处理异常:"+be.getMessage()); }catch (Exception e){ log.error("deleteContent exception:",e); return RpcResult.getSgmFail("内部处理错误"); } return RpcResult.getSgmSuccess(); } 自定义SpringBoot的Banner 别再让你的Spring Boot启动banner千篇一律,spring 支持自定义banner,该技能对业务功能实现没任何卵用,但会给枯燥的编程生活添加一点乐趣。 以下是官方文档的说明: https://docs.spring.io/spring-boot/docs/1.3.8.RELEASE/reference/htmlsingle/#boot-features-banner 另外你还需要ASCII艺术字生成工具: https://tools.kalvinbg.cn/txt/ascii 效果如下: _ _ _ _ _ (_|_)_ __ __ _ __| | ___ _ __ __ _ | |__ ___ ___ | |_ ___ | | | '_ \ / _` | / _` |/ _ \| '_ \ / _` | | '_ \ / _ \ / _ \| __/ __| | | | | | | (_| | | (_| | (_) | | | | (_| | | |_) | (_) | (_) | |_\__ \ _/ |_|_| |_|\__, | \__,_|\___/|_| |_|\__, | |_.__/ \___/ \___/ \__|___/ |__/ |___/ |___/ 多用Java语法糖 编程语言中java的语法是相对繁琐的,用过golang的或scala的人感觉特别明显。java提供了10多种语法糖,写代码常使用语法糖,给人一种 “这哥们java用得通透” 的感觉。 举例:try-with-resource语法,当一个外部资源的句柄对象实现了AutoCloseable接口,JDK7中便可以利用try-with-resource语法更优雅的关闭资源,消除板式代码。 try (FileInputStream inputStream = new FileInputStream(new File("test"))) { System.out.println(inputStream.read()); } catch (IOException e) { throw new RuntimeException(e.getMessage(), e); } 利用链式编程 链式编程,也叫级联式编程,调用对象的函数时返回一个this对象指向对象本身,达到链式效果,可以级联调用。链式编程的优点是:编程性强、可读性强、代码简洁。 举例:假如觉得官方提供的容器不够方便,可以自定义,代码如下,但更建议使用开源的经过验证的类库如guava包中的工具类 /** 链式map */ public class ChainMap<K,V> { private Map<K,V> innerMap = new HashMap<>(); public V get(K key) { return innerMap.get(key); } public ChainMap<K,V> chainPut(K key, V value) { innerMap.put(key, value); return this; } public static void main(String[] args) { ChainMap<String,Object> chainMap = new ChainMap<>(); chainMap.chainPut("a","1") .chainPut("b","2") .chainPut("c","3"); } } 未完,待续,欢迎评论区补充

优秀的个人博客,低调大师

怎么提高自己的系统架构水平

系统设计与架构理论这个问题,回答起来非常宽泛,基本所有的技术理论都可以涵盖。作为一个撸代码快 10 年的后端技术人员,简单发表一下我的看法。 系统设计与架构,与系统的业务类型关联还是很大的,比如传统的业务系统主要关注的是领域建模设计,高并发、高可用、数据一致性等系统,在设计的时候会与业务系统有较大的差别,所以这里针对不同类型的系统,来简单介绍一下设计的时候面临的一些难点与解决方案。 背景常规业务系统设计关键——领域模型 业务系统设计的关键是在于如何定义系统的模型以及模型之间的关系,其中主要是领域模型的定义,当我们在模型确定之后,模型之间的关系也会随之明确。 模型设计可以参考领域模型的经典书籍《Domain-Driven Design》一书,通过这个基本可以对领域定义、防腐层、贫血模型等概念有一个较为清晰的认识了。 单个应用内的领域模型系统也需要注意领域分层,作为开发大家是不是见过、重构过很多Controller-Service-DAO 样式的代码分层设计?往往在在做重构的时候会令人吐血。 设计较好的领域设计这里给一个分层建议: ▐接口层 Interface 主要负责与外部系统进行交互&通信,比如一些 dubbo服务、Restful API、RMI等,这一层主要包括 Facade、DTO还有一些Assembler。 ▐应用层 Application 这一层包含的主要组件就是 Service 服务,但是要特别注意,这一层的Service不是简单的DAO层的包装,在领域驱动设计的架构里面,Service层只是一层很“薄”的一层,它内部并不实现任何逻辑,只是负责协调和转发、委派业务动作给更下层的领域层。 ▐领域层 Domain Domain 层是领域模型系统的核心,负责维护面向对象的领域模型,几乎全部的业务逻辑都会在这一层实现。内部主要包含Entity(实体)、ValueObject(值对象)、Domain Event(领域事件)和 Repository(仓储)等多种重要的领域组件。 ▐基础设施层 Infrastructure 它主要为 Interfaces、Application 和 Domain 三层提供支撑。所有与具体平台、框架相关的实现会在 Infrastructure 中提供,避免三层特别是 Domain 层掺杂进这些实现,从而“污染”领域模型。Infrastructure 中最常见的一类设施是对象持久化的具体实现。 高并发系统设计 在面试中是不是经常被问到一个问题:如果你系统的流量增加 N 倍你要怎么重新设计你的系统?这个高并发的问题可以从各个层面去解,比如 ▐代码层面 锁优化(采用无锁数据结构),主要是 concurrent 包下面的关于 AQS 锁的一些内容 数据库缓存设计(降低数据库并发争抢压力),这里又会有缓存、DB 数据不一致的问题,在实际使用中,高并发系统和数据一致性系统采用的策略会截然相反。 数据更新时采用合并更新,可以在应用层去做更新合并,同一个 Container 在同一时间只会有一个 DB 更新请求。 其他的比如基于 BloomFilter 的空间换时间、通过异步化降低处理时间、通过多线程并发执行等等。 ▐数据库层面 根据不同的存储诉求来进行不同的存储选型,从早期的 RDBMS,再到 NoSql(KV存储、文档数据库、全文索引引擎等等),再到最新的NewSql(TiDB、Google spanner/F1 DB)等等。 表数据结构的设计,字段类型选择与区别。 索引设计,需要关注聚簇索引原理与覆盖索引消除排序等,至于最左匹配原则都是烂大街的常识了,高级一点索引消除排序的一些机制等等,B+树与B树的区别。 最后的常规手段:分库分表、读写分离、数据分片、热点数据拆分等等,高并发往往会做数据分桶,这里面往深了去说又有很多,比如分桶如何初始化、路由规则、最后阶段怎么把数据合并等等,比较经典的方式就是把桶分成一个主桶+N个分桶。 ▐架构设计层面 分布式系统为服务化 无状态化支持水平弹性扩缩容 业务逻辑层面 failfast 快速失败 调用链路热点数据前置 多级缓存设计 提前容量规划等等 高可用系统设计 对于可用性要求非常高的系统,一般我们都说几个9的可用率,比如 99.999% 等。 面对高可用系统设计也可以从各个方面来进行分析 代码层面:需要关注分布式事务问题,CAP理论是面试的常规套路 软件层面:应用支持无状态化,部署的多个模块完全对等,请求在任意模块处理结果完全一致 => 模块不存储上下文信息,只根据请求携带的参数进行处理。目的是为了快速伸缩,服务冗余。常见的比如session问题等。 ▐负载均衡问题 软件部署多份之后,如何保证系统负载?如何选择调用机器?也就是负载均衡问题 狭义上的负载均衡按照类型可以分为这几种: 硬件负载:比如F5等 软件负载:比如 LVS、Ngnix、HaProxy、DNS等。 当然,还有代码算法上的负载均衡,比如Random、RoundRobin、ConsistentHash、加权轮训等等算法 广义上的负载均衡可以理解为负载均衡的能力,比如一个负载均衡系统需要如下4个能力: 故障机器自动发现 故障服务自动摘除(服务熔断) 请求自动重试 服务恢复自动发现 ▐幂等设计问题 上面提负载均衡的时候,广义负载均衡需要完成自动重试机制,那么在业务上,我们就必须保证幂等设计。 这里可以从2个层面来进行考虑: 请求层面由于请求会重试所以必须做幂等,需要保证请求重复执行和执行一次的结果完全相同。请求层面的幂等设计需要在数据修改的层做幂等,也就是数据访问层读请求天然幂等,写请求需要做幂等。读请求一般是天然幂等的,无论查询多少次返回的结果都是一致。这其中的本质实际上是分布式事务问题,这里下面再详细介绍。 业务层面不幂等会造成诸如奖励多发、重复下单等非常严重的问题。业务层面的幂等本质上是分布式锁的问题,后面会介绍。如何保证不重复下单?这里比如token机制等等。如何保证商品不超卖?比如乐观锁等。MQ消费方如何保证幂等等都是面试的常见题。 ▐分布缩式 业务层面的幂等设计本质上是分布式锁问题,什么是分布式锁?分布式环境下锁的全局唯一资源,使请求串行化,实际表现互斥锁,解决业务层幂等问题。 常见的解决方式是基于 Redis 缓存的 setnx 方法,但作为技术人员应该清楚这其中还存在单点问题、基于超时时间无法续租问题、异步主从同步问题等等,更深一点,CAP理论,一个AP系统本质上无法实现一个AP需求,即使是 RedLock 也不行。 那我们如何去设计一个分布式锁呢?强一致性、服务本身要高可用是最基本的需求,其他的比如支持自动续期,自动释放机制,高度抽象接入简单,可视化、可管理等。 基于存储层的可靠的解决方案比如: zookeeperCP/ZAB/N+1可用:基于临时节点实现和Watch机制。 ETCDCP or AP/Raft/N+1可用:基于 restful API;KV存储,强一致性,高可用,数据可靠:持久化;Client TTL 模式,需要心跳CAS 唯一凭证 uuid。 ▐服务的熔断 微服务化之后,系统分布式部署,系统之间通过 RPC 通讯,整个系统发生故障的概率随着系统规模的增长而增长,一个小的故障经过链路传导放大,有可能造成更大的故障。希望在调用服务的时,在一些非关键路径服务发生服务质量下降的情况下,选择尽可能地屏蔽所造成的影响。 大部分熔断返回默认值 null,也可以定制,RPCClient 原生支持最好,业务方少改代码(熔断放的地方),进入熔断时,打印熔断日志,同时返回 Exception(业务方定制熔断方法),需要有服务治理平台,可以看到服务的状态、是否降级、是否熔断、可以实时下发阀值配置等。 ▐服务降级 服务整体负载超出预设的上限,或者即将到来的流量预计将会超过阀值,为了保证重要或者基本的服务能够正常运行,拒绝部分请求或者将一些不重要的不紧急的服务或任务进行服务的延迟使用或暂停使用。 主要的手段如下: 服务层降级,主要手段 拒绝部分请求(限流),比如缓存请求队列,拒绝部分等待时间长的请求;根据Head,来拒绝非核心请求;还有其他通用算法上的限流比如令牌桶、漏桶算法等等。 关闭部分服务:比如双11大促0点会关闭逆向退款服务等等。 分级降级:比如自治式服务降级,从网关到业务到DB根据拦截、业务规则逐渐降低下游请求量,体现上是从上到下的处理能力逐渐下降。 数据层降级 比如流量大的时候,更新请求只缓存到MQ,读请求读缓存,等流量小的时候,进行补齐操作(一般数据访问层如果做了降级,就没必要在数据层再做了) 柔性可用策略 比如一些指定最大流量的限流工具,又或是根据CPU负载的限流工具等,需要保证自动打开,不依赖于人工。 ▐发布方式引发的可用性问题 发布方式也是影响高可用的一个点,哈哈,以前还经历过一些线上直接停机发布的案例(银行内部系统),不过作为高大上的互联网,主要会采用这几种发布方式:灰度发布、蓝绿发布、金丝雀发布等等。 数据一致性系统设计 一般一些金融、账务系统对这一块要求会非常严格,下面主要介绍下这里面涉及到的事务一致性、一致性算法等内容。 ▐事务一致性问题 在 DB 层面,一般通过 刚性事务 来实现数据一致性,主要通过 预写日志(WAL) 的方式来实现,WAL(write ahead logging)预写日志的方式。就是所有对数据文件的修改,必须要先写日志,这样,即使在写数据的时候崩溃了,也能通过日志文件恢复,传统的数据库事务就是基于这一个机制(REDO 已提交事务的数据也求改 UNDO 未提交事务的回滚)。 除了这个方式之外,还有一个就是通过 影子数据块 来进行数据备份,提前记录被修改的数据块的修改前的状态,备份起来,如果需要回滚,直接用这个备份的数据块进行覆盖就好了。 其他的就是基于二阶段提交的 XA模型 了。 但是目前互联网系统,已经广泛采用分布式部署模式了,传统的刚性事务无法实现,所以 柔性事务成了目前主流的分布式事务解决防范,主要的模式有下面几种: TCC 模式/或者叫2阶段模式在 try 阶段预扣除资源(但是不锁定资源,提升可用性),在Confirm 或者 Cancel 阶段进行数据提交或者回滚。一般需要引入协调者,或者叫事务管理器。 SAGA模式业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,支持向前或者向后补偿。 MQ的事务消息就是先发 halfMsg,在处理完之后,再发送 commit 或者 rollback Msg,然后 MQ 会定期询问 producer ,halfMsg 能不能 commit 或者 rollback,最终实现事务的最终一致性。实际上是把补偿的动作委托给了 RocketMQ。 分段事物(异步确保)基于可靠消息+本地事务消息表 + 消息队列重试机制。目前这也是一些大厂的主流方案,内部一般称为分段事物。 柔性事务基本都是基于最终一致性去实现,所以肯定会有 补偿 动作在里面,在达到最终一致性之前,对用户一般展示 软状态。 需要注意的一点是,并不是所有的系统都适合引入数据一致性框架,比如用户可以随时修改自己发起的请求的情况,例如,商家设置后台系统,商户会随时修改数据,这里如果涉及到一致性的话,引入一致性框架会导致补偿动作达到最终一致性之前,资源锁会阻塞用户后续的请求。导致体验较差。这种情况下就需要通过其他手段来保障数据一致性了,比如数据对账等操作。 ▐一致性算法 从早期的 Paxos 算法,再到后面衍生的 zab 协议(参考:A simple totally ordered broadcast protocol),提供了当下可靠的分布式锁的解决方案。再到后来的 Raft 算法(In Search of an Understandable Consensus Algorithm),也都是分布式系统设计里面需要了解到的一些知识要点。 最后 这里简单介绍了不同系统设计的时候会面临的一些难点,基本里面每一个点,都是前人在解决各种疑难问题的道路上不断探索,最终才得出的这些业界解决方案,呈现在大家眼前,作为一个技术人员,学会这些技术点只是时间问题,但这种发现问题、直面问题、再到解决问题的能力和精神才是我们最值得学习的地方,也是做为一个系统设计人员或者说是架构师的必要能力。 ✿拓展阅读 作者|勇剑 编辑|橙子君 出品|阿里巴巴新零售淘系技术 本文分享自微信公众号 - 淘系技术(AlibabaMTT)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

进程内缓存助你提高并发能力!

前言 缓存,设计的初衷是为了减少繁重的IO操作,增加系统并发能力。不管是 CPU多级缓存,page cache,还是我们业务中熟悉的 redis 缓存,本质都是将有限的热点数据存储在一个存取更快的存储介质中。 计算机本身的缓存设计就是 CPU 采取多级缓存。那对我们服务来说,我们是不是也可以采用这种多级缓存的方式来组织我们的缓存数据。同时 redis 的存取都会经过网络IO,那我们能不能把热点数据直接存在本进程内,由进程自己缓存一份最近最热的这批数据呢? 这就引出了我们今天探讨的:local cache,本地缓存,也叫进程缓存。 本文带你一起探讨下 go-zero 中进程缓存的设计。Let’s go! 快速入门 作为一个进程存储设计,当然是 crud 都有的: 我们先初始化 local cache // 先初始化 local cache cache, err = collection.NewCache(time.Minute, collection.WithLimit(10)) if err != nil { log.Fatal(err) } 其中参数的含义: expire:key统一的过期时间 CacheOption:cache设置。比如key的上限设置等 基础操作缓存 // 1. add/update 增加/修改都是该API cache.Set("first", "first element") // 2. get 获取key下的value value, ok := cache.Get("first") // 3. del 删除一个key cache.Del("first") Set(key, value) 设置缓存 value, ok := Get(key) 读取缓存 Del(key) 删除缓存 高级操作 cache.Take("first", func() (interface{}, error) { // 模拟逻辑写入local cache time.Sleep(time.Millisecond * 100) return "first element", nil }) 前面的 Set(key, value) 是单纯将 &lt;key, value&gt; 加入缓存;Take(key, setFunc) 则是在 key 对于的 value 不存在时,执行传入的 fetch 方法,将具体读取逻辑交给开发者实现,并自动将结果放到缓存里。 到这里核心使用代码基本就讲完了,其实看起来还是挺简单的。也可以到 https://github.com/tal-tech/go-zero/blob/master/core/collection/cache_test.go 去看 test 中的使用。 解决方案 首先缓存实质是一个存储有限热点数据的介质,面临以下的这些问题: 有限容量 热点数据统计 多线程存取 下面来说说这3个方面我们的设计实践。 有限容量 有限就意味着满了要淘汰,这个就涉及到淘汰策略。cache 中使用的是:LRU(最近最少使用)。 那淘汰怎么发生呢? 有几个选择: 开一个定时器,不断循环所有key,等到了预设过期时间,执行回调函数(这里是删除map中过的key) 惰性删除。访问时判断该键是否被删除。缺点是:如果未访问的话,会加重空间浪费。 而 cache 中采取的是第一种 主动删除。但是,主动删除中遇到最大的问题是: 不断循环,空消耗CPU资源,即使在额外的协程中这么做,也是没有必要的。 cache 中采取的是时间轮记录额外过期通知,等过期 channel 中有通知时,然后触发删除回调。 有关 时间轮 更多的设计文章:https://go-zero.dev/cn/timing-wheel.html 热点数据统计 对于缓存来说,我们需要知道这个缓存在使用额外空间和代码的情况下是否有价值,以及我们想知道需不需要进一步优化过期时间或者缓存大小,所有这些我们就很依赖统计能力了, go-zero 中 sqlc 和 mongoc 也同样提供了统计能力。所以我们在 cache 中也加入的缓存,为开发者提供本地缓存监控的特性,在接入 ELK 时开发者可以更直观的监测到缓存的分布情况。 而设计其实也很简单,就是:Get() 命中,就在统计 count 上加1即可。 func (c *Cache) Get(key string) (interface{}, bool) { value, ok := c.doGet(key) if ok { // 命中hit+1 c.stats.IncrementHit() } else { // 未命中miss+1 c.stats.IncrementMiss() } return value, ok } 多线程存取 当多个协程并发存取的时候,对于缓存来说,涉及的问题以下几个: 写-写冲突 LRU 中元素的移动过程冲突 并发执行写入缓存时,造成流量冲击或者无效流量 这种情况下,写冲突好解决,最简单的方法就是 加锁 : // Set(key, value) func (c *Cache) Set(key string, value interface{}) { // 加锁,然后将 <key, value> 作为键值对写入 cache 中的 map c.lock.Lock() _, ok := c.data[key] c.data[key] = value // lru add key c.lruCache.add(key) c.lock.Unlock() ... } // 还有一个在操作 LRU 的地方时:Get() func (c *Cache) doGet(key string) (interface{}, bool) { c.lock.Lock() defer c.lock.Unlock() // 当key存在时,则调整 LRU item 中的位置,这个过程也是加锁的 value, ok := c.data[key] if ok { c.lruCache.add(key) } return value, ok } 而并发执行写入逻辑,这个逻辑主要是开发者自己传入的。而这个过程: func (c *Cache) Take(key string, fetch func() (interface{}, error)) (interface{}, error) { // 1. 先获取 doGet() 中的值 if val, ok := c.doGet(key); ok { c.stats.IncrementHit() return val, nil } var fresh bool // 2. 多协程中通过 sharedCalls 去获取,一个协程获取多个协程共享结果 val, err := c.barrier.Do(key, func() (interface{}, error) { // double check,防止多次读取 if val, ok := c.doGet(key); ok { return val, nil } ... // 重点是执行了传入的缓存设置函数 val, err := fetch() ... c.Set(key, val) }) if err != nil { return nil, err } ... return val, nil } 而 sharedCalls 通过共享返回结果,节省了多次执行函数,减少了协程竞争。 总结 本篇文章讲解了本地缓存设计实践。从使用到设计思路,你也可以根据你的业务动态修改 缓存的过期策略,加入你想要的统计指标,实现自己的本地缓存。 甚至可以将本地缓存和 redis 结合,给服务提供多级缓存,这个就留到我们下一篇文章:缓存在服务中的多级设计。 关于 go-zero 更多的设计和实现文章,可以关注『微服务实践』公众号。 项目地址 https://github.com/tal-tech/go-zero 欢迎使用 go-zero 并 star 支持我们! 微信交流群 关注『微服务实践』公众号并点击 进群 获取社区群二维码。

优秀的个人博客,低调大师

提高Android Studio的Build速度实践

1.在下面的目录创建gradle.properties文件 1 2 3 /home/<username>/.gradle/ (Linux) /Users/<username>/.gradle/ (Mac) C:\Users\<username>\.gradle (Windows) 在文件夹中添加 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Settings specified in this file will override any Gradle settings # configured through the IDE. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # The Gradle daemon aims to improve the startup and execution time of Gradle. # When set to true the Gradle daemon is to run the build. # TODO: disable daemon on CI, since builds should be clean and reliable on servers org.gradle.daemon= true # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx10248m -XX:MaxPermSize=256m org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects org.gradle.parallel= true # Enables new incubating mode that makes Gradle selective when configuring projects. # Only relevant projects are configured which results in faster builds for large multi-projects. # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:configuration_on_demand org.gradle.configureondemand= true 以上配置的目的:增大gradle运行的java虚拟机的大小,让gradle在编译的时候使用独立进程,让gradle可以平行的运行。 2. Android studio中配置 开启offline模式 本文转自Work Hard Work Smart博客园博客,原文链接:http://www.cnblogs.com/linlf03/p/6817039.html,如需转载请自行联系原作者

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。