前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【C++指南】哈希驱动的封装:如何让unordered_map/set飞得更快更稳?【上】

【C++指南】哈希驱动的封装:如何让unordered_map/set飞得更快更稳?【上】

作者头像
egoist祈
发布于 2025-04-19 14:23:31
发布于 2025-04-19 14:23:31
4400
代码可运行
举报
文章被收录于专栏:egoistegoist
运行总次数:0
代码可运行

引入 :直接定址法

在现实生活中,我们往往会将一类东西跟另一种东西进行绑定,且这种 关系具有一定的联系 。 在计算机当中也是必然,如“left”的中文意思是“左边”,“string”的中文意思是“字符串”等等。而对于每个数字都有对应存储的下标。 当 关键字的范围⽐较集中 时,⽐如⼀组关键字都在[0,99]之间,那么我们开⼀个100个数的数组,每个关键字的值直接就是存储位置的下标。 但是如果 一组关键字比较分散 ,如只出现了1、20、99时,此时要开100空间的数组有97个空间会被浪费,这显然不是我们期望的。因此,关于一段 哈希的故事就此展开

哈希

哈希(hash)又称散列,是⼀种组织数据的⽅式。从译名来看,有散乱排列的意思。本质就是通过哈希函数把关键字Key跟存储位置建⽴⼀个映射关系,查找时通过这个哈希函数计算出Key存储的位置,进⾏快速查找。

哈希函数

⼀个好的哈希函数应该让N个关键字被等概率的均匀的散列分布到哈希表的M个空间中,但是实际中却很难做到。因此我们要尽量往这个⽅向去考量设计。

除法散列法(除留余数法)

当数据比较分散的情况下,用直接定址法是无法很好地处理问题的,那是否能仅用较小地空间让保证所有的值都映射到该空间上来呢(保证空间大于值数量)?于是有人提出了除法散列法的概念并对此进行了说明。

除法散列法也叫做除留余数法,假设哈希表的大小为M,那么通过key除以M的余数作为映射位置的下标,也就是哈希函数为:h(key) = key % M。(这样即能保证所有的值都在这个空间上)

哈希冲突和负载因子

当使⽤除法散列法时,要 尽量避免M为某些值 ,如2的幂,10的幂等。 如果是 ,那么key %2 ^X 本质相当于保留key的后X位,那么后x位相同的值,计算出的哈希值都是⼀样的,就冲突了。如:{63 , 31}看起来没有关联的值,如果M是16,即2^4,保留后4位,因为63的⼆进制后8位是 00111111,31的⼆进制后8位是 00011111,后四位都是相同的,那么都会映射到同一个空间上去,这样就产生了冲突,即哈希冲突。 因此 当使⽤除法散列法时,建议M取不太接近2的整数次幂的⼀个质数(素数) 。 负载因子: 假设哈希表中已经映射存储了N个值,哈希表的⼤⼩为M,M一定要大于,那么负载因子 = N/M,保证负载因子小于1。 负载因⼦越⼤,说明M是接近于N的,则空间利⽤率越⾼,相对地哈希冲突的概率越⾼; 负载因⼦越⼩,说明M的空间很大,则空间利用率低,相对地哈希冲突的概率越低。

处理哈希冲突

实践中哈希表⼀般还是选择除法散列法作为哈希函数,当然哈希表 ⽆论选择什么哈希函数也避免不了冲突 ,那么插⼊数据时,如何解决冲突呢?主要有两种两种⽅法,开放定址法和链地址法。

开放定址法

在开放定址法中所有的元素都放到哈希表⾥,当⼀个关键字key⽤哈希函数计算出的位置冲突了,则按照某种规则找到⼀个没有存储数据的位置进⾏存储,开放定址法中负载因⼦⼀定是⼩于的。这⾥的规则有三种: 线性探测、⼆次探测、双重探测(自行了解) 。(哈希表有三种状态表示: 存在、空、删除

开放定址法的哈希表结构

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
enum Status
{
	EMPTY,
	EXIST,
	DELETE
};

template<class K, class V>
struct HashData
{
	pair<K, V> _kv;
	Status _status = EMPTY;
};

template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
public:
    HashTable(size_t size = 11)
		:_tables(size)
		, _n(0)
	{}

    //...

private:
	vector<HashData<K, V>> _tables;
	size_t _n;
};
优化

但是上面的实现并不是那么好,如果当映射的元素_n/_tables.size()大于负载因子时,显然是需要扩容的,如果选择2倍扩容,原本的11空间变为22空间,看似没有毛病。但前面我们说过,使用除法散列法时,要尽量避免M为某些值,即取不太接近2的整数次幂的⼀个质数。因此,不能直接地选择2倍扩容地方式来放大空间。那代码又该如何实现呢?

  1. 提前造好一些数据,这些数据得保证是质数,且是不太接近2的整数次幂的⼀个质数;
  2. 每次扩容的时候都往我们造好的数据上进行扩容。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
  53,         97,         193,       389,       769,
  1543,       3079,       6151,      12289,     24593,
  49157,      98317,      196613,    393241,    786433,
  1572869,    3145739,    6291469,   12582917,  25165843,
  50331653,   100663319,  201326611, 402653189, 805306457,
  1610612741, 3221225473, 4294967291
};

inline unsigned long __stl_next_prime(unsigned long n)
{
	const unsigned long* first = __stl_prime_list;
	const unsigned long* last = __stl_prime_list + __stl_num_primes;
	const unsigned long* pos = lower_bound(first, last, n);
	return pos == last ? *(last - 1) : *pos;
}

enum Status
{
	EMPTY,
	EXIST,
	DELETE
};

template<class K, class V>
struct HashData
{
	pair<K, V> _kv;
	Status _status = EMPTY;
};

template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
public:
	HashTable(size_t size = __stl_num_primes)
		:_tables(size)
		, _n(0)
	{}

    //...

private:
	vector<HashData<K, V>> _tables;
	size_t _n;
};

在上面这段程序中,哈希表默认初始空间是28,当大于负载因子时进行扩容,第一次扩容是53,第二次是97…… ,可以发现,每次扩容都是以近乎2倍扩容且满足不太接近2的整数次幂的⼀个质数。这种手动造数据的方法看似笨拙,实则这难道不是利用其特性下的一种取巧吗

线性探测
  • 在映射数据的时候可能会存在哈希冲突,此时从发⽣冲突的位置开始,依次线性向后探测,直到寻找到下⼀个没有存储数据的位置为⽌,如果⾛到哈希表尾,则回绕到哈希表头的位置。
  • h(key) = hash0 = key % M,hash0位置冲突了,则线性探测公式为:hc(key,i) = hashi = (hash0 + i) % Mi = {1, 2, 3, ..., M − 1},保证线性探测时能从队尾走到队头,且因为负载因⼦小于1,则最多探测M-1次,⼀定能找到⼀个存储key的位置。
  • 可以发现线性探测的问题会占用其他值可能映射到的空间,会导致原本不冲突的值产生哈希冲突。严重的话肯呢个会使多个hash0,hash1,hash2,hash3的值都争夺hash3位置,这种现象叫做群集/堆积。
二次探测(了解)

存在哈希冲突是必然的,但有什么比较好的方法可以减少哈希冲突的现象呢?

  • 从发⽣冲突的位置开始,依次左右按⼆次⽅跳跃式探测,直到寻找到下⼀个没有存储数据的位置为止,如果往右⾛到哈希表尾,则回绕到哈希表头的位置;如果往左⾛到哈希表头,则回绕到哈希表尾的位置;
  • h(key) = hash0 = key % M , hash0位置冲突了,则⼆次探测公式为:hc(key,i) = hashi = (hash0 ± i^2 ) % Mi = {1, 2, 3, ...,M/2 };
  • ⼆次探测当 hashi = (hash0 − i 2 )%M 时,当hashi<0时,需要hashi += M。

线性探测实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
bool Insert(const pair<K, V>& kv)
{
	size_t hash0 = kv.first % _tables.size();
	size_t hashi = hash0;
	size_t i = 1;

	//如果该点存在 --> 线性探测
	while (_tables[hashi]._status == EXIST)
	{
		hashi = (hashi + i) % _tables.size();
		i++;
	}

	_tables[hash0]._kv = kv;
	_tables[hash0]._status = EXIST;
	++_n;

	return true;
}
扩容

方案一:新建一个哈希表,遍历旧表让里面的数据重新映射到新表当中;

方案二:采用复用的手段将旧表数据映射到新表中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if ((double)_n / (double)_tables.size() > 0.7)
{
	HashTable<K, V, Hash> newHT(__stl_next_prime(_tables.size() + 1));
	for (size_t i = 0;i < _tables.size();i++)
	{
		if (_tables[i]._status == EXIST)
		{
			newHT.Insert(_tables[i]._kv);
		}
	}
	_tables.swap(newHT._tables);
}
查找和删除

查找规律:巧妙利用线性探测的特性

删除规律:采用Find函数寻找该值是否存在,如果存在标记为del,返回true;否则,返回false。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	HashData<K, V>* Find(const K& key)
	{
		size_t hash0 = key % _tables.size();
		size_t hash1 = hash0;
		size_t i = 1;
		while (_tables[hash1]._status != EMPTY)
		{
			if (_tables[hash1]._kv.first == key && _tables[hash1]._status != DELETE)
			{
				return &_tables[hash1];
			}

			hash1 = (hash1 + i) % _tables.size();
			++i;
		}
		return nullptr;
	}

	bool Erase(const K& key)
	{
		HashData<K, V>* ret = Find(key);
		if (ret)
		{
			ret->_status = DELETE;
			return true;
		}
		else
		{
			return false;
		}
	}
代码实现
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
  53,         97,         193,       389,       769,
  1543,       3079,       6151,      12289,     24593,
  49157,      98317,      196613,    393241,    786433,
  1572869,    3145739,    6291469,   12582917,  25165843,
  50331653,   100663319,  201326611, 402653189, 805306457,
  1610612741, 3221225473, 4294967291
};

inline unsigned long __stl_next_prime(unsigned long n)
{
	const unsigned long* first = __stl_prime_list;
	const unsigned long* last = __stl_prime_list + __stl_num_primes;
	const unsigned long* pos = lower_bound(first, last, n);
	return pos == last ? *(last - 1) : *pos;
}

enum Status
{
	EMPTY,
	EXIST,
	DELETE
};

template<class K, class V>
struct HashData
{
	pair<K, V> _kv;
	Status _status = EMPTY;
};

template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
public:
	HashTable(size_t size = __stl_num_primes)
		:_tables(size)
		, _n(0)
	{}

	bool Insert(const pair<K, V>& kv)
	{
		//扩容 --> 负载因子大于0.7
		if ((double)_n / (double)_tables.size() > 0.7)
		{
			HashTable<K, V, Hash> newHT(__stl_next_prime(_tables.size() + 1));
			for (size_t i = 0;i < _tables.size();i++)
			{
				if (_tables[i]._status == EXIST)
				{
					newHT.Insert(_tables[i]._kv);
				}
			}
			_tables.swap(newHT._tables);
		}

		size_t hash0 = kv.first % _tables.size();
		size_t hashi = hash0;
		size_t i = 1;

		//如果该点存在 --> 线性探测
		while (_tables[hashi]._status == EXIST)
		{
			hashi = (hashi + i) % _tables.size();
			i++;
		}

		_tables[hash0]._kv = kv;
		_tables[hash0]._status = EXIST;
		++_n;

		return true;
	}

	HashData<K, V>* Find(const K& key)
	{
		size_t hash0 = key % _tables.size();
		size_t hash1 = hash0;
		size_t i = 1;
		while (_tables[hash1]._status != EMPTY)
		{
			if (_tables[hash1]._kv.first == key && _tables[hash1]._status != DELETE)
			{
				return &_tables[hash1];
			}

			hash1 = (hash1 + i) % _tables.size();
			++i;
		}
		return nullptr;
	}

	bool Erase(const K& key)
	{
		HashData<K, V>* ret = Find(key);
		if (ret)
		{
			ret->_status = DELETE;
			return true;
		}
		else
		{
			return false;
		}
	}

private:
	vector<HashData<K, V>> _tables;
	size_t _n;
};
链地址法

开放定址法无论怎样势必会造成哈希冲突,因其 占⽤的 都是哈希表中的空间,始终存在互相影响问题。那如何解决这种弊端呢?

链地址法为了解决这种冲突,不是再将所有的数据直接存储在哈希表中,而是通过指针的方式让每一个值都映射到对应空间,即使产生冲突会用指针的方式将它们悬挂起来,挂在哈希表该位置下面,当然我们形象地称这种方法为拉链法或哈希桶。

但是在一些极端场景下,如有人恶意造出一段数据让它都映射到同一个位置,导致某个桶特别长,查询时间复杂度达到了O(N)。有大佬提出,如果这个桶的长度超过⼀定阀值(8)时就把链表转换成红⿊树。(用全域散列法也可)

链地址法的结构实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    //手动造数据见上面

	template<class K,class V>
	struct HashNode
	{
		pair<K, V> _kv;
		HashNode<K, V>* _next;

		HashNode(const pair<K, V>& kv)
			:_next(nullptr)
			,_kv(kv)
		{}
	};

	template<class K, class V>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		HashTable(size_t size = __stl_next_prime(0))
			:_tables(size, nullptr)
			,_n(0)
		{}

	private:
		vector<Node*> _tables;
		size_t _n;
	};
特殊情况:插入元素不是数字

如果插入元素是浮点数、负数情况呢?

仿函数的作用再次体现出来了,无论是整数、浮点数、负数,统一强转成正数来处理,找到该映射的位置,对查找、删除等操作都不会受到影响,因为我们同样需要将目标值强制转成正数映射到对应位置。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	//仿函数
	template<class K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key; // --> 针对浮点、负数等情况
		}
	};

针对字符串

但是字符串的处理,并不能直接强转成正数来处理,这并不被允许。因此,在这里采用特化的思想进行特殊处理,实现如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	//针对字符串
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& key)
		{
			size_t hash0 = 0;
			for (auto& ch : key)
			{
				//对不同字符串但hash0相同的处理,减少冲突
				hash0 *= 131;
				hash0 += ch;
			}
			return hash0;
		}
	};
改动
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template<class K, class V, class Hash = HashFunc<K>>
扩容

开放定址法负载因⼦必须⼩于1,链地址法的负载因⼦就没有限制了。 在stl中 的实现 最⼤负载因⼦基本控制在1,⼤于1就扩容,我们同样采用此逻辑进行扩容。

扩容操作时,开一个新的哈希表,需要将旧表上的值都重新映射到新表中。

  1. 方法一:由于是链地址法的实现,一个位置下可能会挂着多个值(即哈希桶),那么就需要遍历该位置中桶的每个结点,将每个结点的值重新映射到新表中,而每次操作都需要申请新结点,并把旧表中该结点释放掉。显然,这种方式是非常浪费的,且效率非常低效。
  2. 方法二:正是由于采用的是链地址法的实现,由于哈希表中每个位置都是一个指针,那么我们只需要遍历旧表中结点映射在新表中的位置,使其指向旧表中该结点。最后,将旧表置为空,交换新旧链表,完成扩容操作。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
		bool Insert(const pair<K, V>& kv)
		{
			//不允许冗余
			if (Find(kv.first))
				return false;

			Hash hs;
			//需要扩容
			if (_n == _tables.size())
			{
				// 也可以,但是扩容新开辟节点,释放旧节点,有点浪费
				/*HashTable<K, V> newHT(__stl_next_prime(_tables.size() + 1));
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						newHT.Insert(cur->_kv);
						cur = cur->_next;
					}
				}
				_tables.swap(newHT._tables);*/

				vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
				//遍历旧表
				for (size_t i = 0;i < _tables.size();i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						//将旧表的每个结点插在新表中
						Node* next = cur->_next;
						size_t hash0 = hs(cur->_kv.first) % newtables.size();
						cur->_next = newtables[hash0];
						newtables[hash0] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newtables);
			}

			size_t hash0 = hs(kv.first) % _tables.size();
			Node* newnode = new Node(kv);

			//头插
			newnode->_next = _tables[hash0];
			_tables[hash0] = newnode;
			++_n;

			return true;
		}
删除
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        bool Erase(const K& key)
		{
			Hash hs;

			size_t hash0 = hs(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hash0];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prev == nullptr)
					{
						_tables[hash0] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}

					--_n;
					delete cur;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}
代码实现
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	static const int __stl_num_primes = 28;
	static const unsigned long __stl_prime_list[__stl_num_primes] =
	{
	  53,         97,         193,       389,       769,
	  1543,       3079,       6151,      12289,     24593,
	  49157,      98317,      196613,    393241,    786433,
	  1572869,    3145739,    6291469,   12582917,  25165843,
	  50331653,   100663319,  201326611, 402653189, 805306457,
	  1610612741, 3221225473, 4294967291
	};

	inline unsigned long __stl_next_prime(unsigned long n)
	{
		const unsigned long* first = __stl_prime_list;
		const unsigned long* last = __stl_prime_list + __stl_num_primes;
		const unsigned long* pos = lower_bound(first, last, n);
		return pos == last ? *(last - 1) : *pos;
	}

	template<class K,class V>
	struct HashNode
	{
		pair<K, V> _kv;
		HashNode<K, V>* _next;

		HashNode(const pair<K, V>& kv)
			:_next(nullptr)
			,_kv(kv)
		{}
	};

	//仿函数
	template<class K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key; // --> 针对浮点、负数等情况
		}
	};

	//针对字符串
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& key)
		{
			size_t hash0 = 0;
			for (auto& ch : key)
			{
				//对不同字符串但hash0相同的处理,减少冲突
				hash0 *= 131;
				hash0 += ch;
			}
			return hash0;
		}
	};

	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		HashTable(size_t size = __stl_next_prime(0))
			:_tables(size, nullptr)
			,_n(0)
		{}

		bool Insert(const pair<K, V>& kv)
		{
			//不允许冗余
			if (Find(kv.first))
				return false;

			Hash hs;
			//需要扩容
			if (_n == _tables.size())
			{
				// 也可以,但是扩容新开辟节点,释放旧节点,有点浪费
				/*HashTable<K, V> newHT(__stl_next_prime(_tables.size() + 1));
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						newHT.Insert(cur->_kv);
						cur = cur->_next;
					}
				}
				_tables.swap(newHT._tables);*/

				vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
				//遍历旧表
				for (size_t i = 0;i < _tables.size();i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						//将旧表的每个结点插在新表中
						Node* next = cur->_next;
						size_t hash0 = hs(cur->_kv.first) % newtables.size();
						cur->_next = newtables[hash0];
						newtables[hash0] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newtables);
			}

			size_t hash0 = hs(kv.first) % _tables.size();
			Node* newnode = new Node(kv);

			//头插
			newnode->_next = _tables[hash0];
			_tables[hash0] = newnode;
			++_n;

			return true;
		}

		Node* Find(const K& key)
		{
			Hash hs;

			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
					return cur;

				cur = cur->_next;
			}

			return nullptr;
		}

		bool Erase(const K& key)
		{
			Hash hs;

			size_t hash0 = hs(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hash0];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prev == nullptr)
					{
						_tables[hash0] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}

					--_n;
					delete cur;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}

		~HashTable()
		{
			for (size_t i = 0;i < _tables.size();i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}

	private:
		vector<Node*> _tables;
		size_t _n;
	};
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-04-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Xcode插件-Alcatraz安装
      Alcatraz 是一个帮你管理 Xcode 插件、模版以及颜色配置的工具。它可以直接集成到 Xcode 的图形界面中,让你感觉就像在使用 Xcode 自带的功能一样。
hrscy
2018/08/30
9380
Xcode插件-Alcatraz安装
使用Alcatraz管理Xcode插件
~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/
用户3004328
2018/09/06
5370
使用Alcatraz管理Xcode插件
Xcode8时代让我们一起继续使用我们的插件吧
第一步复制一份你从AppStore下载最新Xcode8。正版授权的我们命名为Xcode_Release用于我们发版本使用。复制出来的一份我们改成Xcode.
君赏
2018/08/31
6000
Xcode8时代让我们一起继续使用我们的插件吧
Xcode中插件的管理工具-----Alcatraz
Alcatraz 是一款 Xcode的插件管理工具,可以用来管理XCode的 插件、模版以及颜色配置的工具。
用户1451823
2018/09/13
1.2K0
Xcode中插件的管理工具-----Alcatraz
Xcode炫酷插件
1.首先前往https://github.com/alcatraz/Alcatraz 下载Xcode插件管理工具Alcatraz 2.下载完成后,退出Xcode,使用终端命令来进行安装Alcatraz: 安装Alcatraz: <pre>curl -fsSL https://raw.github.com/supermarin/Alcatraz/master/Scripts/install.sh | sh</code></pre> 删除Alcatraz: <pre>rm -rf ~/Library
且行且珍惜_iOS
2018/05/22
2K0
Mac 全栈开发-Alcatraz
Alcatraz是一款开源的XCode包管理插件,你可以利用它安装主题皮肤等其他插件。
用户1065635
2019/03/21
4500
Xcode8 最快最方便的安装插件方案
自从Xcode8出来后,为了安全起见,给Xcode安装插件就惨遭苹果封杀,随后出现很多解决方案,其中有一种比较完美的�方案: 教你如何科学的在Xcode8上使用插件,但是用过这个方案的同学会发现每次运行并安装插件之前需要添加当前Xcode的DVTPlugInCompatibilityUUID,相当麻烦,而且安装完这个插件,上个或者上上个插件就失效了(随机的,也可能不会),不知道大家有没有遇到,反正我是遇到好多次~~最要命的是还要拷贝一份Xcode用来上架专用,对于我这种256G的本子来说还是相当无奈的
LinXunFeng
2018/06/29
6150
兼容-记录Xcode8.0恢复插件全过程
Xcode 的插件大大丰富了 Xcode 的功能,而且有了 Alcatraz ,插件的管理也非常容易,但是有个非常恼人的问题:一旦升级 Xcode ,插件就失效!终于有时间来写下自己恢复Xcode8.0插件的全过程了。也算无语,我在回复插件的时候尝试了两个不同的方法。不管怎样最后还是回复了插件的使用了。
進无尽
2018/09/12
1.2K0
兼容-记录Xcode8.0恢复插件全过程
Mac 上执行命令报错解决方案
mac 执行 git 命令时候出现 invalid active developer path :
zucchiniy
2020/05/22
1.1K0
Mac 卸载Java「建议收藏」
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/144486.html原文链接:https://javaforall.cn
全栈程序员站长
2022/08/31
1.4K0
Mac 卸载Java「建议收藏」
xcode7中KSImageNamed-Xcode-master插件无法使用问题的解决
Xcode 升级到7之后VVDocumenter-Xcode,OMColorSense,KSImageNamed等一系列的插件失效的解决办法,以及不小心误点了 Skipbundle 的解决办法。
用户1451823
2018/09/13
1.1K0
xcode7中KSImageNamed-Xcode-master插件无法使用问题的解决
Xcode 清理存储空间
Xcode版本:8.3.3 iOS版本:10.3.2 移除 Xcode 运行安装 APP 产生的缓存文件(DerivedData) 只要重新运行Xcode就一定会重新生成,而且会随着运行程序的增多,占用空间会越来越大。删除后在重新运行程序可能会稍微慢一点,建议定期清理。 路径: ~/Library/Developer/Xcode/DerivedData 释放空间:0~xx GB ---- 移除 APP 打包的ipa历史版本(Archives) 删除后不可恢复,文件夹是按照日期排列的,所以如果你不想全部
用户1890628
2018/05/10
4K0
写个自己的Xcode4插件
刚写iOS程序的时候就知道Xcode支持第三方插件,比如ColorSense等很实用的插件,但Xcode的插件开发没有官方的文档支持,一直觉得很神秘,那今天就来揭开它的面纱。
JoeyBlue
2021/09/07
3590
linux 安装及使用 composer
报错的原因是我 php 安装了 suhosin 扩展,解决方法,报错中已给出,就是在 php.ini 文件中添加
PHP开发工程师
2022/06/08
1.6K0
linux 安装及使用 composer
Final Cut Pro X效果插件开发总结
一、介绍       最近公司需要针对Final Cut Pro(FCP)开发一款效果插件,用于对公司自己开发的视频格式进行后期处理。Final Cut Pro是苹果公司推出的一款视频剪辑软件,因此需要在OSX平台上进行开发。目前最新版本的Final Cut Pro已经更名为Final Cut Pro X,因此也可简称FCPX。网络上针对FCPX的可用插件不少,但是相关的开发资料就显得非常匮乏,Google了半天都没找到定点信息。没办法,关键时刻还得去看看官方文档。寻寻觅觅终于还是发现了一些有用的信息。公司
24K纯开源
2018/01/18
2.9K0
Final Cut Pro X效果插件开发总结
Mac下使用Pecl安装PHP的Swoole扩展实践
前段时间把Mac系统重装了,PHP的一些扩展都没了,昨天需要调试一个swoole开发的项目,发现命令行中的PHP是系统自带的,如果安装swoole扩展很不方便;需要自己手动去下载swoole的源码,然后去编译swoole的源码,并自己配置,整个过程非常繁琐;
汤青松
2019/12/03
2.2K0
解决xcode打开时loading假死的问题
症状如下: 点击打开xcode后,就一直会看到loading,但是CPU消耗很高,基本上就是死了(动弹不得),通过活动监测器看到xcode显示为“未响应” 以为是安装程序的问题,结果选中xcode拉到废纸篓中,重新下载安装,还是一样的总是,都快崩溃了。 出错原因:可能是上次强制退出时保存xcode出错,导致之后每次打开xcode都会加载这个错误的工程,出现假死现象。 出现这个问题就真得崩溃了,有些小伙伴甚至还重装了Xcode,这里给大家推荐一个行之有效的方法。 有效地解决方法: 打开终端:cd /Users
猿人谷
2018/01/17
2.9K0
iOS开发常用之其他
Xcode的插件 iOS开发进阶,从Xcode开始 - 学习使用Xcode构建出色的应用程序! 在Xcode启动的时候,Xcode将会寻找位于〜/ Library / Application Support / Developer / Shared / Xcode / Plug-ins文件夹的后缀名为.xcplugin的bundle作为插件进行加载(运行其中的可执行文件) 。Xcode5插件简介开发写个自己的Xcode4插件 Xcode 4插件制作入门 :Xcode所使用的所有库都包含在Xc
GuangdongQi
2019/02/22
2.1K0
2018-01-12dyld: Library not loaded: 各种情况
dyld: Library not loaded: 第一种情况:带有 Swift 项目 dyld: Library not loaded: @rpath/libswiftCore.dylib Referenced from: /var/containers/Bundle/Application/CF227EE4-F36F-4161-A8A4-BB063D74B0CF/Boss.app/Boss Reason: no suitable image found. Did find: /private/va
程序员不务正业
2018/06/14
2.5K0
删除Xcode中类似VVdocumenter(自动注释)的插件
Xcode管理插件有统一的位置,经过运行安装的插件是保存在一个文件夹中的。打开文件夹就可以删除指定的插件了 步骤:选择Finder —>选择“前往”(同时按下option键)—> 选择资源库 屏幕
梧雨北辰
2018/04/24
1.7K0
删除Xcode中类似VVdocumenter(自动注释)的插件
相关推荐
Xcode插件-Alcatraz安装
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档