常用加解密及摘要算法介绍及使用场景–MD5,SHA1,AES,DES,3DES,RSA,ECC

 加解密  常用加解密及摘要算法介绍及使用场景–MD5,SHA1,AES,DES,3DES,RSA,ECC已关闭评论
10月 292020
 

网上看到的一篇常用加解密算法、摘要算法的介绍文章,转载来自CSDN

数字签名信息加密是前后端开发经常使用的技术。应用场景包括:用户登录,交易,信息通讯等。

1、数字签名

数字签名 简单来说就是通过提供可鉴别 的数字信息 验证自身身份的一种方式。

一套 数字签名 通常定义两种 互补的运算,一个用于签名,另一个用于验证,分别  发送者持有能够代表自己的私钥,由接受者持有的与私钥对应的公钥,能够在接受到来自发送者信息时候用于验证其身份。

2、加密和解密

2.1 加密: 数据加密 基本过程,就是对来的铭文的文件或者数据 按照 某种算法 进行处理,使其成为不可读的一段代码,通常成为 “密文”。通过这样的途径,来达到保护数据 不被别人 非法窃取,阅读的目的。

2.2 解密:加密的逆过程为解密,将 编码的信息 转为为 原有的数据 的过程。

3、对称加密和非对称加密

面试相关问题:举例说一下有哪些常用的对称算法?及其使用场景。
常见的对称加密算法:DES3DESAES

常见的非对称加密算法:RSADSA

散列算法:SHA-1MD5等,其应用场景?

3.1、对称加密(共享密钥加密算法)

在对称加密算法中,使用的密钥只有一个,发送者  接受者 双方都是用这一个密钥进行 加密  解密。这就要求通信双方都必须事先知道这个密钥。

  • 数据加密过程:在对称加密算法中,数据发送方  明文 和加密密钥 一起经过特殊加密处理,生成 密文 进行发送。
  • 数据解密过程:数据接收方,收到密文后,若想读取原数据,则需要使用加密使用的密钥 即相同算法的 逆算法 对密文进行解密,才可以恢复到 可读的明文。

3.2、非对称加密算法(公开密钥加密算法)

在非对称加密算法中,她需要两个密钥,一个称为公开密钥,另一个为私有密钥。因为加密和解密使用的密钥不同,所以称为非对称加密算法。

  • 如果使用公钥对数据进行加密,只有用对应的私钥才可以对其进行解密。
  • 如果使用私钥对数据进行加密,只有用对应的公钥才可以对其进行解密。

4、常用的签名加密算法

4.1MD5算法 (相比于SHA1 安全性低,但是 速度快)

MD5算法用的是哈希函数,他的典型应用是对于一段信息产生信息摘要,以防止被篡改。严格来说MD5不算一种加密算法,而是一种 摘要算法。无论多长的输入,MD5都会输出长度为128bits的一个串(通常用16进制表示32个字符)。


public class MD5 {

    public static final byte[] computeMD5(byte[] content) {

        try {

            MessageDigest md5 = MessageDigest.getInstance(“MD5”);

            return md5.digest(content);

        } catch (NoSuchAlgorithmException e) {

            throw new RuntimeException(e);

        }

    }



    public static void main(String[] args) {

        String s1 = “hello world”;

        byte[] bytes = s1.getBytes();

        System.out.println(s1 +加密后的密文为:+computeMD5(bytes));

    }

}


// 输出结果:hello world经过MD5加密后的密文为:[B@4554617c

 

4.2 SHA1算法 (相比于MD5 安全性高,但是 速度慢)

SHA1算法和MD5算法是一样流行 消息摘要算法,然而SHA1的安全性会高于MD5。对于长度小于2^64位的消息,SHA1会产生一个160位的消息摘要。


public class SHA1 {
    public static byte[] computeSHA1(byte[] content) {

        try {

            MessageDigest sha1 = MessageDigest.getInstance(“SHA1”);

            return sha1.digest(content);

        } catch (NoSuchAlgorithmException e) {

            throw new RuntimeException(e);

        }

    }



    public static void main(String[] args) {

        String s1 = “hello world”;

        byte[] bytes = s1.getBytes();

        System.out.println(s1 +加密后的密文为:+computeSHA1(bytes));

    }

}


// 输出结果:hello world经过SHA1加密后的密文为:[B@4554617c

 

根据MD5SHA1的信息摘要及不可逆的特性,应用场景有 

  • 密码验证:服务器端存储用户密码加密后的内容,每次密码校验比较的是密文是否相同,确保服务器管理员也无法获取到用户使用的密码。
  • 文件的完整性比较:当下载一个文件时,服务器返回的信息中包括这个文件的MD5(或者SHA1),在本地下载完毕将其进行MD5加密,之后比较两个MD5只进行比较,如果一直则说明文件完整不存在丢包现象。
  • 文件上传:在上传文件信息的时候,将该文件的MD5同时上传给服务器。服务器中存储了这个文件MD5,并存储这个MD5只对应的已上传的字节长度,比如未上传为0,已完成为-1,已上传200自己,则值为200。可以用于匹配该文件在服务器中的状态,方便断点再传。只要源文件没有改,就算文件改了名字,换个账户都可以在服务器中找到对应的文件,避免存储多份相同文件,并可以提高二次上传时的速度。
  • 版权验证:当一个视频或者音创作出来的时候他的MD5是唯一的,翻录过后的版本的MD5会不同,可以用于版权验证。

4.3 HMAC算法

是一个密钥相关的 哈希运算消息认证码,HMAC运算利用 哈希算法(MD5SHA1等),以一个密钥  一个消息作为输入,生成一个消息摘要 作为输出。HMAC 发送方和接收方都有这个key进行计算,而没有这个key的第三方,则无法计算出正确的散列值,这样就可以防止数据被篡改。 该算法在多线程环境下是不安全的。

对称算法和非对称算法:

4.4 对称算法——AES/DES/3DES算法

AES/DES/3DES 都是对称的块加密算法,加解密过程是可逆的,

4.4.1 AES算法

AES加密算法是密码学中的高级加密标准,该加密算法采用的是 对称分组密码体制,密钥长度最少支持 128位,192位,256位,因而有AES128AES192AES256等常用的加密方式。分组长度为128位,算法更易于各种硬件和软件的实现。

AES本身就是为了取代DES的,AES具有更好的安全性,效率和灵活性。

4.4.2 DES算法

DES加密算法是一种 分组密码,以64位位 分组对数据加密,他的密钥长度是56位,加密和解密用同一个算法。
DES加密算法对于密钥进行保密,而公开算法,包括加密和解密算法。这样只有掌握了和发送方 相同密钥的人才能来解读由DES进行加密的密文数据。

4.4.3 3DES算法

是基于DES的对称算法,对 一块数据  三个不同的密钥 进行三次加密,安全性更高。
 

4.5 非对称算法——RSAECC算法

4.5.1 RSA算法

RSA算法是目前最具影响力的公钥加密算法。RSA是第一个同事用于加密和数字签名的算法,它能够抵抗到目前为止已知的所有密码攻击

RSA算法基于一个十分简单的数论试试,将两个大素数相乘十分容易,但是想要对乘积进行因式分解确十分困难,因此可以将乘积公开作为加密密钥。

4.5.2 ECC算法

ECC算法的主要优势是,在某一些情况下,它可以生成比其他方法更小的密钥,比如RSA算法,并提供相当或者更高级别的安全等级。不过他的缺点是 加密和解密操作的实现会比其他机制时间要长,对于CPU的消耗严重。

utm   详细X
基本翻译
abbr. 统一威胁管理(Unified Threat Management);通用横墨卡托投影(Universal Transverse Mercator projection)
网络释义
UTM: 统一威胁管理
UTM UTM: 统一威胁管理
UTM-: 通用横向墨卡托图

android开源EventBus框架使用介绍

 android  android开源EventBus框架使用介绍已关闭评论
10月 102020
 

网上一篇关于android eventBus框架使用的文章,分享下,来自:https://blog.csdn.net/sdsxtianshi/article/details/80449804

 

1. EventBus 框架

    EventBus,顾名思义即事件总线,是针对Android跨进程、线程通信的优化方案,在一定程度上可以代替Handle、Intent、Brodcast等实现通信;如下图所示即EventBus的运行框架。
这里写图片描述
     在EventBus中主要有以下三个成员

  • Event:事件,可以自定义为任意对象,类似Message类的作用;
  • Publisher:事件发布者,可以在任意线程、任意位置发布Event,已发布的Evnet则由EventBus进行分发;
  • Subscriber:事件订阅者,接收并处理事件,需要通过register(this)进行注册,而在类销毁时要使用unregister(this)方法解注册。每个Subscriber可以定义一个或多个事件处理方法,其方法名可以自定义,但需要添加@Subscribe的注解,并指明ThreadMode(不写默认为Posting)。
1.1 五种ThreadMode
  • Posting:直接在事件发布者所在线程执行事件处理方法;
  • Main:直接在主线程中执行事件处理方法(即UI线程),如果发布事件的线程也是主线程,那么事件处理方法会直接被调用,并且未避免ANR,该方法应避免进行耗时操作;
  • MainOrdered:也是直接在主线程中执行事件处理方法,但与Main方式不同的是,不论发布者所在线程是不是主线程,发布的事件都会进入队列按事件串行顺序依次执行;
  • BACKGROUND:事件处理方法将在后台线程中被调用。如果发布事件的线程不是主线程,那么事件处理方法将直接在该线程中被调用。如果发布事件的线程是主线程,那么将使用一个单独的后台线程,该线程将按顺序发送所有的事件。
  • Async:不管发布者的线程是不是主线程,都会开启一个新的线程来执行事件处理方法。如果事件处理方法的执行需要一些时间,例如网络访问,那么就应该使用该模式。为避免触发大量的长时间运行的事件处理方法,EventBus使用了一个线程池来有效地重用已经完成调用订阅者方法的线程以限制并发线程的数量。
    后面会通过代码展示五种ThreadMode的工作方式。

2. EventBus的使用流程

1. build.gradle 中添加EventBus的依赖:

dependencies {
    ...
    compile 'org.greenrobot:eventbus:3.1.1'
}

2. 定义Event事件类:

public class myEvent {
    private String mMessage;
            ...
}

3. 添加订阅事件:

EventBus.getDefault().register(this);

4. 定义事件处理方法并添加注解,参数为定义的事件类:

@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(myEvent me){
            ...
}

5. 发布事件以触发事件处理方法:

EventBus.getDefault().post(new myEvent("this is first message !"));

3. EventBus应用

以下Demo以5中不同的ThreadMode定义了5个事件处理方法,并新开启一个线程作为事件发布者。

3.1 定义Event时间类
/**
 * Created by wise on 2018/5/16.
 */
/**Event事件类**/
public class myEvent {
    private String mMessage;
    public myEvent(String message){
        this.mMessage = message;
    }

    public void setMessage(String message){
        this.mMessage = message;
    }

    public String getmessage(){
        return this.mMessage;
    }

}
3.2 EventBus功能实现
public class EventBusActivity extends AppCompatActivity {
    private static final String TAG = "EventBusActivity";
    private TextView tv_Event;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_bus);
        /**注册事件**/
        EventBus.getDefault().register(this);
        Thread thread1 = new Thread(new myThread1());
        thread1.start();
    }
    /**事件处理方法**/
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEventMain(myEvent me){
        Log.d(TAG,me.getmessage() + " onEventMain" + " thread:" + android.os.Process.myTid());
    }
    /**事件处理方法**/
    @Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
    public void onEventMainOrdered(myEvent me){
        Log.d(TAG,me.getmessage() + " onEventMainOrdered" + " thread:" + android.os.Process.myTid());
    }
    /**事件处理方法**/
    @Subscribe(threadMode = ThreadMode.POSTING)
    public void onEventPosting(myEvent te){
      Log.d(TAG,te.getmessage() + " onEventPosting" + " thread:" + android.os.Process.myTid());
    }
    /**事件处理方法**/
    @Subscribe(threadMode = ThreadMode.BACKGROUND)
    public void onEventBackground(myEvent me){
        Log.d(TAG,me.getmessage() + " onEventBackground" + " thread:" + android.os.Process.myTid());
    }
    /**事件处理方法**/
    @Subscribe(threadMode = ThreadMode.ASYNC)
    public void onEventAsync(myEvent me){
        Log.d(TAG,me.getmessage() + " onEventAsync" + " thread:" + android.os.Process.myTid());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        /**解注册**/
        EventBus.getDefault().unregister(this);
    }

    public class myThread1 implements Runnable {

        @Override
        public void run() {
            Log.d(TAG,"" + android.os.Process.myTid());
            try {
                Thread.sleep(1000);
                /**事件发布**/
                EventBus.getDefault().post(new myEvent("this is first message !"));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

执行结果如下:

05-24 17:17:47.335 24447 24478 D EventBusActivity: 24478
05-24 17:17:48.341 24447 24478 D EventBusActivity: this is first message ! onEventBackground thread:24478
05-24 17:17:48.341 24447 24498 D EventBusActivity: this is first message ! onEventAsync thread:24498
05-24 17:17:48.342 24447 24447 D EventBusActivity: this is first message ! onEventMain thread:24447
05-24 17:17:48.342 24447 24447 D EventBusActivity: this is first message ! onEventMainOrdered thread:24447
05-24 17:17:48.344 24447 24478 D EventBusActivity: this is first message ! onEventPosting thread:24478

 

主线程Tid为24447,子线程Tid为24478,可以看到每种ThreadMode的运行方式。另外我们可以通过定义不同的事件类作为post的参数来执行不同的事件执行方法,后一篇会分析源码解释调用事件处理方法的逻辑。

4. EventBus的粘性事件

以上的Demo中,事件订阅者的注册必须在发布事件之前,否则发布之后,订阅者无法接受到事件,而粘性事件则避免了这一问题,粘性事件的发布使用postSticky()方法即可,并在注解中配置sticky参数。

// 订阅粘性事件
@Subscribe(sticky = true)
public void onMessageEvent(myEvent me) {
    ...
}

// 粘性事件发布
EventBus.getDefault().postSticky(new myEvent ("This is sticky event"));

发布一个粘性事件之后,EventBus将在内存中缓存该粘性事件。当有订阅者订阅了该粘性事件,订阅者将接收到该事件。需要注意的是在未移除粘性事件之前,它会一直缓存在内存中,因此在处理完该事件后要及时移除该事件的缓存,移除粘性事件的方法如下:

// 移除指定的粘性事件
removeStickyEvent(Object event);

// 移除指定类型的粘性事件
removeStickyEvent(Class<T> eventType);

// 移除所有的粘性事件
removeAllStickyEvents();

5. EventBus中的优先级

在定义事件处理方法时,还可以在注解中设置该方法的优先级:

@Subscribe(priority = 1)
public void onEvent(myEvent me) {
        ...
}

 

默认情况下,订阅者方法的事件传递优先级为0。数值越大,优先级越高。在相同的线程模式下,更高优先级的订阅者方法将优先接收到事件。注意:优先级只有在相同的线程模式下才有效。

swift 闭包使用$0和$1等代替参数介绍

 swift  swift 闭包使用$0和$1等代替参数介绍已关闭评论
3月 162020
 

swift自动为闭包提供参数名缩写功能,可以直接通过$0$1、$2等…来表示闭包中的第一个第二个、第三个参数及后续的参数,并且对应的参数类型会根据函数类型来进行判断。如下代码:

  • 不使用$0 $1这些来代替
let nbs = [10,15,13,14,23,26]
snbs = nbs.sorted(by: { (a, b) -> Bool in
  return a < b
})
print("sorted -" + "\(snbs)") 
  • 使用$0,$1

let nbs = [10,15,13,14,23,26]
var snbs = nbs.sorted(by: {$0 < $1})
print(“sorted -” + “\(snbs)”)

可以发现使用$0、$1的话,参数类型可以自动判断,并且in关键字也可以省略,也就是只用写函数体就可以了, 确实方便。

swift闭包的@escaping和@noescape介绍与理解

 swift  swift闭包的@escaping和@noescape介绍与理解已关闭评论
3月 162020
 
swift闭包中关于@escaping与@noescape,下面的文章内容简洁易懂,转自链接:https://www.jianshu.com/p/905ba2a85455

OC的Block有一个坑,就是它的调用时机。

看下面两个方法,思考一下它们到底有什么区别。

- (void)methodAWithBlock:(void(^)())block {
    _block = block;
}
- (void)methodBWithBlock:(void(^)())block {
    block();
}
  • 第一个方法是将block作为实例变量存入当前的对象。常见的例子是异步的网络请求回调。
  • 第二个方法是立即调用这个传入来的block。常见的例子是数组的排序。

如果这是一个私有的类,@implementation看不到。那怎么判断这个block是拿来干什么的呢?答案是无法判断。

第一个方法里的Block是被当作实例变量接收了,例如该对象是A。对象A同时也被对象B持有,就成了这样B->A->block,这时block实现里引用了B,那么就变成了经典的B->A->block->B,引用循环。

可喜可贺的是如果我们不看内部实现,根本无法确切地判断出这个block是被对象A持有的。当然这是比较极端的例子,一般在声明方法时都会注明这个Block是作什么用的,只是在语言上无法防止这种不确定行为而已。

swift的闭包

有意思的是,swift在闭包上加强了静态检查。它有两个修饰词@escaping和@noescape。这个看代码就能说明。

    func addClosure(_ closure: @escaping ()->Void) {
        self.closure = closure
    }
    
    func doSomething(_ closure: ()->Void) {
        closure()
    }
  • 第一个方法加了@escaping,意味着“逃脱”,闭包的生命周期可以逃脱方法的作用域,在方法return后不会销毁,这意味着它的调用时机是不确定的,是异步的。一般用于异步网络请求。
  • 第二个没有修饰词,所以是默认的@noescape,这意味着该闭包不能超出方法的作用域,方法return后闭包就销毁了,所以它是安全的。

下面这种做法是会报错的,因为在方法doSomething返回后,闭包还存在于异步队列里等候调用。

    func doSomething(_ closure: ()->Void) {
        DispatchQueue(label: "queue").async {
            closure()
        }
    }

总结

看来swift作为编程语言在静态检查上比很多语言都强,但其实这么强的静态检查对养成好的编程习惯也是好事。

swift下类和结构体的比较(相同点与不同点,什么情况下使用)

 swift  swift下类和结构体的比较(相同点与不同点,什么情况下使用)已关闭评论
3月 122020
 

网上看到的一篇好文章,清楚明了的解释了swift下类与结构体的相同点和不同点,并说明了该在什么情况下使用,推荐! 来自: https://juejin.im/post/5b57e75ef265da0f87593513

结构体是构建代码所用的一种通用且灵活的构造体。我们可以使用完全相同的语法规则来为类和结构体定义属性(变量,常量)和添加方法。从而扩展类和结构体的功能。

与其他编程语言所不同的是,Swift并不要求你为自定义类和结构去创建独立的接口和实现文件。你所要做的是在一个单一文件中定义一个类或者结构体,系统将会自动生成面向其他代码的外部接口。

注: 通常一个的势力被称为对象。在Swift中,类和结构体的关系要比在其他语言中更加密切,本章中所讨论的部分功能都可以在类和结构体上。因此主要使用实例。内容包含类和结构体对比结构体和枚举是值类型类是引用类型类和结构体的选择字符串、数组和字典类型的赋值和复制行为


类和结构体对比

Swift中的类和结构体有很多共同点:

  • 定义属性用于储存值
  • 定义方法用于提供功能
  • 定义下标操作通过下标语法可以访问它们的值
  • 定义构造器用于生成初始化值
  • 通过扩展以增加默认实现的功能
  • 遵循协议以提供某种标准功能

与结构体相比,类还有如下的附加功能:

  • 继承允许一个类继承另一个类的特征
  • 类型转换允许在运行时检查和解释一个类实例的类型
  • 析构器允许一个类实例释放任何其所被分配的资源
  • 引用计数允许对一个类的多次引用

注: 结构体总是通过被复制的方式在代码中传递,不使用引用计数。

定义语法

类和结构体有着类似的定义方式。通过关键字classstruct来分别表示类和结构体,并在一对大括号中定义它们的具体内容:

class SomeClass {
	// 在这里定义类
}
struct SomeStructure {
	// 在这里定义结构体
} 

注: 在每次定义一个新类或者结构体的时候,实际上是定义了一个新的Swift类型。因此请使用UpperCamelCase这种命名方式命名(如SomeClassSomeStructure等),已便符合标准Swift类型的大写命名风格(如StringIntBool)。相反的,请使用lowerCamelCase这种方式为属性和方法命名(如:framerateincrementCount),以便和类型名区分。

以下是定义结构体和定义类的示例:

struct Resolution {
	var width = 0
	var height = 0
}
class VideoMode {
	var resolution = Resolution()
	var interlaced = false
	var frameRate = 0.0
	var name: String?
} 

在上面的示例中我们定义了一个名为Resolution的结构体,用来描述一个显示器的像素分辨率。这个结构体包含了两个名为widthheight的存储属性。存储属性是被捆绑和存储在类或结构体中的常量或变量。这两个属性被初始化为整数0的时候,它们会被推断为Int类型。

在上面的示例中我们还定义了一个名为VideoMode的类,用来描述一个视频显示器的特定模式。这个类包含了四个变量存储属性。第一个是分辨率,它被初始化为一个新的Resolution结构体的实例,属性类型被推断为Resolution。新的VideoMode实例同时还会初始化其他三个属性,它们分别是,初始化为falseinterlaced,初始值为0.0frameRate,以及可选值为Stringnamename属性会被自定赋值nil,为可选类型。

类和结构体实例

Resolution结构体和VideoMode类的定义仅描述了什么是ResolutionVideoMode。它们并没有描述一个特定的分辨率(resolution)和视频模式(video mode)。为了描述一个特定的分辨率或者视频模式,我们需要生成一个它们的实例。

let someResolution = Resolution()
let someVideoMode = VideoMode() 

结构体和类都使用构造器语法来生成新的实例。构造器语法的最简单形式是在结构体或者类的类型名称后跟随一对空括号,如Resolution()VideoMode()。通过这种方式所创建的类或者结构体实例,其属性均会被初始化为默认值。

属性访问

通过使用点语法,你可以访问实例的属性。其语法规则则是,实例名后面紧跟属性名:

print("The width of someResolution is \(someResolution.width)")
// 打印 "The width of someResolution is 0"

print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is 0"

someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is now 1280" 

注: 与OC语言不通的是,Swift允许直接设置结构体属性的子属性。上面的最后一个例子,就是直接设置了someVideoModeresolution属性的width这个子属性,以上操作并不需要重新为整个resolution属性设置新值。

结构体类型的成员逐一构造器

所有结构体都有一个自动生成的成员逐一构造器,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中,如下:

let vga = Resolution(width: 640, height: 480) 

与结构体不同,类实例没有默认的成员逐一构造器

结构体和枚举是值类型

值类型被赋予给一个变量、常量或者传递给一个函数的时候,其值会被拷贝

在Swift中,所有的基本类型:整数,浮点数,布尔值,字符串,数组,字典都是值类型,并且在底层都是以结构体的形式所实现。所有的结构体和枚举类型都是值类型。这意味着他们的实例,以及实例中所包含的任何值类型属性,在代码中传递都会被复制。

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd 

在以上示例中,声明了一个名为hd的常量,其值为一个初始化为全高清视频分辨率(1920 像素宽,1080 像素高)的Resolution的实例。

下面,为了符合数码影院的放映需求(2048 像素宽,1080 像素高),cinemawidth 属性需要作如下修改:

cinema.width = 2048 

这里,将会显示 cinemawidth 属性确已改为了 2048

print("cinema is now  \(cinema.width) pixels wide")
// 打印 "cinema is now 2048 pixels wide" 

然而,初始的 hd 实例中 width 属性还是 1920

print("hd is still \(hd.width) pixels wide")
// 打印 "hd is still 1920 pixels wide" 

证明将hd赋值给cinema的时候,实际是将hd中所有储存的值进行拷贝,然后将拷贝的数据存储到新的cinema实例中。由于两者相互独立,因此将 cinemawidth 修改为 2048 并不会影响 hd 中的 width 的值。

枚举也遵循相同的行为准则:

enum CompassPoint {
	case North, South, East, West
}
var currentDirection = CompassPoint.West
let rememberedDirection = currentDirection
currentDirection = .East
if rememberedDirection == .West {
	print("The remembered direction is still .West")
}
// 打印 "The remembered direction is still .West" 

类是引用类型

与值类型不同,引用类型在被赋予到一个变量、常量挥着被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝。使用VideoMode举例如下:

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// 打印 "The frameRate property of theEighty is now 30.0" 

以上示例中,声明了一个名为tenEighty的常量,其引用了一个VideoMode了的新实例。在之前的示例中,这个视频模式被赋予了HD分辨率(1920*1080)的一个拷贝(即 hd 实例)。同时设置为interlaced,命名为“1080i”。最为帧率为25.0帧每秒。然后,tenEighty 被赋予名为 alsoTenEighty 的新常量,同时对 alsoTenEighty 的帧率进行修改。

因为类是引用类型,所以 tenEightalsoTenEight 实际上引用的是相同的 VideoMode 实例。换句话说,它们是同一个实例的两种叫法。

通过查看 tenEightyframeRate 属性,我们会发现它正确的显示了所引用的 VideoMode 实例的新帧率,其值为 30.0

注: tenEightyalsoTenEighty 被声明为常量而不是变量。然而你依然可以改变 tenEighty.frameRatealsoTenEighty.frameRate,因为 tenEightyalsoTenEighty 这两个常量的值并未改变。它们并不“存储”这个 VideoMode 实例,而仅仅是对 VideoMode 实例的引用。所以,改变的是被引用的 VideoModeframeRate 属性,而不是引用 VideoMode 的常量的值。

恒等于运算符

因为类是引用类型,有可能有多个常量和变量在幕后同时引用同一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在二笔赋予到常量、变量或者传递到函数时,其值总是被拷贝。)

Swift里有两个恒等于符号:等价于(===不等价于!== 来判定两个常量是否引用同一个类实例。

if tenEighty === alsoTenEighty {
	print("tenEighty and alsoTenEighty refer to the same Resolution instance.")
}
//打印 "tenEighty and alsoTenEighty refer to the same Resolution instance." 

注: 等于(==等价于(===)意义不同:

等于表示两个实例的值相等相同 等价于表示两个类类型的常量或者变量引用同一个类实例。

指针

在OC中,指针是来引用内存中的地址。一个引用某个引用类型实例的Swift常量或者变量,与OC的指针类似,但并不直接指向某个内存地址,也不要求使用(*)来表明你在创建一个引用。Swift中的这些引用于其他的常量或者变量的定义相同。

类和结构体的选择

在我们的代码中,我们可以使用类和结构体来定义我们的自定义数据类型。

然而,结构体实例总是通过值传递,类实例总是通过引用传递。这意味两者使用不同的任务。当你在考虑一个工程项目的数据和功能的时候,你需要决定每个数据结构是定义成类还是结构体。

按照通用准则,当符合一条或多条一下条件时,可以考虑结构体:

  • 该数据结构的主要目的是用来封装少量相关简单数据值
  • 有理由预计该数据结构的实例在被赋值或传递时,封装的数据将会被拷贝而不是被引用。
  • 该数据结构中存储的值类型属性,也应该被拷贝,而不是被引用。
  • 该数据结构不需要去继承另一个既有类型的属性或者行为。

字符串、数组、字典类型的赋值与复制行为

Swift中,许多基本类型如StringArrayDictionary类型均以结构体的形式实现。这意味着被赋值给新的常量,或者被传入函数或方法中时,它们的值会被拷贝。

OC中NSStringNSArrayNSDictionary类型均以类的形式实现,而并非结构体。它们在被赋值或者被传入函数或者方法时,不会发生值拷贝,而是传递现有实例的引用。

 

swift内存管理中WEAK 和 UNOWNED介绍与使用

 swift  swift内存管理中WEAK 和 UNOWNED介绍与使用已关闭评论
1月 062020
 

网上看到的一篇关于swift 的内存管理的weak和unowned介绍和使用的文章,觉得写的挺好,分享下。文章来源地址: onevcat

 

不管在什么语言里,内存管理的内容都很重要,所以我打算花上比其他 tip 长一些的篇幅仔细地说说这块内容。

Swift 是自动管理内存的,这也就是说,我们不再需要操心内存的申请和分配。当我们通过初始化创建一个对象时,Swift 会替我们管理和分配内存。而释放的原则遵循了自动引用计数 (ARC) 的规则:当一个对象没有引用的时候,其内存将会被自动回收。这套机制从很大程度上简化了我们的编码,我们只需要保证在合适的时候将引用置空 (比如超过作用域,或者手动设为 nil 等),就可以确保内存使用不出现问题。

但是,所有的自动引用计数机制都有一个从理论上无法绕过的限制,那就是循环引用 (retain cycle) 的情况。

什么是循环引用

虽然我觉得循环引用这样的概念介绍不太应该出现在这本书中,但是为了更清晰地解释 Swift 中的循环引用的一般情况,这里还是简单进行说明。假设我们有两个类 A 和 B, 它们之中分别有一个存储属性持有对方:

class A {
    let b: B
    init() {
        b = B()
        b.a = self
    }

    deinit {
        print("A deinit")
    }
}

class B {
    var a: A? = nil
    deinit {
        print("B deinit")
    }
}

在 A 的初始化方法中,我们生成了一个 B 的实例并将其存储在属性中。然后我们又将 A 的实例赋值给了 b.a。这样 a.b 和 b.a 将在初始化的时候形成一个引用循环。现在当有第三方的调用初始化了 A,然后即使立即将其释放,A 和 B 两个类实例的 deinit 方法也不会被调用,说明它们并没有被释放。

var obj: A? = A()
obj = nil
// 内存没有释放

因为即使 obj 不再持有 A 的这个对象,b 中的 b.a 依然引用着这个对象,导致它无法释放。而进一步,a 中也持有着 b,导致 b 也无法释放。在将 obj 设为 nil 之后,我们在代码里再也拿不到对于这个对象的引用了,所以除非是杀掉整个进程,我们已经永远也无法将它释放了。多么悲伤的故事啊..

在 Swift 里防止循环引用

为了防止这种人神共愤的悲剧的发生,我们必须给编译器一点提示,表明我们不希望它们互相持有。一般来说我们习惯希望 “被动” 的一方不要去持有 “主动” 的一方。在这里 b.a 里对 A 的实例的持有是由 A 的方法设定的,我们在之后直接使用的也是 A 的实例,因此认为 b 是被动的一方。可以将上面的 class B 的声明改为:

class B {
    weak var a: A? = nil
    deinit {
        print("B deinit")
    }
}

在 var a 前面加上了 weak,向编译器说明我们不希望持有 a。这时,当 obj 指向 nil 时,整个环境中就没有对 A 的这个实例的持有了,于是这个实例可以得到释放。接着,这个被释放的实例上对 b 的引用 a.b 也随着这次释放结束了作用域,所以 b 的引用也将归零,得到释放。添加 weak 后的输出:

A deinit
B deinit

可能有心的朋友已经注意到,在 Swift 中除了 weak 以外,还有另一个冲着编译器叫喊着类似的 “不要引用我” 的标识符,那就是 unowned。它们的区别在哪里呢?如果您是一直写 Objective-C 过来的,那么从表面的行为上来说 unowned 更像以前的 unsafe_unretained,而 weak 就是以前的 weak。用通俗的话说,就是 unowned 设置以后即使它原来引用的内容已经被释放了,它仍然会保持对被已经释放了的对象的一个 “无效的” 引用,它不能是 Optional 值,也不会被指向 nil。如果你尝试调用这个引用的方法或者访问成员属性的话,程序就会崩溃。而 weak 则友好一些,在引用的内容被释放后,标记为 weak 的成员将会自动地变成 nil (因此被标记为 @weak 的变量一定需要是 Optional 值)。关于两者使用的选择,Apple 给我们的建议是如果能够确定在访问时不会已被释放的话,尽量使用 unowned,如果存在被释放的可能,那就选择用 weak

我们结合实际编码中的使用来看看选择吧。日常工作中一般使用弱引用的最常见的场景有两个:

  1. 设置 delegate 时
  2. 在 self 属性存储为闭包时,其中拥有对 self 引用时

前者是 Cocoa 框架的常见设计模式,比如我们有一个负责网络请求的类,它实现了发送请求以及接收请求结果的任务,其中这个结果是通过实现请求类的 protocol 的方式来实现的,这种时候我们一般设置 delegate 为 weak

// RequestManager.swift
class RequestManager: RequestHandler {

    @objc func requestFinished() {
        print("请求完成")
    }

    func sendRequest() {
        let req = Request()
        req.delegate = self

        req.send()
    }
}

// Request.swift
@objc protocol RequestHandler {
    optional func requestFinished()
}

class Request {
    weak var delegate: RequestHandler!;

    func send() {
        // 发送请求
        // 一般来说会将 req 的引用传递给网络框架
    }

    func gotResponse() {
        // 请求返回
        delegate?.requestFinished?()
    }
}

req 中以 weak 的方式持有了 delegate,因为网络请求是一个异步过程,很可能会遇到用户不愿意等待而选择放弃的情况。这种情况下一般都会将 RequestManager 进行清理,所以我们其实是无法保证在拿到返回时作为 delegate 的 RequestManager 对象是一定存在的。因此我们使用了 weak 而非 unowned,并在调用前进行了判断。

闭包和循环引用

另一种闭包的情况稍微复杂一些:我们首先要知道,闭包中对任何其他元素的引用都是会被闭包自动持有的。如果我们在闭包中写了 self 这样的东西的话,那我们其实也就在闭包内持有了当前的对象。这里就出现了一个在实际开发中比较隐蔽的陷阱:如果当前的实例直接或者间接地对这个闭包又有引用的话,就形成了一个 self -> 闭包 -> self 的循环引用。最简单的例子是,我们声明了一个闭包用来以特定的形式打印 self 中的一个字符串:

class Person {
    let name: String
    lazy var printName: ()->() = {
        print("The name is \(self.name)")
    }

    init(personName: String) {
        name = personName
    }

    deinit {
        print("Person deinit \(self.name)")
    }
}

var xiaoMing: Person? = Person(personName: "XiaoMing")
xiaoMing!.printName()
xiaoMing = nil
// 输出:
// The name is XiaoMing,没有被释放

printName 是 self 的属性,会被 self 持有,而它本身又在闭包内持有 self,这导致了 xiaoMing 的 deinit 在自身超过作用域后还是没有被调用,也就是没有被释放。为了解决这种闭包内的循环引用,我们需要在闭包开始的时候添加一个标注,来表示这个闭包内的某些要素应该以何种特定的方式来使用。可以将 printName 修改为这样:

lazy var printName: ()->() = {
    [weak self] in
    if let strongSelf = self {
        print("The name is \(strongSelf.name)")
    }
}

现在内存释放就正确了:

// 输出:
// The name is XiaoMing
// Person deinit XiaoMing

如果我们可以确定在整个过程中 self 不会被释放的话,我们可以将上面的 weak 改为 unowned,这样就不再需要 strongSelf 的判断。但是如果在过程中 self 被释放了而 printName 这个闭包没有被释放的话 (比如 生成 Person 后,某个外部变量持有了 printName,随后这个 Persone 对象被释放了,但是 printName 已然存在并可能被调用),使用 unowned 将造成崩溃。在这里我们需要根据实际的需求来决定是使用 weak 还是 unowned

这种在闭包参数的位置进行标注的语法结构是将要标注的内容放在原来参数的前面,并使用中括号括起来。如果有多个需要标注的元素的话,在同一个中括号内用逗号隔开,举个例子:

// 标注前
{ (number: Int) -> Bool in
    //...
    return true
}

// 标注后
{ [unowned self, weak someObject] (number: Int) -> Bool in
    //...
    return true
}

objective-c中categories和extentions简介

 objective-c  objective-c中categories和extentions简介已关闭评论
12月 132019
 

网上很多关于objective-c中categories和extentions的介绍都很含糊,但下面这篇文章自己觉得写的很好,虽然内容有些老了,但不影响你理解categories和extentions,来自:https://code.tutsplus.com/tutorials/objective-c-succinctly-categories-and-extensions–mobile-22016

Categories are an Objective-C language feature that let you add new methods to an existing class, much like C# extensions. However, do not confuse C# extensions with Objective-C extensions. Objective-C’s extensions are a special case of categories that let you define methods that must be declared in the main implementation block.

These are powerful features that have many potential uses. First, categories make it possible to split up a class’ interface and implementation into several files, which provides much-needed modularity for larger projects. Second, categories let you fix bugs in an existing class (e.g., NSString) without the need to subclass it. Third, they provide an effective alternative to the protected and private methods found in C# and other Simula-like languages.

category is a group of related methods for a class, and all of the methods defined in a category are available through the class as if they were defined in the main interface file. As an example, take the Person class that we’ve been working with throughout this book. If this were a large project, Person may have dozens of methods ranging from basic behaviors to interactions with other people to identity checking. The API might call for all of these methods to be available through a single class, but it’s much easier for developers to maintain if each group is stored in a separate file. In addition, categories eliminate the need to recompile the entire class every time you change a single method, which can be a time-saver for very large projects.

Let’s take a look at how categories can be used to achieve this. We start with a normal class interface and a corresponding implementation:

// Person.h
@interface Person : NSObject
@interface Person : NSObject
@property (readonly) NSMutableArray* friends;
@property (copy) NSString* name;
- (void)sayHello;
- (void)sayGoodbye;
@end
// Person.m
#import "Person.h"
@implementation Person
@synthesize name = _name;
@synthesize friends = _friends;
-(id)init{
    self = [super init];
    if(self){
        _friends = [[NSMutableArray alloc] init];
    }
    return self;
}
- (void)sayHello {
    NSLog(@"Hello, says %@.", _name);
}
- (void)sayGoodbye {
    NSLog(@"Goodbye, says %@.", _name);
}
@end

Nothing new here-just a Person class with two properties (the friends property will be used by our category) and two methods. Next, we’ll use a category to store some methods for interacting with other Person instances. Create a new file, but instead of a class, use the Objective-C Category template. Use Relations for the category name and Person for the Category on field:

Figure 28 Creating the PersonRelations class
Creating the Person+Relations class

As expected, this will create two files: a header to hold the interface and an implementation. However, these will both look slightly different than what we’ve been working with. First, let’s take a look at the interface:

// Person+Relations.h
#import <Foundation/Foundation.h>
#import "Person.h"
@interface Person (Relations)
- (void)addFriend:(Person *)aFriend;
- (void)removeFriend:(Person *)aFriend;
- (void)sayHelloToFriends;
@end

Instead of the normal @interface declaration, we include the category name in parentheses after the class name we’re extending. A category name can be anything, as long as it doesn’t conflict with other categories for the same class. A category’s file name should be the class name followed by a plus sign, followed by the name of the category (e.g., Person+Relations.h ).

So, this defines our category’s interface. Any methods we add in here will be added to the original Person class at run time. It will appear as though the addFriend:removeFriend:, and sayHelloToFriends methods are all defined in Person.h, but we can keep our functionality encapsulated and maintainable. Also note that you must import the header for the original class, Person.h. The category implementation follows a similar pattern:

// Person+Relations.m
#import "Person+Relations.h"
@implementation Person (Relations)
- (void)addFriend:(Person *)aFriend {
    [[self friends] addObject:aFriend];
}
- (void)removeFriend:(Person *)aFriend {
    [[self friends] removeObject:aFriend];
}
- (void)sayHelloToFriends {
    for (Person *friend in [self friends]) {
        NSLog(@"Hello there, %@!", [friend name]);
    }
}
@end

This implements all of the methods in Person+Relations.h. Just like the category’s interface, the category name appears in parentheses after the class name. The category name in the implementation should match the one in the interface.

Also, note that there is no way to define additional properties or instance variables in a category. Categories have to refer back to data stored in the main class (friends in this instance).

It’s also possible to override the implementation contained in Person.m by simply redefining the method in Person+Relations.m. This can be used to monkey patch an existing class; however, it’s not recommended if you have an alternative solution to the problem, since there would be no way to override the implementation defined by the category. That is to say, unlike the class hierarchy, categories are a flat organizational structure-if you implement the same method in two separate categories, it’s impossible for the runtime to figure out which one to use.

The only change you have to make to use a category is to import the category’s header file. As you can see in the following example, the Person class has access to the methods defined in Person.h along with those defined in the category Person+Relations.h:

// main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Person+Relations.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *joe = [[Person alloc] init];
        joe.name = @"Joe";
        Person *bill = [[Person alloc] init];
        bill.name = @"Bill";
        Person *mary = [[Person alloc] init];
        mary.name = @"Mary";
        [joe sayHello];
        [joe addFriend:bill];
        [joe addFriend:mary];
        [joe sayHelloToFriends];
    }
    return 0;
}

And that’s all there is to creating categories in Objective-C.

To reiterate, all Objective-C methods are public-there is no language construct to mark them as either private or protected. Instead of using “true” protected methods, Objective-C programs can combine categories with the interface/implementation paradigm to achieve the same result.

The idea is simple: declare “protected” methods as a category in a separate header file. This gives subclasses the ability to “opt-in” to the protected methods while unrelated classes use the “public” header file as usual. For example, take a standard Ship interface:

// Ship.h
#import <Foundation/Foundation.h>
@interface Ship : NSObject
- (void)shoot;
@end

As we’ve seen many times, this defines a public method called shoot. To declare a protected method, we need to create a Ship category in a dedicated header file:

// Ship_Protected.h
#import <Foundation/Foundation.h>
@interface Ship(Protected)
- (void)prepareToShoot;
@end

Any classes that need access to the protected methods (namely, the superclass and any subclasses) can simply import Ship_Protected.h. For example, the Ship implementation should define a default behavior for the protected method:

// Ship.m
#import "Ship.h"
#import "Ship_Protected.h"
@implementation Ship {
    BOOL _gunIsReady;
}
- (void)shoot {
    if (!_gunIsReady) {
        [self prepareToShoot];
        _gunIsReady = YES;
    }
    NSLog(@"Firing!");
}
- (void)prepareToShoot {
    // Execute some private functionality.
    NSLog(@"Preparing the main weapon...");
}
@end

Note that if we hadn’t imported Ship_Protected.h, this prepareToShoot implementation would be a private method, as discussed in the Methods chapter. Without a protected category, there would be no way for subclasses to access this method. Let’s subclass the Ship to see how this works. We’ll call it ResearchShip:

// ResearchShip.h
#import "Ship.h"
@interface ResearchShip : Ship
- (void)extendTelescope;
@end

This is a normal subclass interface-it should not import the protected header, as this would make the protected methods available to anyone that imports ResearchShip.h, which is precisely what we’re trying to avoid. Finally, the implementation for the subclass imports the protected methods and (optionally) overrides them:

// ResearchShip.m
#import "ResearchShip.h"
#import "Ship_Protected.h"
@implementation ResearchShip
- (void)extendTelescope {
    NSLog(@"Extending the telescope");
}
// Override protected method
- (void)prepareToShoot {
    NSLog(@"Oh shoot! We need to find some weapons!");
}
@end

To enforce the protected status of the methods in Ship_Protected.h, other classes aren’t allowed to import it. They’ll just import the normal “public” interfaces of the superclass and subclass:

// main.m
#import <Foundation/Foundation.h>
#import "Ship.h"
#import "ResearchShip.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Ship *genericShip = [[Ship alloc] init];
        [genericShip shoot];
        Ship *discoveryOne = [[ResearchShip alloc] init];
        [discoveryOne shoot];
    }
    return 0;
}

Since neither main.mShip.h, nor ResearchShip.h import the protected methods, this code won’t have access to them. Try adding a [discoveryOne prepareToShoot] method-it will throw a compiler error, since the prepareToShoot declaration is nowhere to be found.

To summarize, protected methods can be emulated by placing them in a dedicated header file and importing that header file into the implementation files that require access to the protected methods. No other files should import the protected header.

While the workflow presented here is a completely valid organizational tool, keep in mind that Objective-C was never meant to support protected methods. Think of this as an alternative way to structure an Objective-C method, rather than a direct replacement for C#/Simula-style protected methods. It’s often better to look for another way to structure your classes rather than forcing your Objective-C code to act like a C# program.

One of the biggest issues with categories is that you can’t reliably override methods defined in categories for the same class. For example, if you defined an addFriend: class in Person(Relations) and later decided to change the addFriend: implementation via a Person(Security) category, there is no way for the runtime to know which method it should use since categories are, by definition, a flat organizational structure. For these kinds of cases, you need to revert to the traditional subclassing paradigm.

Also, it’s important to note that a category can’t add instance variables. This means you can’t declare new properties in a category, as they could only be synthesized in the main implementation. Additionally, while a category technically does have access to its classes’ instance variables, it’s better practice to access them through their public interface to shield the category from potential changes in the main implementation file.

Extensions (also called class extensions) are a special type of category that requires their methods to be defined in the main implementation block for the associated class, as opposed to an implementation defined in a category. This can be used to override publicly declared property attributes. For example, it is sometimes convenient to change a read-only property to a read-write property within a class’ implementation. Consider the normal interface for a Ship class:

Included code sample: Extensions

// Ship.h
#import <Foundation/Foundation.h>
#import "Person.h"
@interface Ship : NSObject
@property (strong, readonly) Person *captain;
- (id)initWithCaptain:(Person *)captain;
@end

It’s possible to override the @property definition inside of a class extension. This gives you the opportunity to re-declare the property as readwrite in the implementation file. Syntactically, an extension looks like an empty category declaration:

// Ship.m
#import "Ship.h"
// The class extension.
@interface Ship()
@property (strong, readwrite) Person *captain;
@end
// The standard implementation.
@implementation Ship
@synthesize captain = _captain;
- (id)initWithCaptain:(Person *)captain {
    self = [super init];
    if (self) {
        // This WILL work because of the extension.
        [self setCaptain:captain];
    }
    return self;
}
@end

Note the () appended to the class name after the @interface directive. This is what marks it as an extension rather than a normal interface or a category. Any properties or methods that appear in the extension must be declared in the main implementation block for the class. In this case, we aren’t adding any new fields-we’re overriding an existing one. But unlike categories, extensions can add extra instance variables to a class, which is why we’re able to declare properties in a class extension but not a category.

Because we re-declared the captain property with a readwrite attribute, the initWithCaptain: method can use the setCaptain: accessor on itself. If you were to delete the extension, the property would return to its read-only status and the compiler would complain. Clients using the Ship class aren’t supposed to import the implementation file, so the captain property will remain read-only.

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Ship.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *heywood = [[Person alloc] init];
        heywood.name = @"Heywood";
        Ship *discoveryOne = [[Ship alloc] initWithCaptain:heywood];
        NSLog(@"%@", [discoveryOne captain].name);
        Person *dave = [[Person alloc] init];
        dave.name = @"Dave";
        // This will NOT work because the property is still read-only.
        [discoveryOne setCaptain:dave];
    }
    return 0;
}

Another common use case for extensions is for declaring private methods. In the previous chapter, we saw how private methods can be declared by simply adding them anywhere in the implementation file. But, prior to Xcode 4.3, this was not the case. The canonical way to create a private method was to forward-declare it using a class extension. Let’s take a look at this by slightly altering the Ship header from the previous example:

// Ship.h
#import <Foundation/Foundation.h>
@interface Ship : NSObject
- (void)shoot;
@end

 

Next, we’re going to recreate the example we used when we discussed private methods in the Methods chapter. Instead of simply adding the private prepareToShoot method to the implementation, we need to forward-declare it in a class extension.

// Ship.m
#import "Ship.h"
// The class extension.
@interface Ship()
- (void)prepareToShoot;
@end
// The rest of the implementation.
@implementation Ship {
    BOOL _gunIsReady;
}
- (void)shoot {
    if (!_gunIsReady) {
        [self prepareToShoot];
        _gunIsReady = YES;
    }
    NSLog(@"Firing!");
}
- (void)prepareToShoot {
    // Execute some private functionality.
    NSLog(@"Preparing the main weapon...");
}
@end

The compiler ensures the extension methods are implemented in the main implementation block, which is why it functions as a forward-declaration. Yet because the extension is encapsulated in the implementation file, other objects shouldn’t ever know about it, giving us another way to emulate private methods. While newer compilers save you this trouble, it’s still important to understand how class extensions work, as it has been a common way to leverage private methods in Objective-C programs until very recently.

This chapter covered two of the more unique concepts in the Objective-C programming language: categories and extensions. Categories are a way to extend the API of existing classes, and extensions are a way to add required methods to the API outside of the main interface file. Both of these were initially designed to ease the burden of maintaining large code bases.

The next chapter continues our journey through Objective-C’s organizational structures. We’ll learn how to define a protocol, which is an interface that can be implemented by a variety of classes.

Python模板-Mako语法介绍

 python  Python模板-Mako语法介绍已关闭评论
9月 052019
 

资源

官网 http://www.makotemplates.org/

文档 http://docs.makotemplates.org/en/latest/

文档翻译 Mako模板入门 http://help.42qu.com/code/mako.html

安装

pip install mako

HelloWorld

from mako.template import Template

mytemplate = Template("hello world!")
print mytemplate.render()

-------------------------

from mako.template import Template
print Template("hello ${data}!").render(data="world")

语法

输出变量 ${x}

数学计算 ${1+1}
the contents within the ${} tag are evaluated by Python directly, so full expressions are OK

filter
${"test"|u}
${"test"|u,trim}
内置filter列表
    u : URL escaping, provided by urllib.quote_plus(string.encode('utf-8'))
    h : HTML escaping, provided by markupsafe.escape(string)
    x : XML escaping
    trim : whitespace trimming, provided by string.strip()
    entity : produces HTML entity references for applicable strings, derived from htmlentitydefs
    unicode (str on Python 3): produces a Python unicode string (this function is applied by default)
    decode.<some encoding> : decode input into a Python unicode with the specified encoding
    n : disable all default filtering; only filters specified in the local expression tag will be applied.

分支
% if x == 5:
    abcd
% endif

循环
% for a in ['1', '2', '3']:
    % if a == '1':
      abc
    % elif a == '2':
      def
    % else:
      gh
    % endif
$ endfor

Python语法
this is a template
<%
    x = db.get_resource('foo')
    y = [z.element for z in x if x.frobnizzle==5]
%>
% for elem in y:
    element: ${elem}
% endfor

换行

加 / 强制不换行


设置变量
% for item in ('apple', 'banana'):
    <%
        isBanana = False
    %>
    % if item == 'banana':
    <%
        isBanana = True
    %>
    %endif
    % if isBanana:
        <span> Bought a banana</span>
    %endif
%endfor

注释

## 这是一个注释.
...text ...

多行
<%doc>
这里是注释
更多注释
</%doc>

模块级别语句

<% %> 的一个变体是 <%! %>,代表模块级别的代码块。其中的代码会在模板的模块级别执行,而不是在模板的 rendering 函数中。

<%!
import mylib
import re

def filter(text):
    return re.sub(r'^@', '', text)
%>

标签

定义了当前模板的总体特性,包括缓存参数,以及模板被调用时期待的参数列表(非必须)
<%page args="x, y, z='default'"/>
<%page cached="True" cache_type="memory"/>


<%include file="header.html"/>
hello world
<%include file="footer.html"/>

%def 标签用于定义包含一系列内容的一个 Python 函数,此函数在当前模板的其他某个地方被调用到
<%def name="myfunc(x)">
this is myfunc, x is ${x}
</%def>
${myfunc(7)}

<%block filter="h">
some <html> stuff.
</%block>
<%block name="header">
    <h2><%block name="title"/></h2>
</%block>

Mako 中的 %namespace 等价于 Python 里的 import 语句。它允许访问其他模板文件的所有 rendering 函数和元数据
<%namespace file="functions.html" import="*"/>

<%inherit file="base.html"/>

处理多行注释:
<%doc>
    these are comments
    more comments
</%doc>

该标签使得 Mako 的词法器对模板指令的常规解析动作停止,并以纯文本的形式返回其整个内容部分
<%text filter="h">
heres some fake mako ${syntax}
<%def name="x()">${x}</%def>
</%text>

有时你想中途停止执行一个模板或者 <%def> 方法,只返回已经收集到的文本信息,可以通过在 Python 代码块中使用 return 语句来完成

% if not len(records):
    No records found.
    <% return %>
% endif

文件template

为提高性能,从文件中加载的 Template, 可以将它产生的模块的源代码以普通 python 模块文件的形式(.py),

缓存到文件系统中。只要加一个参数 module_directory 即可做到这一点:

from mako.template import Template

mytemplate = Template(filename='/docs/mytmpl.txt', module_directory='/tmp/mako_modules')
print mytemplate.render()

当上述代码被 render 的时候,会创建文件 /tmp/mako_modules/docs/mytmpl.txt.py.

下一次 Template 对象被用同样参数调用的时候,就会直接重用该模块文件。

文件TemplateLookup

#有一个对 header.txt 文件的包含引用。而从何处去查找 header.txt, 则由 TemplateLookup 指明,是 "/docs" 目录
from mako.template import Template
from mako.lookup import TemplateLookup

mylookup = TemplateLookup(directories=['/docs'])
mytemplate = Template("""<%include file="header.txt"/> hello world!""", lookup=mylookup)


--------------

#可以直接通过 TemplateLookup 来获取模板对象,利用 TemplateLookup 的 get_template 方法,
#并传递模板的 URI 作为参数
mylookup = TemplateLookup(directories=['/docs'], output_encoding='utf-8', encoding_errors='replace')
mytemplate = mylookup.get_template("foo.txt")
print mytemplate.render()

-------------
参数
mylookup = TemplateLookup(directories=['/docs'], output_encoding='utf-8', encoding_errors='replace', , collection_size=500)
TemplateLookup 同时也会在内存中缓存一组模板,所以并不是每一次请求都会导致模板的重新编译和模块重新加载。默认 TemplateLookup 的大小没有限制,但你可以通过 collection_size 参数来限制它
以上的 lookup 会持续加载模板到内存中,直到达到 500 的时候,它就会清除掉一定比例的模板缓存项,根据“最近最少访问”原则

另一个 TemplateLookup 相关的标志是  filesystem_checks. 默认为 True,
每一次 get_template() 方法返回模板后,原始的模板文件的 revision time 会和上次加载模板的时间做对比,
如果文件更新,则会加载其内容,并重新编译该模板。
在生产环境下,设置 filesystem_checks 为 False 可以带来一定的性能提升(和具体的文件系统有关)

自己创建context

from mako.template import Template
from mako.runtime import Context
from StringIO import StringIO

mytemplate = Template("hello, ${name}!")
buf = StringIO()
ctx = Context(buf, name="jack")
mytemplate.render_context(ctx)
print buf.getvalue()

其他

1.解决mako中文乱码问题

TemplateLookup(... , output_encoding='utf-8', ...)
Template(..., input_encoding='utf-8')
又在mako的模板文件的首行添加
## -*- encoding:utf8 -*-

MYSQL中 INSERT…. ON DUPLICATE KEY UPDATE重复插入时更新及REPLACE讲解

 mysql  MYSQL中 INSERT…. ON DUPLICATE KEY UPDATE重复插入时更新及REPLACE讲解已关闭评论
9月 052019
 

个人总结:

INSERT…. ON DUPLICATE KEY UPDATE 相当于 INSERT + UPDATE的结合体

REPLACE 相当于 DELETE + INSERT的结合体

 

mysql当插入重复时更新的方法:

第一种方法:

 

示例一:插入多条记录

假设有一个主键为 client_id 的 clients 表,可以使用下面的语句:

 

Sql代码

INSERT INTO clients

(client_id,client_name,client_type)

SELECT supplier_id,supplier_name,‘advertising’

FROM suppliers

WHERE not exists(select * from clients where clients.client_id=suppliers.supplier_id);

 

示例一:插入单条记录

 

Sql代码

INSERT INTO clients

(client_id,client_name,client_type)

SELECT 10345,‘IBM’,‘advertising’

FROM dual

WHERE not exists (select * from clients where clients.client_id=10345);

 

使用 dual 做表名可以让你在 select 语句后面直接跟上要插入字段的值,即使这些值还不存在当前表中。

第二种方法:

 

INSERT 中ON DUPLICATE KEY UPDATE的使用(本文重点)

如果您指定了ON DUPLICATE KEY UPDATE,并且插入行后会导致在一个UNIQUE索引或PRIMARY KEY中出现重复值,则执行旧行UPDATE。例如,如果列a被定义为UNIQUE,并且包含值1,则以下两个语句具有相同的效果:

 

Sql代码

mysql>INSERT INTO table (a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;

mysql>UPDATE table SET c=c+1 WHERE a=1;

 

如果行作为新记录被插入,则受影响行的值为1;如果原有的记录被更新,则受影响行的值为2。

注释:如果列b也是唯一列,则INSERT与此UPDATE语句相当:

 

Sql代码

mysql>UPDATE table SET c=c+1 WHERE a=1 OR b=2 LIMIT 1;

 

如果a=1 OR b=2与多个行向匹配,则只有一个行被更新。通常,您应该尽量避免对带有多个唯一关键字的表使用ON DUPLICATE KEY子句。

您可以在UPDATE子句中使用VALUES(col_name)函数从INSERT…UPDATE语句的INSERT部分引用列值。换句话说,如果没有发生重复关键字冲突,则UPDATE子句中的VALUES(col_name)可以引用被插入的col_name的值。本函数特别适用于多行插入。VALUES()函数只在INSERT…UPDATE语句中有意义,其它时候会返回NULL。

示例:

 

Sql代码

mysql>INSERT INTO table (a,b,c) VALUES (1,2,3),(4,5,6)

ON DUPLICATE KEY UPDATE c=VALUES(a)+VALUES(b);

 

本语句与以下两个语句作用相同:

 

Sql代码

mysql>INSERT INTO table (a,b,c) VALUES (1,2,3)

ON DUPLICATE KEY UPDATE c=3;

mysql>INSERT INTO table (a,b,c) VALUES (4,5,6)

ON DUPLICATE KEY UPDATE c=9;

 

当您使用ON DUPLICATE KEY UPDATE时,DELAYED选项被忽略。

第三种方法:

 

REPLACE语句

我们在使用数据库时可能会经常遇到这种情况。如果一个表在一个字段上建立了唯一索引,当我们再向这个表中使用已经存在的键值插入一条记录,那将会抛出一个主键冲突的错误。当然,我们可能想用新记录的值来覆盖原来的记录值。如果使用传统的做法,必须先使用DELETE语句删除原先的记录,然后再使用INSERT插入新的记录。而在MySQL中为我们提供了一种新的解决方案,这就是REPLACE语句。使用REPLACE插入一条记录时,如果不重复,REPLACE就和INSERT的功能一样,如果有重复记录,REPLACE就使用新记录的值来替换原来的记录值。

使用REPLACE的最大好处就是可以将DELETE和INSERT合二为一,形成一个原子操作。这样就可以不必考虑在同时使用DELETE和INSERT时添加事务等复杂操作了。

在使用REPLACE时,表中必须有唯一索引,而且这个索引所在的字段不能允许空值,否则REPLACE就和INSERT完全一样的。

在执行REPLACE后,系统返回了所影响的行数,如果返回1,说明在表中并没有重复的记录,如果返回2,说明有一条重复记录,系统自动先调用了DELETE删除这条记录,然后再记录用INSERT来插入这条记录。如果返回的值大于2,那说明有多个唯一索引,有多条记录被删除和插入。

REPLACE的语法和INSERT非常的相似,如下面的REPLACE语句是插入或更新一条记录。

REPLACE INTO users (id,name,age) VALUES(123, ‘赵本山’, 50);

 

 

插入多条记录:

REPLACE INTO users(id, name, age)

Sql代码

VALUES(123, ‘赵本山’, 50), (134,‘Mary’,15);

REPLACE也可以使用SET语句

REPLACE INTO users SET id = 123, name = ‘赵本山’, age = 50;

 

上面曾提到REPLACE可能影响3条以上的记录,这是因为在表中有超过一个的唯一索引。在这种情况下,REPLACE将考虑每一个唯一索引,并对每一个索引对应的重复记录都删除,然后插入这条新记录。假设有一个table1表,有3个字段a, b, c。它们都有一个唯一索引。

CREATE TABLE table1(a INT NOT NULL UNIQUE,b INT NOT NULL UNIQUE,c INT NOT NULL UNIQUE);

 

假设table1中已经有了3条记录

a b c

1 1 1

2 2 2

3 3 3

下面我们使用REPLACE语句向table1中插入一条记录。

REPLACE INTO table1(a, b, c) VALUES(1,2,3);

 

返回的结果如下

Query OK, 4 rows affected (0.00 sec)

在table1中的记录如下

a b c

1 2 3

 

转自: https://www.iteye.com/blog/lobert-1604122

linux下分别使用find和grep进行查找

 linux  linux下分别使用find和grep进行查找已关闭评论
8月 302019
 

网上的资料,这个介绍的很详细,收藏下:

在使用linux时,经常需要进行文件查找。其中查找的命令主要有find和grep。两个命令是有区的。

区别:(1)find命令是根据文件的属性进行查找,如文件名,文件大小,所有者,所属组,是否为空,访问时间,修改时间等。

(2)grep是根据文件的内容进行查找,会对文件的每一行按照给定的模式(patter)进行匹配查找。

一.find命令

    基本格式:find  path expression

1.按照文件名查找

(1)find / -name httpd.conf  #在根目录下查找文件httpd.conf,表示在整个硬盘查找
(2)find /etc -name httpd.conf  #在/etc目录下文件httpd.conf
(3)find /etc -name ‘*srm*’  #使用通配符*(0或者任意多个)。表示在/etc目录下查找文件名中含有字符串‘srm’的文件
(4)find . -name ‘srm*’   #表示当前目录下查找文件名开头是字符串‘srm’的文件

2.按照文件特征查找

(1)find / -amin -10   # 查找在系统中最后10分钟访问的文件(access time)
(2)find / -atime -2   # 查找在系统中最后48小时访问的文件
(3)find / -empty   # 查找在系统中为空的文件或者文件夹
(4)find / -group cat   # 查找在系统中属于 group为cat的文件
(5)find / -mmin -5   # 查找在系统中最后5分钟里修改过的文件(modify time)
(6)find / -mtime -1   #查找在系统中最后24小时里修改过的文件
(7)find / -user fred   #查找在系统中属于fred这个用户的文件
(8)find / -size +10000c  #查找出大于10000000字节的文件(c:字节,w:双字,k:KB,M:MB,G:GB)
(9)find / -size -1000k   #查找出小于1000KB的文件

3.使用混合查找方式查找文件

 参数有: !,-and(-a),-or(-o)。

(1)find /tmp -size +10000c -and -mtime +2   #在/tmp目录下查找大于10000字节并在最后2分钟内修改的文件
(2)find / -user fred -or -user george   #在/目录下查找用户是fred或者george的文件文件
(3)find /tmp ! -user panda  #在/tmp目录中查找所有不属于panda用户的文件

二、grep命令

   基本格式:find  expression

 1.主要参数

[options]主要参数:
-c:只输出匹配行的计数。
-i:不区分大小写
-h:查询多文件时不显示文件名。
-l:查询多文件时只输出包含匹配字符的文件名。
-n:显示匹配行及行号。
-s:不显示不存在或无匹配文本的错误信息。
-v:显示不包含匹配文本的所有行。

pattern正则表达式主要参数:
\: 忽略正则表达式中特殊字符的原有含义。
^:匹配正则表达式的开始行。
$: 匹配正则表达式的结束行。
\<:从匹配正则表达 式的行开始。
\>:到匹配正则表达式的行结束。
[ ]:单个字符,如[A]即A符合要求 。
[ – ]:范围,如[A-Z],即A、B、C一直到Z都符合要求 。
.:所有的单个字符。
* :有字符,长度可以为0。

2.实例

(1)grep ‘test’ d*  #显示所有以d开头的文件中包含 test的行
(2)grep ‘test’ aa bb cc    #显示在aa,bb,cc文件中包含test的行
(3)grep ‘[a-z]\{5\}’ aa   #显示所有包含每行字符串至少有5个连续小写字符的字符串的行
(4)grep magic /usr/src  #显示/usr/src目录下的文件(不含子目录)包含magic的行
(5)grep -r magic /usr/src  #显示/usr/src目录下的文件(包含子目录)包含magic的行

(6)grep -w pattern files :只匹配整个单词,而不是字符串的一部分(如匹配’magic’,而不是’magical’),

 

转自:https://www.cnblogs.com/xudong-bupt/archive/2013/03/23/2976793.html

分布式时序数据库InfluxDB介绍

 数据库  分布式时序数据库InfluxDB介绍已关闭评论
12月 162016
 

记录下influxdb的一些资料,免得到时找不到

InfluxDB 是一个开源分布式时序、事件和指标数据库。使用 Go 语言编写,无需外部依赖。其设计目标是实现分布式和水平伸缩扩展。

它有三大特性:

1. Time Series (时间序列):你可以使用与时间有关的相关函数(如最大,最小,求和等)
2. Metrics(度量):你可以实时对大量数据进行计算
3. Eevents(事件):它支持任意的事件数据

InfluxDB

特点

  • schemaless(无结构),可以是任意数量的列
  • Scalable
  • min, max, sum, count, mean, median 一系列函数,方便统计
  • Native HTTP API, 内置http支持,使用http读写
  • Powerful Query Language 类似sql
  • Built-in Explorer 自带管理工具

管理界面:

InfluxDB

API

InfluxDB 支持两种api方式

  • HTTP API
  • Protobuf API

Protobuf 还未开发完成, 官网文档都没有

如何使用 http api 进行操作?

比如对于foo_production这个数据库,插入一系列数据,可以发现POST 请求到 /db/foo_production/series?u=some_user&p=some_password, 数据放到body里。

数据看起来是这样的:

下面的”name”: “events”, 其中”events”就是一个series,类似关系型数据库的表table

格式是json,可以在一个POST请求发送多个 series, 每个 series 里的 points 可以是多个,但索引要和columns对应。

上面的数据里没有包含time 列,InfluxDB会自己加上,不过也可以指定time,比如:

time 在InfluxDB里是很重要的,毕竟InfluxDB是time series database
在InfluxDB里还有个sequence_number字段是数据库维护的,类似于mysql的 主键概念

InfluxDB 增删更查都是用http api来完成,甚至支持使用正则表达式删除数据,还有计划任务。

比如:

发送POST请求到 /db/:name/scheduled_deletes, body如下,

这个查询会删除大于14天的数据,并且任何以stats开头的数据,并且每天3:00 AM运行。

更加详细查看官方文档: http://influxdb.org/docs/api/http.html

查询语言

InfluxDB 提供了类似sql的查询语言

看起来是这样的:

非常容易上手, 还支持Group By, Merging Series, Joining Series, 并内置常用统计函数,比如max, min, mean 等

文档: http://influxdb.org/docs/query_language/


常用语言的库都有,因为api简单,也很容易自己封装。

InfluxdDB作为很多监控软件的后端,这样监控数据就可以直接存储在InfluxDB
StatsD, CollectD, FluentD

还有其它的可视化工具支持InfluxDB, 这样就可以基于InfluxDB很方便的搭建监控平台

InfluxDB 数据可视化工具

InfluxDB 用于存储基于时间的数据,比如监控数据,因为InfluxDB本身提供了Http API,所以可以使用InfluxDB很方便的搭建了个监控数据存储中心。

对于InfluxDB中的数据展示,官方admin有非常简单的图表, 看起来是这样的

InfluxDB

除了自己写程序展示数据还可以选择:

tasseo

tasseo,为Graphite写的Live dashboard,现在也支持InfluxDB,tasseo 比较简单, 可以配置的选项很少。

InfluxDB

Grafana

Grafana是一个纯粹的html/js应用,访问InfluxDB时不会有跨域访问的限制。只要配置好数据源为InfluxDB之后就可以,剩下的工作就是配置图表。Grafana 功能非常强大。使用ElasticsSearch保存DashBoard的定义文件,也可以Export出JSON文件(Save ->Advanced->Export Schema),然后上传回它的/app/dashboards目录。

配置数据源:

InfluxDB

转自:http://www.ttlsa.com/monitor-safe/monitor/distributed-time-series-database-influxdb/

ZooKeeper介绍

 zookeeper  ZooKeeper介绍已关闭评论
10月 312016
 

ZooKeeper介绍

一、是什么

      ZooKeeper 顾名思义 动物园管理员,他是拿来管大象(Hadoop) 、 蜜蜂(Hive) 、 小猪(Pig) 的管理员,也就是说它是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。 
      它为分布式系统提供了高效可靠且易于使用的协同服务,它可以为分布式应用提供相当多的服务,诸如统一命名服务,配置管理,状态同步和组服务等。 
      说的这么抽象,它到底是个什么呢。

简单的说,zookeeper=文件系统+通知机制。
  • 1
  • 1

1、文件系统

      Zookeeper维护一个类似文件系统的数据结构:

这里写图片描述

      每个子目录项如 NameService 都被称作为 znode,和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。

有四种类型的znode:

PERSISTENT-持久化目录节点
  • 1
  • 1

      客户端与zookeeper断开连接后,该节点依旧存在

PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
  • 1
  • 1

      客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号

EPHEMERAL-临时目录节点
  • 1
  • 1

      客户端与zookeeper断开连接后,该节点被删除

EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
  • 1
  • 1

      客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

2、 通知机制

      客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。

二、做什么

1、 命名服务

      分布式应用中,通常需要有一套完整的命名规则,既能够产生唯一的名称又便于人识别和记住,通常情况下用树形的名称结构是一个理想的选择,树形的名称结构是一个有层次的目录结构,既对人友好又不会重复。Name Service 已经是 Zookeeper 内置的功能,你只要调用 Zookeeper 的 API 就能实现。如调用 create 接口就可以很容易创建一个目录节点。

2、 配置管理

      程序总是需要配置的,如果程序分散部署在多台机器上,要逐个改变配置就变得困难。好吧,现在把这些配置全部放到zookeeper上去,保存在 Zookeeper 的某个目录节点中,然后所有相关应用程序对这个目录节点进行监听,一旦配置信息发生变化,每个应用程序就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中就好。 
这里写图片描述

3、 集群管理

      Zookeeper 能够很容易的实现集群管理的功能,如有多台 Server 组成一个服务集群,那么必须要一个master知道当前集群中每台机器的服务状态,一旦有机器不能提供服务,集群中其它节点必须知道,从而做出调整重新分配服务策略。同样当增加集群的服务能力时,就会增加一台或多台 Server,同样也必须让master知道。Zookeeper 不仅能够帮你维护当前的集群中机器的服务状态,而且能够帮你选出一个master,让这个master来管理集群,这就是 Zookeeper 的另一个功能 Leader Election。

      它们的实现方式都是在 Zookeeper 上创建一个 EPHEMERAL 类型的目录节点,然后每个 Server 在它们创建目录节点的父目录节点上调用getChildren(String path, boolean watch) 方法并设置 watch 为 true,由于是 EPHEMERAL 目录节点,当创建它的 Server 死去,这个目录节点也随之被删除,所以 Children 将会变化,这时 getChildren上的 Watch 将会被调用,所以其它 Server 就知道已经有某台 Server 死去了。新增 Server 也是同样的原理。

      Zookeeper 如何实现 Leader Election,也就是选出一个 Master Server。和前面的一样每台 Server 创建一个 EPHEMERAL 目录节点,不同的是它还是一个 SEQUENTIAL 目录节点,所以它是个 EPHEMERAL_SEQUENTIAL 目录节点。之所以它是 EPHEMERAL_SEQUENTIAL 目录节点,是因为我们可以给每台 Server 编号,我们可以选择当前是最小编号的 Server 为 Master,假如这个最小编号的 Server 死去,由于是 EPHEMERAL 节点,死去的 Server 对应的节点也被删除,所以当前的节点列表中又出现一个最小编号的节点,我们就选择这个节点为当前 Master。这样就实现了动态选择 Master,避免了传统意义上单 Master 容易出现单点故障的问题。

这里写图片描述

4、 分布式锁

      有了zookeeper的一致性文件系统,锁的问题变得容易。锁服务可以分为两类,一个是保持独占,另一个是控制时序。

      对于第一类,我们将zookeeper上的一个znode看作是一把锁,通过createznode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。厕所有言:来也冲冲,去也冲冲,用完删除掉自己创建的distribute_lock 节点就释放出锁。

      对于第二类, /distribute_lock 已经预先存在,所有客户端在它下面创建临时顺序编号目录节点,和选master一样,编号最小的获得锁,用完删除,依次方便。

这里写图片描述

5、队列管理

      Zookeeper 可以处理两种类型的队列:

(1)同步队列:当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。
  • 1
  • 1

      同步队列用 Zookeeper 实现的实现思路如下: 
      创建一个父目录 /synchronizing,每个成员都监控标志(Set Watch)位目录 /synchronizing/start 是否存在,然后每个成员都加入这个队列,加入队列的方式就是创建 /synchronizing/member_i 的临时目录节点,然后每个成员获取 / synchronizing 目录的所有目录节点,也就是 member_i。判断 i 的值是否已经是成员的个数,如果小于成员个数等待 /synchronizing/start 的出现,如果已经相等就创建 /synchronizing/start。

(2)FIFO 队列:先进先出队列,例如实现生产者和消费者模型。
  • 1
  • 1

      FIFO 队列用 Zookeeper 实现思路如下: 
      实现的思路也非常简单,就是在特定的目录下创建 SEQUENTIAL 类型的子目录 /queue_i,这样就能保证所有成员加入队列时都是有编号的,出队列时通过 getChildren( ) 方法可以返回当前所有的队列中的元素,然后消费其中最小的一个,这样就能保证 FIFO。

三、产生背景

      有这样一个场景:系统中有大约100w的用户,每个用户平 均有3个邮箱账号,每隔5分钟,每个邮箱账需要收取100封邮件,最多3亿份邮件需要下载到服务器中(不含附件和正文)。用20台机器划分计算的压力,从 多个不同的网路出口进行访问外网,计算的压力得到缓解,那么每台机器的计算压力也不会很大了。

      通过我们的讨论和以往的经验判断在这场景中可以实现并行计算,但我们还期望能对并行计算的节点进行动态的添加/删除,做到在线更新并行计算的数目并且不会影响计算单元中的其他计算节点,但是有4个问题需要解决,否则会出现一些严重的问题:

      20台机器同时工作时,有一台机器down掉了,其他机器怎么进行接管计算任务,否则有些用户的业务不会被处理,造成用户服务终断。 
      随着用户数量增加,添加机器是可以解决计算的瓶颈,但需要重启所有计算节点,如果需要,那么将会造成整个系统的不可用。 
      用户数量增加或者减少,计算节点中的机器会出现有的机器资源使用率繁忙,有的却空闲,因为计算节点不知道彼此的运行负载状态。 
      怎么去通知每个节点彼此的负载状态,怎么保证通知每个计算节点方式的可靠性和实时性。 
      先不说那么多专业名词,白话来说我们需要的是:1记录状态,2事件通知 ,3可靠稳定的中央调度器,4易上手、管理简单。 
      采用Zookeeper完全可以解决我们的问题,分布式计算中的协调员,观察者,分布式锁 都可以作为zookeeper的关键词,在系统中利用Zookeeper来处理事件通知,队列,优先队列,锁,共享锁等功能,利用这些特色在分布式计算中发挥重要的作用。

转自:http://blog.csdn.net/u010168160/article/details/50821730