深入理解YYCache缓存策略

article/2025/10/4 5:05:19

文章目录

  • 前言
  • 几个主要成员类
    • 1 YYCache
    • 2 YYMemoryCache
    • 3 YYDiskCache
  • 实例化
    • 1 实例方法
    • 2 构造器方法
    • 1 检查是否有缓存
    • 2 读缓存
    • 1 写内存缓存
    • 2 写磁盘缓存
    • 1 清空内存缓存
    • 2 清空磁盘缓存
  • YYMemoryCache 初始化做了什么
  • 总结
      • 本文完

前言

YYCache是著名iOS框架YYKit的一个组件是之一, 这里有作者对这个轮子的介绍, 同时有作者对主流的几个缓存框架的性能对比. 我们以YYCache为入口, 逐个分析每个api, 学习缓存如何设计.本文的思路, 同样适用于SDWebImage的缓存策略, 只是有些细节不太一样

下面是大神对缓存策略的基本描述是这样的

缓存通常一个缓存是由内存缓存和磁盘缓存组成,内存缓存提供容量小但高速的存取功能,磁盘缓存提供大容量但低速的持久化存储

来一张图, 概述框架的结构YYCache

几个主要成员类

1 YYCache

用来调用YYMemoryCache和YYDiskCache的外层接口, 我们直接使用的类, 两者在使用过程中对缓存开销, 缓存时长和数量都制定了一定的策略;

2 YYMemoryCache

内存缓存负责处理容量小, 相对高速的数据;并提供了自动和手动两张删除方式且是线程安全的, 且对异步回调提供了支持

  • _YYLinkedMap
    双向链表类; YYMemoryCache使用链接映射来存取
  • _YYLinkedMapNode
    双向节点类, 供_YYLinkedMap使用

3 YYDiskCache

磁盘缓存负责处理容量大, 相对低速的. 并提供了自动和手动两张删除方式且是线程安全的, 且对异步回调提供了支持. 同时, 它可以根据所存储的内容自动的选择合适的数据类型(文件/sqlite)来获取最高的性能;

  • YYKVStorage
    YYDiskCache的底层实现类, 基于sqlite和文件系统做键值存储, 用于管理磁盘缓存
  • YYKVStorageItem
    YYKVStorage用来存储数据的单元

实例化

YYCache是线程安全的, 其中YYMemoryCache将对象负责内存缓存, YYDiskCache负责磁盘缓存; 两种存储载体有明显的区别, 内存存取速度快且容量小, 磁盘存取速度慢但容量大!

YYCache is a thread safe key-value cache.

1 实例方法

这三个api是逐级调用的, 是实例方法, 不论调用哪个, 都会创建三个实例对象, 分别是YYCache, YYMemoryCache, YYDiskCache.

- (instancetype) init;
// 名称不要重复, 否则缓存不稳定
- (nullable instancetype)initWithName:(NSString *)name;
- (nullable instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;

2 构造器方法

这两个api是构造器方法创建实例; 和上面的实例方法不同的是, 这两个方法只创建了YYCache实例

+ (nullable instancetype)cacheWithName:(NSString *)name;
+ (nullable instancetype)cacheWithPath:(NSString *)path;

1 检查是否有缓存

// 若在内存缓存中查找到对应的缓存后就会返回YES;返回结果在当前线程
- (BOOL)containsObjectForKey:(NSString *)key {return [_memoryCache containsObjectForKey:key] || [_diskCache containsObjectForKey:key];
}
// 使用block方式回调在后台队列
- (void)containsObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key, BOOL contains))block {if (!block) return;if ([_memoryCache containsObjectForKey:key]) {dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{block(key, YES);});} else  {[_diskCache containsObjectForKey:key withBlock:block];}
}

1.1 检查内存
读取内存缓存的策略, 我们可以看到, 在读取数据之前, 用pthread_mutex_lock获取到了互斥锁, 当前线程被锁定, 直到获取到结果. 也就是说, 此方法调用后会阻塞线程直到文件读取完成. 并且需要注意的是, 这里作者使用了LRU 淘汰算法提升查找速度

- (BOOL)containsObjectForKey:(id)key {if (!key) return NO;pthread_mutex_lock(&_lock);BOOL contains = CFDictionaryContainsKey(_lru->_dic, (__bridge const void *)(key));pthread_mutex_unlock(&_lock);return contains;
}

1.2 检查磁盘
同样的, 在读取磁盘缓存时, 也使用锁操作保证安全, 即此方法也会阻塞当前线程, 直至文件读取完成.与查找内存缓存不同的是, 对磁盘缓存查找时使用了信号量来设置锁! 当信号量等于1时, 可以被当作锁来保证线程安全. 与自旋锁OSSpinLock不同的是, 使用信号量做锁不会消耗CPU资源(OSSpinLock会产生忙等), 适合用于不等待的存储过程.

// 信号量
#define Lock() dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER)
self->_lock: dispatch_semaphore_t _lock;
- (BOOL)containsObjectForKey:(NSString *)key {if (!key) return NO;Lock();BOOL contains = [_kv itemExistsForKey:key];Unlock();return contains;
}
// 检查是否有指定的key
// 拿key直接去sqlite中查询
- (BOOL)itemExistsForKey:(NSString *)key {if (key.length == 0) return NO;return [self _dbGetItemCountWithKey:key] > 0;
}

2 读缓存

同样的, 可以使用key取缓存值, 原理和上述1中大致相同

// 直接返回值
- (nullable id<NSCoding>)objectForKey:(NSString *)key;
// block返回
-(void)objectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, id<NSCoding> object))block;

需要注意的是, 如果我们在内存缓存中没有查找到对应的值, 但是在磁盘缓存中查找到值了, 会同时把该值缓存到内存缓存中去

object = [_diskCache objectForKey:key];
f (object) {// 缓存内存[_memoryCache setObject:object forKey:key];
}

同样的, 内存缓存查找时使用了LRU 淘汰算法对双向链表进行查找(_YYLinkedMapNode是一个双向节点类)

- (id)objectForKey:(id)key {if (!key) return nil;pthread_mutex_lock(&_lock);_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));if (node) {node->_time = CACurrentMediaTime();[_lru bringNodeToHead:node];}pthread_mutex_unlock(&_lock);return node ? node->_value : nil;
}
// 把缓存节点移动到链表头部, 原位置两侧的缓存要接上
// 并且原链表头部的缓存节点要变成现在链表的第二个缓存节点
- (void)bringNodeToHead:(_YYLinkedMapNode *)node {if (_head == node) return;if (_tail == node) {_tail = node->_prev;_tail->_next = nil;} else {node->_next->_prev = node->_prev;node->_prev->_next = node->_next;}node->_next = _head;node->_prev = nil;_head->_prev = node;_head = node;
}

在写操作的内部, 已经嵌入了更新操作

- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {[_memoryCache setObject:object forKey:key];[_diskCache setObject:object forKey:key];
}
// 有回调
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key withBlock:(void (^)(void))block {[_memoryCache setObject:object forKey:key];[_diskCache setObject:object forKey:key withBlock:block];
}

我们分别对内存缓存和磁盘缓存的写操作做一个详细的分析

1 写内存缓存

- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {if (!key) return;// 如果传入的值为空, 则根据key移除此缓存if (!object) {[self removeObjectForKey:key];return;}// 对当前线程添加互斥锁, 阻塞当前线程pthread_mutex_lock(&_lock);_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));NSTimeInterval now = CACurrentMediaTime();// 设置新的缓存节点至链表头部, 同时对原链表节点顺位后移if (node) {// 如果已经有值, 则更新其值, 同时保存新的时间和缓存开销_lru->_totalCost -= node->_cost;_lru->_totalCost += cost;node->_cost = cost;node->_time = now;node->_value = object;[_lru bringNodeToHead:node];} else {// 如果没有, 则新增节点同时添加对应的成员值node = [_YYLinkedMapNode new];node->_cost = cost;node->_time = now;node->_key = key;node->_value = object;// 把值和key映射保存到双向链表的某个节点中(CFDictionary是个底层容器)[_lru insertNodeAtHead:node];}// 如果开销超限, 则根据LRU算法异步的清除缓存, 直到总开销到达指定值if (_lru->_totalCost > _costLimit) {dispatch_async(_queue, ^{[self trimToCost:_costLimit];});}// 如果数量超限, 同样从尾部开始清除缓存if (_lru->_totalCount > _countLimit) {// 删除存在的尾部节点_YYLinkedMapNode *node = [_lru removeTailNode];// 如果支持异步释放if (_lru->_releaseAsynchronously) {// 判断是否支持主线程是释放// 是: 获取主队列, 否: 全局并发队列dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();dispatch_async(queue, ^{// 异步排队等待并释放节点缓存[node class]; //hold and release in queue});} else if (_lru->_releaseOnMainThread && !pthread_main_np()) {// 回到主线程释放dispatch_async(dispatch_get_main_queue(), ^{[node class]; //hold and release in queue});}}// 解除线程阻塞, 添加内存缓存完成pthread_mutex_unlock(&_lock);
}

2 写磁盘缓存

有回调的方法和此方法类似

- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {if (!key) return;if (!object) {// 和内存缓存一样 如果传入的值为空, 则根据key移除此缓存[self removeObjectForKey:key];return;}// 获取要存储对象所关联的扩展数据对象// 用户可以提前在外部调用setExtendedData类方法为要添加的值对象设置关联对象NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object];NSData *value = nil;// 如果有不支持NSCoding协议的数据需要存储, 则可以在外部转为NSDataif (_customArchiveBlock) {value = _customArchiveBlock(object);} else {@try {// 归档数据, 返回值是NSDatavalue = [NSKeyedArchiver archivedDataWithRootObject:object];}@catch (NSException *exception) {// 如果不是归档要求的类型就抛出异常// 我没有找到作者为何在此处捕获异常的目的, 在网上也没有找到相关描述// 如果有大佬知晓, 烦请告知, 非常感谢// nothing to do...}}if (!value) return;NSString *filename = nil;// 根据YYKVStorage的策略, 判断是用sqlite还是文件系统存储// _kv.type是根据初始化时传入的数据对象是否超否threshold阈值来判断是哪种类型的if (_kv.type != YYKVStorageTypeSQLite) {// 判断数据长度(字节)是否超过内联阈值 默认值是20kb(20480Byte)if (value.length > _inlineThreshold) {// 则存储为文件类型filename = [self _filenameForKey:key];}}// 信号量加锁, 阻塞当前线程Lock();// 开始存储// 如果是文件类型, 则会写入文件[_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData];// 存储完成解锁, 释放当前线程Unlock();
}
- (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value filename:(NSString *)filename extendedData:(NSData *)extendedData {if (key.length == 0 || value.length == 0) return NO;if (_type == YYKVStorageTypeFile && filename.length == 0) {return NO;}if (filename.length) {// 写入文件系统if (![self _fileWriteWithName:filename data:value]) {return NO;}// 把元数据以及关联的数据(如果有)写入dbif (![self _dbSaveWithKey:key value:value fileName:filename extendedData:extendedData]) {// 如果写入失败, 则文件管理器根据文件路径删除对应的文件[self _fileDeleteWithName:filename];return NO;}return YES;} else {if (_type != YYKVStorageTypeSQLite) {// 如果非sqlite类型// 则根据key从db中取出文件名, 并删除文件NSString *filename = [self _dbGetFilenameWithKey:key];if (filename) {[self _fileDeleteWithName:filename];}}// 否则 存储为sql类型数据return [self _dbSaveWithKey:key value:value fileName:nil extendedData:extendedData];}
}
// 私有方法, 判断是否有自定义的文件名加密方式
// 如果没有, 则默认对key MD5后形成字符串作为文件名
- (NSString *)_filenameForKey:(NSString *)key {NSString *filename = nil;if (_customFileNameBlock) filename = _customFileNameBlock(key);if (!filename) filename = _YYNSStringMD5(key);return filename;
}

清除缓存总共有五个api, 分别是

- (void)removeObjectForKey:(NSString *)key;
// 根据key清除缓存, 并异步回调
- (void)removeObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key))block;- (void)removeAllObjects;
// 清空所有缓存, 并异步回调
- (void)removeAllObjectsWithBlock:(void(^)(void))block;
// 清空所有缓存, 并实时回调删除清除进度, 完成后异步回调
// 只有磁盘缓存才有进度的回调
- (void)removeAllObjectsWithProgressBlock:(void(^)(int removedCount, int totalCount))progressendBlock:(void(^)(BOOL error))end {[_memoryCache removeAllObjects];[_diskCache removeAllObjectsWithProgressBlock:progress endBlock:end];}

在这里只分析清空的情况, 其实原理都差不多

1 清空内存缓存

内存缓存的清空比较简单

- (void)removeAllObjects {// 获取互斥锁, 阻塞线程pthread_mutex_lock(&_lock);// 执行清空操作[_lru removeAll];// 释放锁, 解除阻塞线程pthread_mutex_unlock(&_lock);
}
- (void)removeAll {// 清空总内存开销和数量_totalCost = 0;_totalCount = 0;// 清除头节点和尾节点_head = nil;_tail = nil;if (CFDictionaryGetCount(_dic) > 0) {CFMutableDictionaryRef holder = _dic;_dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);// 和读的时候类似, 需要判断是否支持异步释放if (_releaseAsynchronously) {// 获取队列dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();dispatch_async(queue, ^{// 在指定的队列中排队释放内存CFRelease(holder); // hold and release in specified queue});} else if (_releaseOnMainThread && !pthread_main_np()) {dispatch_async(dispatch_get_main_queue(), ^{// 主队列排队释放内存CFRelease(holder); // hold and release in specified queue});} else {// 同步的释放内存CFRelease(holder);}}
}

2 清空磁盘缓存

接下来看看磁盘缓存是如何清空的. 直接清空数据库且没有回调时,

  • 策略是先关闭db
  • 把文件全部移到垃圾桶
  • 异步串行清空垃圾桶
  • 打开数据库, 再init

原理都大同小异, 我们来讲一下带清空进度的情况.

- (void)removeAllObjectsWithProgressBlock:(void(^)(int removedCount, int totalCount))progressendBlock:(void(^)(BOOL error))end {__weak typeof(self) _self = self;dispatch_async(_queue, ^{__strong typeof(_self) self = _self;if (!self) {if (end) end(YES);return;}// 同样的, 是信号量锁住当前线程Lock();[_kv removeAllItemsWithProgressBlock:progress endBlock:end];Unlock();});
}
- (void)removeAllItemsWithProgressBlock:(void(^)(int removedCount, int totalCount))progressendBlock:(void(^)(BOOL error))end {// 获取总记录数int total = [self _dbGetTotalItemCount];if (total <= 0) {// 没有磁盘缓存, 直接回调if (end) end(total < 0);} else {int left = total;int perCount = 32;NSArray *items = nil;BOOL suc = NO;do {// 每次查找到d前32位数据items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount];// 遍历数据, 挨个清除for (YYKVStorageItem *item in items) {if (left > 0) {// 清除文件类型数据if (item.filename) {[self _fileDeleteWithName:item.filename];}// 清除sql类型数据suc = [self _dbDeleteItemWithKey:item.key];left--;} else {// 直到清除完毕break;}if (!suc) break;}// 每删除完32条数据, 回调一次进度if (progress) progress(total - left, total);} while (left > 0 && items.count > 0 && suc);// 检查db状态if (suc) [self _dbCheckpoint];if (end) end(!suc);}
}

YYMemoryCache 初始化做了什么

现在我们来分析一下, YYMemoryCache的初始化过程, 来更进一步的了解其原理(YYDiskCache有些区别, 但是大致思想是相同的)

- (instancetype)init {self = super.init;// 创建了一个互斥锁, 并赋值给锁成员pthread_mutex_init(&_lock, NULL);// 初始化双向链表, 并赋值给成员_lru = [_YYLinkedMap new];// 初始化串行队列, 并赋值给成员_queue = dispatch_queue_create("com.ibireme.cache.memory", DISPATCH_QUEUE_SERIAL);// 缓存数量_countLimit = NSUIntegerMax;// 缓存开销_costLimit = NSUIntegerMax;// 缓存过期时间_ageLimit = DBL_MAX;_autoTrimInterval = 5.0;// 这两个是什么意思呢// 这里用到了观察者模式, 把当前对象设置为观察者并注册到通知中心// 如果观察者(self)收到内存警告是否可以清除缓存, 默认YES_shouldRemoveAllObjectsOnMemoryWarning = YES;// 收到内存警告后的清除策略, 默认在后台清除_shouldRemoveAllObjectsWhenEnteringBackground = YES;[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidReceiveMemoryWarningNotification) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidEnterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil];// 后面内部会设置一些其他的配置// 如设置缓存限制, 数量限制, 时间限制等// 如autoTrimInterval设置自动检测内存的时间, 并由yycache维护的定时器进行管理(但是我并没有找到这个定时器, 哪位大佬告知一下)// 这些操作都是异步串行队列中延时执行[self _trimRecursively];return self;
}

总结

我们上面简单讲述了YYCache组件的各个api的调用流程; 我们来简单总结一下, 这个轮子带给我们学习的地方(学习的地方太多了, 重点讲几个), 以及需要注意的地方
1 大家知道如果想高效的查询数据, 使用字典是一个很好的方法; 在数据结构中, 我们可以把字典理解为一个哈希表, 通过key和内存地址进行映射, 同时这中数据结构可以为后面的哈希查找等算法提供高效的支持.
2 不论是内存缓存还是磁盘缓存, 思想都是值得我们借鉴的. 比如 把key进行md5转换作为文件名映射到内存中等. 比如查找策略, 利用LRU算法提高命中率. 在SDWebImage中的实现思想也有相似的地方, 我们自己可以做一些对比;
3 说一个我发现的问题,但是我并没有验证, 只做一个猜测! 不论是YYWebImage还是SDWebImage的缓存策略, 当没有内存超限的时候都不会对内存进行释放, 如果我们对内存进行优化的过程中, 可以考虑主动的清除一些缓存, 以防在内存敏感的页面引发问题.
4 对于想了解其中算法(LRU和MRU)的同学请移步大佬写的常用淘汰算法;

本文完

我是个Coder界的小学生, 如有不足, 万望不吝指教

转载请注明作者和链接哦!
参考资料:
YYCache 设计思路


http://chatgpt.dhexx.cn/article/O7CBStDd.shtml

相关文章

Unity OnDestroy 调用

Test Code private GameObject _temp;_temp Instantiate(Resources.Load("gameObject original"), parent) as GameObject;if (Input.GetMouseButtonDown(1)) {Destroy(_temp);_temp null; }试验结果&#xff08;场景中本来存在的gameObjec或Resources出来的game…

报错原因高的地图调用mapView.onDestroy() 崩溃问题

以前在android app中使用地图的项目需要使用高德地图。 按照高德地图的开发文档创建测试项目&#xff0c;导入依赖&#xff0c;很快就成功显示了地图&#xff0c;然后在退出地图Activity时&#xff0c;app立即崩溃&#xff0c;通过追踪&#xff0c;发现是在销毁地图时出现崩溃…

Android跨进程通信Client Crash后Server端onDestroy

hi&#xff0c;粉丝朋友大家好&#xff01; 好久没有给大家写blog了&#xff0c;哈哈&#xff0c;这里说声抱歉&#xff01;实在家里比较忙&#xff0c;今天就来给大家分享一个跨进程专题课中学员问的一个问题&#xff0c;blog就来解答一下这个问题。 问题背景&#xff1a; 视频…

Activity onDestroy延迟回调

前端时间工作的时候遇到了两个奇怪的问题&#xff1a; 使用百度步行导航的时候&#xff0c;开启导航后立即退出&#xff0c;再次进入的时候就会黑屏&#xff1b;使用度小满支付的时候&#xff0c;当支付成功后页面一直显示loading&#xff0c;过了10s左右才恢复正常。 这看似…

基于MFC的OpenDDS发布订阅例子(PubSubDemo)

在编译完成Message.idl,产生MessageCommon.dll和相应的MessageTypeSupport的.h头文件和.cpp文件(MessageTypeSupportImpl.h、MessageTypeSupportC.h)的基础上,新建PubSubDemo.sln和工程PubSubDemo.vcxproj,并开始编码,实现基于Message的发布和订阅流程。 1)新建基于Dia…

OPenDDS程序 的 实现+运行

标题DDS程序实现和运行 本文记录了Windows10环境下OpenDDS环境搭建&#xff0c;idl自定义&#xff0c;代码生成&#xff0c;代码编写的全过程。 一、环境搭建 1.详细情况请参考开发笔记&#xff1a;1. OpenDDS环境搭建-Windows 10.note 编译好后生成了两个文件夹“OpenDDS-3.…

OpenDDS-1

转自&#xff1a;软件开发.OpenDDS 设计智能座舱时ECU之间通信及与TSP通信选择使用OpenDDS是可以的&#xff0c;因此不少人都认为OpenDDS是属于汽车以太网&#xff08;Aumotive Ethernet&#xff0c;AE&#xff09;&#xff0c;但事实上autosar AE中定义中有SomeIP、DoIP、AVB…

Java程序调用OpenDDS

一、前言 前面我们用三篇博客介绍了 OpenDDS在WIndows上的环境配置 Windows下的OpenDDS编译&#xff08;超详细&#xff09;_山中野竹的博客-CSDN博客_opendds windows 三种方式运行发布订阅示例程序 OpenDDS运行示例&#xff08;Messenger&#xff09;程序_山中野竹的博客-C…

OpenDDS运行实例

因为OpenDDS是分布式的部署&#xff0c;所以一般发布端和订阅端都不在同一台电脑上。 我在同一台电脑上进行测试&#xff0c;所以ip地址为&#xff1a;127.0.0.1 1.发布端 1.1 新建ior文件 在根目录先新建repo.ior文件&#xff1a; IOR:010000001e00000049444c3a4f70656e44…

Java调用OpenDDS(1)-编译安装openDDS-补上了所有网络上其他文章遗漏的细节

Java调用OpenDDS过程中踩了很多坑&#xff0c;记录一下。 提纲 1、DDS简介 2、DDS协议的实现产品 3、OpenDDS安装过程 1、DDS简介 DDS指的是Data Distribution Service&#xff0c;也即数据分发服务&#xff0c;是OMG&#xff08;Object Management Group&#xff0c;对象管理…

OpenDDS自学

前言 最近做毕设要做一个DDS系统和TISA系统的网关&#xff0c;完全没有基础&#xff0c;只好对着OpenDDS的Developers’ Guide和《分布式系统实时发布/订阅数据分发技术》这本书一点一点学(顺便吐槽这本书就是guide的翻译版&#xff0c;很多语句不通)。遇到很多问题&#xff0…

VS2015编译OpenDDS

最近需要研究下OpenDDS,因此需要搭建个环境&#xff0c;下面是一点经验&#xff0c;大家可以参考。 使用版本是OpenDDS-3.12、ACETAO-6.5.10和strawberry-perl&#xff0c;之所以使用ACETAO-6.5.10是因为往后的版本没有现成的2015对应的sln了。 一.资源下载 1.可以直接使用我…

Java调用OpenDDS(2)-理解OpenDDS自带的Messager示例

OpenDDS安装好之后&#xff0c;下一步就是利用OpenDDS来开发通信项目了。不过在项目中应用OpenDDS之前&#xff0c;先消化一下OpenDDS安装包中自带的示例项目messenger&#xff0c;通过阅读messenger的源代码来熟悉一下OpenDDS提供的用来开发Java项目的类。 提纲 1、准备工作 2…

OpenDDS

OpenDDS简介 Don Busch&#xff0c;首席软件工程师兼合作伙伴 Object Computing&#xff0c;Inc.&#xff08;OCI&#xff09; 介绍 分布式实时应用程序有时以数据为中心而不是以服务为中心&#xff0c;这意味着分布式系统中参与者的主要目标是分发应用程序数据&#xff0c;而…

OpenDDS系列(3) —— 第一个OpenDDS程序

文章目录 [toc]3.1 发送数据3.2 项目3.2.1 主题3.2.2 Publisher&#xff08;发布者&#xff09;3.2.3 Subscriber&#xff08;订阅者&#xff09; 3.3 在Windows上构建3.4 在Linux上构建3.4.1 运行 3.5 结论 3.1 发送数据 我们将创建一个主题&#xff0c;这是一个通过DDS交换数…

OpenDDS学习笔记(2):DDS概述

文章目录 一、DDS体系结构1.1 DLRL层1.2 DCPS层 二、DDS通信过程三、DDS通信特点四、DDS标准实现4.1 RTI DDS软件4.2 OpenSplice DDS软件4.3 OpenDDS软件 一、DDS体系结构 DDS采用DCPS通信机制&#xff0c;提供一个与平台无关的数据模型。它允许应用程序实时发布拥有的信息&am…

OpenDDS系列(1) —— OpenDDS 简介

1. OpenDDS简要介绍 1.1 简介 1.1.1 DDS是什么1.1.2 DDS通信的基本要素1.1.3 DDS架构的主要优点1.1.4 DDS产品种类1.1.5 OpenDDS 1.2 DDS的应用领域 美国海上战争中心(NSWC)高性能分布式计算系统&#xff08;HiPer-D&#xff09; 1.3 结论 1. OpenDDS简要介绍 1.1 简介 1.1.…

IDEA中查找与替换快捷键(项目全局替换、该文件下替换)

该文件下查找&#xff08;CtrlF&#xff09; 项目全局查找&#xff08;CtrlShiftF 或【Edit】——>【Find】——>【Find in Path…】&#xff09; 注意&#xff1a;本人电脑上的IDEA版本不支持该快捷键&#xff08;CtrlShiftF&#xff09;&#xff0c;有可能是快捷键冲突…

idea实现快捷批量修改替换

1. 在当前文件内容中替换 idea替换快捷键&#xff0c;批量处理对象 ctrl r: 当前文件内容替换&#xff0c;指的是在当前打开的文件中替换匹配的字符&#xff0c;只操作一个文件。 2. 在路径中替换(可替换不同文件夹中的内容) ctrl shift r: 在路径中替换&#xff0c;指的是…

idea 查找与替换

查找当前文件内容&#xff1a;ctrlF 如上图片 查找全局文件&#xff1a;ctrlshiftF 或double shift&#xff08;按两下&#xff09;或ctrlshiftN替换当前文件内容 &#xff1a;ctrlR 如上图片 你想通过编辑器快速的将所有的’29’&#xff0c;变为29&#xff0c;你可以 ctrl…