热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

OCBlock的本质(一)底层结构、变量捕获

image-20210422152708323 本篇学习内容 image-20210422152804224 image-20210422152831028
OC-Block的本质(一)-底层结构、变量捕获
image-20210422152708323

本篇学习内容

OC-Block的本质(一)-底层结构、变量捕获
image-20210422152804224
OC-Block的本质(一)-底层结构、变量捕获
image-20210422152831028
OC-Block的本质(一)-底层结构、变量捕获
image-20210422152856465

block的本质

block的原理是怎样的?本质是什么?

  • block本质上也是一个OC对象,因为它的内部也有个isa指针
  • block是封装了函数调用以及函数调用环境的OC对象

通俗的理解:block就是将一些代码封装起来,以便在将来某个时候被使用,如果你不去调用block,block内部封装的代码就不会执行。

举一个简单的例子

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ^{
            NSLog(@"this is a block!");
            NSLog(@"this is a block!");
            NSLog(@"this is a block!");
            NSLog(@"this is a block!");
        };
    }
    return 0;
}

RUN>

没有任何输出,Block代码块没调用

Block的使用也很简单,可以像函数一样被使用。加上()就代表调用,如下

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ^{
            NSLog(@"this is a block!");
            NSLog(@"this is a block!");
            NSLog(@"this is a block!");
            NSLog(@"this is a block!");
        }();
    }
    return 0;
}

RUN>

2021-04-22 15:35:54.161606+0800 Interview03-block[4322:148955] this is a block!
2021-04-22 15:35:54.162084+0800 Interview03-block[4322:148955] this is a block!
2021-04-22 15:35:54.162138+0800 Interview03-block[4322:148955] this is a block!
2021-04-22 15:35:54.162160+0800 Interview03-block[4322:148955] this is a block!

block的底层结构-block的本质探索

写个简单的block,其中block内部使用了block外部的age变量:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
      
        int age = 20;

        void (^block)(int, int) =  ^(int a , int b){
            NSLog(@"this is a block! -- %d", age);
        };
        block(10, 10);
    }
    return 0;
}

RUN>

2021-04-22 15:38:10.449976+0800 Interview03-block[4340:150550] this is a block! -- 20

上面的代码可以看出,block里面使用了它上面的 int age = 20 ,可以将这个先简单的理解成函数调用环境,顾名思义,就是block所用到的一些外部变量。

通过clang编译器执行编译成C++代码:

$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

clang编译器编译完后会得到一个.cpp格式的文件,这就是我们刚才转换的.m文件的底层代码.

main函数的C++代码如下

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int age = 20;
        //block底层定义
        void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
        //block底层调用
        ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
    }
    return 0;
}

但是由于底层的代码添加了许多强转,我们简化代码,如下:

//block底层定义
void (*block)(void) = &__main_block_impl_0(
                                           __main_block_func_0,
                                           &__main_block_desc_0_DATA
                                           );
//block底层调用
block->FuncPtr(block, 10, 10);
block底层定义
//block底层定义
void (*block)(void) = &__main_block_impl_0(
                                           __main_block_func_0,
                                           &__main_block_desc_0_DATA
                                           );

先看__main_block_impl_0这个函数,我们发现它被定义在一个同名结构体里面,这个__main_block_impl_0结构体就是block的底层实现

// 一: block底层数据结构
struct __main_block_impl_0 {
    struct __block_impl impl; // 1: impl 结构体
    struct __main_block_desc_0* Desc; // 2: block描述信息的结构体
    int age; //3:捕获的外部变量
    //4: 和结构体同名的构造函数 ( C++语法 , 类似于 OC 的init方法,返回一个结构体对象,类似于返回self)
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age){
    impl.isa = &_NSConcreteStackBlock;//isa指向类对象,就比如str的isa指向NSString
    impl.Flags = flags;
    impl.FuncPtr = fp;//外面的__main_block_func_0函数地址传进来,保存在这里
    Desc = desc;//外面的__main_block_desc_0结构体地址传进来,保存在这里
  }
};

通过底层代码我们可以看到,block在底层中的数据结构是一个结构体,这个结构体有四个部分组成:

1: struct __block_impl

2: struct __main_block_desc_0

3: 捕获的外部变量

4:和block结构体同名的构造函数

我们找到struct __block_impl 结构体:

//struct __block_impl 结构体
struct __block_impl {
  void *isa; //指向 block 的类型
  int Flags;//按位表示block的附加信息
  int Reserved;//保留变量
  void *FuncPtr; //封装了执行 block 代码块的函数地址
};

发现这个结构体里面第一个成员就是isa,验证了block本质上也是一个OC对象。

然后我们再找到struct __main_block_desc_0 结构体 :

static struct __main_block_desc_0 {
  size_t reserved;//保留变量大小
  size_t Block_size;//block所占用的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

可以发现,这个结构体被重新命名为__main_block_desc_0_DATA,默认传入了两个值0和sizeof(struct __main_block_impl_0),block的底层就是__main_block_impl_0结构体,所以这个结构体==第二个值保存的是block的大小==

接下来我们看一下__main_block_impl_0函数的参数,第一个参数是指向__main_block_func_0函数的指针,如下:

//封装了block执行逻辑的函数
//第一个参数是block,后面是block调用的时候传入的参数
void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  int age = __cself->age; // bound by copy

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_0, age);
}

现在我们知道了,首先__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age)函数有两个参数,第一个参数是__main_block_func_0函数的地址(这个函数里面封装了我们block里面执行的代码),第二个参数是__main_block_desc_0结构体的地址(这个结构体里面有保存block的大小),构造函数的返回值是个__main_block_impl_0结构体,block底层就是__main_block_impl_0结构体,最后再获取__main_block_impl_0结构体的地址,赋值给左边的“block”变量,然后我们拿到“block”变量就可以做其他事情了,至此,block定义完成。

block底层调用
//block底层调用 
block->FuncPtr(block, 10, 10);

这句代码就很简单了,直接取出block里面的FuncPtr函数,传入参数进行调用。

不应该是通过“block-> impl->FuncPtr(block, 10, 10)”来拿到FuncPtr吗?其实我们在简化之前,代码是这样的:

((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);

可以发现系统把block强转成__block_impl类型的了,由于impl又是__main_block_impl_0结构体的第一个成员,所以impl的地址和__main_block_impl_0结构体的地址是一样的,强转之后可以直接获取到FuncPtr。

根据如上分析,验证了,block是封装了函数调用以及函数调用环境的OC对象

__main_block_impl_0``__block_impl__main_block_desc_03个结构体之间的关系

OC-Block的本质(一)-底层结构、变量捕获
block的本质

咋们通过梳理了block底层几个类的关系,现在调整一下代码

struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
};

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int age;
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        
        int age = 20;

        void (^block)(int, int) =  ^(int a , int b){
            NSLog(@"this is a block! -- %d", age);
        };



        struct __main_block_impl_0 *blockStruct = (__bridge struct __main_block_impl_0 *)block;



        block(10, 10);
    }
    return 0;
}

加断点,验证一下内存

OC-Block的本质(一)-底层结构、变量捕获
image-20210422161112236

FuncPtr地址验证

OC-Block的本质(一)-底层结构、变量捕获
image-20210422161234946

总结:

如上图所示,block底层就是一个__main_block_impl_0结构体,它由三个部分组成:

  1. 第一部分是impl,它是个结构体,里面有isa指针和FuncPtr指针,FuncPtr指针指向__main_block_func_0函数,这个函数里面封装了block需要执行的代码。
  2. 第二部分是desc,它是个指针,指向__main_block_desc_0结构体,它里面有一个Block_size用来保存block的大小。
  3. 第三部分是age,它把外面访问的成员变量age封装到自己里面了。

block的变量捕获(capture)

OC-Block的本质(一)-底层结构、变量捕获
image-20210422161702137
  1. 如果是被auto修饰的局部变量,会被捕获,是值传递
  2. 如果是被static修饰的局部变量,会被捕获,是指针传递
  3. 如果是全局变量,不会被捕获,因为可以直接访问
一:auto变量
  • auto变量:自动变量,离开作用域就会销毁,一般我们创建的局部变量都是auto变量,比如 int age = 10,系统会在默认在前面加上auto int age = 10
    首先我们要搞清楚,什么是捕获,所谓捕获外部变量,意思就是在block内部,创建一个变量来存放外部变量,这就叫做捕获.
int main(int argc, const char * argv[]) {
    @autoreleasepool {
       int age = 10;
        void (^block)(void) = ^{
            NSLog(@"age is %d",age);
        };
        age = 20;
        block();
    }
    return 0;
}
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
{
        int age = 10;
       //定义block
        void (*block)(void) = &__main_block_impl_0(
                                                   __main_block_func_0,
                                                   &__main_block_desc_0_DATA,
                                                   age
                                                   );
        age = 20;
       //调用block
        block->FuncPtr(block);
    return 0;
}

我们看到在调用block的构造函数时,传入了三个参数,分别是:__main_block_func_0,&__main_block_desc_0_DATA,age,我们找到block的构造函数,看看内部如何处理这个age:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;  // 1: 定义了一个同名的age变量
//block构造函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
    age = _age; //2 :C++的特殊语法,在构造函数内部会默认把_age赋值给age
  }
};

通过查看block的内部结构看我们发现,block内部创建了一个age变量,并且在block构造函数中,把传递进来的_age赋值给了这个age变量.我们看看调用block时,他的底部取的是哪个age:

//调用block的FuncPtr函数,把block当做参数传递进去
block->FuncPtr(block);
//FuncPtr函数内部
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//通过传递的block,找到block内部的age
  int age = __cself->age; // bound by copy
//打印age
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_5t_pxd6sp5x6rl9gnk21q2q934h0000gn_T_main_3089d7_mi_0,age);
        }

通过底层代码,我们看到,在调用block时,block会找到自己内部的age变量,然后打印数出,所以我们修改age = 20,并不会影响block内部的age

二:static变量

我们执行如下代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       auto int age = 10; 
       static int height = 10;

    void (^block)(void) = ^{
       // age的值捕获进来
       // height的指针捕获进来
       NSLog(@"age is %d, height is %d", age, height);
    };

      age = 20;
      height = 20;

      block();

    }
    return 0;
}

RUN>

2021-04-22 16:31:33.658732+0800 Interview01-Block的本质[4670:177646] age is 10, height is 20
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

代码转成C++代码,抽取关键的代码,如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  int *height;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
  int *height = __cself->height; // bound by copy

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_2w_t9gvrhjs7gv_m4kb_8q3r_980000gn_T_main_12eb7d_mi_1, age, (*height));
}

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

    auto int age = 10;
    static int height = 10;

    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));//&height传递指针

    age = 20;
    height = 20;

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }

    return 0;
}

可以看出age和height都被捕获了,age是值捕获,height是指针捕获。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;//定义 age 变量
  int *height;//定义一个 指针变量,存放外部变量的指针
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

定义了两个变量age,height,不同的是,height是一个指针指针变量,用于存放外部变量的指针.我们再来看看执行block代码块的内部:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
  int *height = __cself->height; // bound by copy
 // *height : 取出指针变量所指向的内存的值
   NSLog((NSString *)&__NSConstantStringImpl__var_folders_5t_pxd6sp5x6rl9gnk21q2q934h0000gn_T_main_bf6cae_mi_0,age,(*height));
        }

我们看到,对于age是捕获到内部,把外部age的值存起来,而对于height,是把外部变量的指针保存起来,所以,我们在修改height时,会影响到block内部的值

  1. 我们定义block,其实就是初始化__main_block_impl_0结构体,定义block的时候会把age和&height传进去,这个结构体里面有一个age一个height指针用于接收传进去的值,这行代码“age(_age), height(_height)”就是保证外面的变量改变的时候实时改变结构体里面的age和height指针的值。
  2. 我们调用block的时候,其实就是执行__main_block_func_0函数,这个函数会获取__main_block_impl_0结构体中age和height指针的值,所以打印的时候就会把age和*height的值打印出来。
  3. 所以执行完block之后age的值没改变,因为是值传递,height的值改变了,因为是指针传递。

为什么auto变量是值传递,static变量是指针传递呢?

因为auto是自动变量,出了作用域后会自动销毁的,如果我们保留他的指针,就会存在访问野指针的情况

//定义block类型
void(^block)(void);

void test(){
    int age = 10;
    static int height = 20;
//在block内部访问 age , height
    block = ^{
        NSLog(@"age is %d, height is %d",age,height);
    };
    age = 20;
    height = 20;
}

//在main函数中调用
int main(int argc, const char * argv[]) {
        test();
 //test调用后,age变量就会自动销毁,如果block内部是保留age变量的指针,那么我们在调用block()时,就出现访问野指针
        block();
}
三:全局变量

全局变量哪里都可以访问,所以block内部是不会捕获全局变量的,直接访问

int age_ = 10;
static int height_ = 10;

void (^block)(void);

void test()
{


    block = ^{
        // age的值捕获进来(capture)
        NSLog(@"age is %d, height is %d", age, height);
    };

    age = 20;
    height = 20;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        test();
        block();
    }
    return 0;
}

转成C++文件之后,代码如下:

int age_ = 10;
static int height_ = 10;

void (*block)(void);

struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __test_block_func_0(struct __test_block_impl_0 *__cself) {


        NSLog((NSString *)&__NSConstantStringImpl__var_folders_yh_qjzhl57s63j2m9l4frv27zjc0000gn_T_main_478a1b_mi_0, age, height);
    }

static struct __test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0)};

void test()
{
    block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA));
    age = 20;
    height = 20;
}

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        test();
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
OC-Block的本质(一)-底层结构、变量捕获
image-20210422165620199

为什么全局变量不需要捕获?

因为全局变量无论哪个函数都可以访问,block内部当然也可以正常访问,所以根本无需捕获

为什么局部变量就需要捕获呢?

因为作用域的问题,我们在一个函数中定义变量,在block内部访问,本质上跨函数访问,所以需要捕获起来.

四:self的捕获
#import 

@interface MJPerson : NSObject

@property (copy, nonatomic) NSString *name;

- (void)test;

- (instancetype)initWithName:(NSString *)name;

@end
    
#import "MJPerson.h"

@implementation MJPerson

- (void)test
{
    void (^block)(void) = ^{
        NSLog(@"-------%d", [self name]);
    };
    block();
}

- (instancetype)initWithName:(NSString *)name
{
    if (self = [super init]) {
        self.name = name;
    }
    return self;
}

@end

MJPerson.m转成C++代码:

struct __MJPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __MJPerson__test_block_desc_0* Desc;
  MJPerson *self;
  __MJPerson__test_block_impl_0(void *fp, struct __MJPerson__test_block_desc_0 *desc, MJPerson *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __MJPerson__test_block_func_0(struct __MJPerson__test_block_impl_0 *__cself) {
  MJPerson *self = __cself->self; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_MJPerson_1027e6_mi_0, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")));
    }

static void _I_MJPerson_test(MJPerson * self, SEL _cmd) {
    void (*block)(void) = ((void (*)())&__MJPerson__test_block_impl_0((void *)__MJPerson__test_block_func_0, &__MJPerson__test_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

static instancetype _I_MJPerson_initWithName_(MJPerson * self, SEL _cmd, NSString *name) {
    if (self = ((MJPerson *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MJPerson"))}, sel_registerName("init"))) {
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)name);
    }
    return self;
}
OC-Block的本质(一)-底层结构、变量捕获
image-20210422171347998

可以看出self被捕获了,并且是指针捕获,既然被捕获,就说明self是局部变量

为什么self是局部变量呢?
其实每个方法都两个隐式参数,一个是self一个是_cmd,self是方法调用者,_cmd是方法名,既然self被当做参数了,那self肯定是局部变量了,也可以在上面的代码中进行验证,我们看一下转换后的test()方法:

static void _I_Person_test(Person * self, SEL _cmd) {
    void(*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

OC 中的test()方法时没有参数的,但是转换成 C++ 后就多了两个参数self,_cmd,其实我们每个 OC 方法都会默认有这两个参数,这也是为什么我们在每个方法中都能访问self_cmd,而参数就是局部变量,所以block就自然而然的捕获了self.

对于[self name],在上面的代码可以看出是给self发送消息,如下:

objc_msgSend((id)self, sel_registerName("name"))

所以,block会捕获self,如果想要访问self中的成员变量就给self发送消息就好了(self都被捕获了,肯定可以获取到self中的其他信息了)。

总结:

一:只要是局部变量,不管是auto 变量,还是static 变量,block都会捕获.不同的是,对于auto 变量,block是保存值,而static 变量 是保存的指针.
二:如果是全局变量,根本不需要捕获,直接访问。

特别备注

本系列文章总结自MJ老师在腾讯课堂iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化,相关图片素材均取自课程中的课件。如有侵权,请联系我删除,谢谢!


推荐阅读
  • 为了解决不同服务器间共享图片的需求,我们最初考虑建立一个FTP图片服务器。然而,考虑到项目是一个简单的CMS系统,为了简化流程,团队决定探索七牛云存储的解决方案。本文将详细介绍使用七牛云存储的过程和心得。 ... [详细]
  • 本文介绍如何使用阿里云的fastjson库解析包含时间戳、IP地址和参数等信息的JSON格式文本,并进行数据处理和保存。 ... [详细]
  • 本文详细介绍了 Apache Jena 库中的 Txn.executeWrite 方法,通过多个实际代码示例展示了其在不同场景下的应用,帮助开发者更好地理解和使用该方法。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 本文介绍了如何通过 Maven 依赖引入 SQLiteJDBC 和 HikariCP 包,从而在 Java 应用中高效地连接和操作 SQLite 数据库。文章提供了详细的代码示例,并解释了每个步骤的实现细节。 ... [详细]
  • andr ... [详细]
  • 深入理解Java泛型:JDK 5的新特性
    本文详细介绍了Java泛型的概念及其在JDK 5中的应用,通过具体代码示例解释了泛型的引入、作用和优势。同时,探讨了泛型类、泛型方法和泛型接口的实现,并深入讲解了通配符的使用。 ... [详细]
  • Scala 实现 UTF-8 编码属性文件读取与克隆
    本文介绍如何使用 Scala 以 UTF-8 编码方式读取属性文件,并实现属性文件的克隆功能。通过这种方式,可以确保配置文件在多线程环境下的一致性和高效性。 ... [详细]
  • 本文探讨了 Objective-C 中的一些重要语法特性,包括 goto 语句、块(block)的使用、访问修饰符以及属性管理等。通过实例代码和详细解释,帮助开发者更好地理解和应用这些特性。 ... [详细]
  • 本文探讨了如何在给定整数N的情况下,找到两个不同的整数a和b,使得它们的和最大,并且满足特定的数学条件。 ... [详细]
  • 本文探讨了如何优化和正确配置Kafka Streams应用程序以确保准确的状态存储查询。通过调整配置参数和代码逻辑,可以有效解决数据不一致的问题。 ... [详细]
  • 本文详细介绍了Java中的访问器(getter)和修改器(setter),探讨了它们在保护数据完整性、增强代码可维护性方面的重要作用。通过具体示例,展示了如何正确使用这些方法来控制类属性的访问和更新。 ... [详细]
  • 本文提供了使用Java实现Bellman-Ford算法解决POJ 3259问题的代码示例,详细解释了如何通过该算法检测负权环来判断时间旅行的可能性。 ... [详细]
  • Java编程实践:深入理解方法重载
    本文介绍了Java中方法重载的概念及其应用。通过多个示例,详细讲解了如何在同一类中定义具有相同名称但不同参数列表的方法,以实现更灵活的功能调用。 ... [详细]
author-avatar
Amyb__ing舒
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有