Map实现之HashMap(结构及原理)(转)

 

java.util包中的集合类包含 Java 中某些最常用的类。最常用的集合类是 List 和 Map。List 的具体实现包括 ArrayList 和 Vector,它们是可变大小的列表,比较适合构建、存储和操作任何类型对象元素列表。List 适用于按数值索引访问元素的情形。

        Map 则提供了一个更通用的元素存储方法。Map 集合类用于存储元素对(称作“键”和“值”),其中每个键映射到一个值。从概念上而言,您可以将 List 看作是具有数值键的 Map。而实际上,除了 List 和 Map 都在定义 java.util 中外,两者并没有直接的联系。

        Map接口的实现类有很多,其中HashMap就是比较重要的一个实现,本文就以HashMap为主重点介绍。

        HashMap是基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

        HashMap结合了ArrayList与LinkedList两个实现的优点,,虽然HashMap并不会向List的两种实现那样在某项操作上性能较高,但是在基本操作(get 和 put)上具有稳定的性能。

 

        首先从成员变量开始一点点的来了解HashMap和上述几个概念。

 

        1.HashMap的成员变量:

Java代码 
 收藏代码
  1. /** 
  2.  * 初始默认容量(必须为2的幂次方) 
  3.  */  
  4. static final int DEFAULT_INITIAL_CAPACITY = 16;  
  5.   
  6. /** 
  7.  * 最大容量,如果被指定为一个更高的值必须为2的幂次方,并且小于1073741824.(1<<30) 
  8.  */  
  9. static final int MAXIMUM_CAPACITY = 1 << 30;  
  10.   
  11. /** 
  12.  * 默认负载因子/负载系数 
  13.  */  
  14. static final float DEFAULT_LOAD_FACTOR = 0.75f;  
  15.   
  16. /** 
  17.  * 内部实现表, 必要时调整大小,其长度亦为2的幂次方 
  18.  */  
  19. transient Entry[] table;  
  20.   
  21. /** 
  22.  * map中添加的元素个数 
  23.  */  
  24. transient int size;  
  25.   
  26. /** 
  27.  * 扩容临界值,当size达到此值时进行扩容 (容量乘以负载因子). 
  28.  */  
  29. int threshold;  
  30.   
  31. /** 
  32.  * 内部实现表的负载因子 
  33.  */  
  34. final float loadFactor;  
  35.   
  36. /** 
  37.  * 操作数,可以理解为map实例被操作的次数,包括添加,删除等等 
  38.  */  
  39. transient volatile int modCount;  

        HashMap其内部实现是一个Entry数组table,而Entry就是保存相应键值的实体。table数组默认大小为16,我们也可以在初始化时指定更大的值,但指定值必须为2的幂次方。

        通过对ArrayList的学习了解到ArrayList其内部实现也是数组,当被添加的元素超出数组的容纳极限时,ArrayList会对内部数组进行一次“扩容”,从而可以添加新的元素。

        在HashMap中也有类似的概念,HashMap并不会像ArrayList一样直到数组都满了的情况下才去“扩容”,而是根据负载因子(load factor)来进行判断。

        举例来说:HashMap实例中table数组的默认大小为16,负载因子为0.75,当添加元素个数大于等于12(16*0.75)时就会进行扩容。

        所以说容量和负载因子直接影响着table数组是否扩容,什么时机扩容,进而影响这HashMap实例的性能。

        当我们在初始化时可以指定HashMap实例的容量大小,当指定大小不为2的幂次方时,如下:

Java代码 
 收藏代码
  1. Map map=new HashMap(131);  

        请问初始化完成HashMap内table的长度是多少? 答案为:256

        其实只要打开HashMap的构造函数源代码就明白为什么了,以下为源代码:

Java代码 
 收藏代码
  1. public HashMap(int initialCapacity, float loadFactor) {  
  2.     if (initialCapacity < 0)  
  3.         throw new IllegalArgumentException("Illegal initial capacity: "  
  4.                 + initialCapacity);  
  5.     if (initialCapacity > MAXIMUM_CAPACITY)  
  6.         initialCapacity = MAXIMUM_CAPACITY;  
  7.     if (loadFactor <= 0 || Float.isNaN(loadFactor))  
  8.         throw new IllegalArgumentException("Illegal load factor: "  
  9.                 + loadFactor);  
  10.   
  11.     // Find a power of 2 >= initialCapacity  
  12.     int capacity = 1;  
  13.     while (capacity < initialCapacity)  
  14.         capacity <<= 1;  
  15.   
  16.     this.loadFactor = loadFactor;  
  17.     threshold = (int) (capacity * loadFactor);  
  18.     table = new Entry[capacity];  
  19.     init();  
  20. }  

        关键在于这两行:

Java代码 
 收藏代码
  1. while (capacity < initialCapacity)  
  2.     capacity <<= 1;  

 

        如果initialCapacity(指定大小)大于capacity(原或初始化大小)时,就会不断循环进行位移赋值计算,相当于capacity=capacity *2.直至capacity 大于或等于我们指定的大小。如果指定的大小正好为2的N次幂时两个值便会相等,进而终止计算;如果指定大小不符合条件时,capacity 就会是刚好大于指定大小的那个2的N次幂的数。

        所以,在上面我们指定大小为131,大于131并且为2的的N次幂的数就为256,所以此时就会按256来初始化table.

 

        2.Entry 元素

        与LinkedList类似,HashMap也是采用Entry内部类来存储实际元素信息,以下是Entry的源代码(省略部分代码):

Java代码 
 收藏代码
  1. static class Entry<K, V> implements Map.Entry<K, V> {  
  2.     final K key;  
  3.     V value;  
  4.     Entry<K, V> next;  
  5.     final int hash;  
  6. }  

        Entry中包括4个成员变量,其中key为键,value为值,next指向下一个节点元素,hash为hash值。Entry通过next属性可以寻找到下一个节点的元素,进而通过遍历就可以找到相应key下存储的信息。

 

        3.HashMap设置元素

        Map通过put方法来在Map实例中关联指定值与指定键。如果该实例已经包含了一个该键的映射关系,则旧值被替换。

        示例如下:

Java代码 
 收藏代码
  1. Map map = new HashMap();  
  2. map.put("user1", "小明");  
  3. map.put("user2", "小强");  
  4. map.put("user3", "小红");  
  5. System.out.println("user1:" + map.get("user1"));  
  6. System.out.println("user2:" + map.get("user2"));  
  7. System.out.println("user3:" + map.get("user3"));  
  8. map.put("user2", "小龙");  
  9. System.out.println("user1:" + map.get("user1"));  
  10. //打印结果  
  11. user1:小明  
  12. user2:小强  
  13. user3:小红  
  14. user1:小明  

        首先,创建了一个HashMap的实例map,此时map实例中的table数组会默认初始化,创建一个长度为DEFAULT_INITIAL_CAPACITY=16的空数组。

        然后,调用put方法将一对键、值(key,value)保存。当已存在Map实例中已存在指定key的映射时,会将新指定的value覆盖原value。

        与LIst的相关实现add方法一样,HashMap的put方法是设置元素的入口,在put的过程中会进行一系列的判断与操作,所以只有将put方法理解透彻后HashMap的内部结构与机制才会更加清晰。

        HashMap进行put操作时按以下步骤执行:

        1)判断key是否为空,如果为空则调用设置null的专有方法。

        2)计算key的hash值。

        3)通过hash与table数组的长度计算出该元素所要放置的数组下标。

        4)遍历该下标下的Entry元素链,如果找到与指定key相同的Entry则直接替换该Entry的value值并返回。

        5)如果未找到则添加一个新元素至该下标下的元素链前端。

        以下是一张官网上对于put操作流程的描述图片,可以作为参考:

        以下是put方法的源代码,其中我已经加入了相关描述便于大家理解:

Java代码 
 收藏代码
  1. /** 
  2.  * 设置指定值 
  3.  */  
  4. public V put(K key, V value) {  
  5.     //1.首先判断key是否为null  
  6.     if (key == null)  
  7.         //如果为null则调用putForNullKey方法  
  8.         return putForNullKey(value);  
  9.     //2.计算key的hash值  
  10.     int hash = hash(key.hashCode());  
  11.     //3.根据计算后的hash值与table数组长度计算该key应放置到table数组的那个下标位置  
  12.     int i = indexFor(hash, table.length);  
  13.     //4.遍历该下标下的所有Entry,如果key已存在则覆盖该key所在Entry的value值  
  14.     for (Entry<K, V> e = table[i]; e != null; e = e.next) {  
  15.         Object k;  
  16.         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  17.             V oldValue = e.value;  
  18.             e.value = value;  
  19.             e.recordAccess(this);  
  20.             return oldValue;  
  21.         }  
  22.     }  
  23.   
  24.     modCount++;  
  25.     //5.如果该key不存在则新添加Entry元素至数组指定位置,并且该Entry作为此下标元素链的头部  
  26.     addEntry(hash, key, value, i);  
  27.     return null;  
  28. }  
 
4.HashMap内部结构
通过对put方法的流程分析,我们基本已经了解HashMap其内部实现的机制与原理,那么来总结一下HashMap初始化及添加元素的过程(以默认值为例):
(1) 初始化HashMap实例,初始化其内部数组table:
Java代码 
 收藏代码
  1. this.loadFactor = DEFAULT_LOAD_FACTOR;//0.75f  
  2. threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);//16*0.75=12  
  3. table = new Entry[DEFAULT_INITIAL_CAPACITY];//16  
此时table被初始化创建,长度为16。
(2) 当第一次put元素时,此时HashMap实例中并没有添加任何元素,所以put方法会直接调用addEntry方法:
Java代码 
 收藏代码
  1. Entry<K,V> e = table[bucketIndex];  
  2. table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
首先,会先获取该下标(bucketIndex)下原Entry信息,因为table并未设置任何值,所以此时e为null。
然后,创建一个新的Entry实例,其next属性指向e,并将此实例赋值给table[bucketIndex]。
(3) 当更新HashMap实例中已有key的value内容时:
Java代码 
 收藏代码
  1. for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  2.     Object k;  
  3.     if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  4.         V oldValue = e.value;  
  5.         e.value = value;  
  6.         e.recordAccess(this);  
  7.         return oldValue;  
  8.     }  
  9. }  
        如果HashMap实例中已经put了该key则只需遍历找到该节点Entry,更新其value并返回,所以更新已有key的操作不会调用addEntry方法。
(4) 此时HashMap实例的内部结构如下图所示:
        HashMap采用此种存储元素的方式是结合了ArrayList与LinkedList两者的优点,虽然单纯某项操作的性能上并不比二者之一高,但这种方式的好处就是存储与获取性能平稳,并不会出现剧烈波动的情况。
        5.HashMap获取元素
既然已经了解了HashMap的内部结构已经设置元素时的相关操作步骤,那么获取元素其实也就比较容易理解了,首先根据指定的key去计算数组下标,然后遍历该下标下的Entry链,最后返回。
以下是get方法的源代码,与put方法的基本流程大致相同:
Java代码 
 收藏代码
  1. /** 
  2.  * 返回指定key的value 
  3.  */  
  4. public V get(Object key) {  
  5.     // 1.判断可以是否为null  
  6.     if (key == null)  
  7.         return getForNullKey();  
  8.     // 2.计算key的hash值  
  9.     int hash = hash(key.hashCode());  
  10.     // 3.遍历table指定下标下的Entry链  
  11.     for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {  
  12.         Object k;  
  13.         // 4.如果找到则返回该Entry的value  
  14.         if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
  15.             return e.value;  
  16.     }  
  17.     // 5.未找到则返回null  
  18.     return null;  
  19. }  
        6.HashMap移除元素
HashMap实现了Map接口的remove方法,所以可以通过remove方法移除已经添加的元素:
Java代码 
 收藏代码
  1. Map map = new HashMap();  
  2. map.put("user1", "小明");  
  3. map.put("user2", "小强");  
  4. map.put("user3", "小红");  
  5. map.remove("user2");  
  6. System.out.println("user1:" + map.get("user1"));  
  7. System.out.println("user2:" + map.get("user2"));  
  8. System.out.println("user3:" + map.get("user3"));  
  9. //打印结果:  
  10. user1:小明  
  11. user2:null  
  12. user3:小红  
        当主动调用remove方法时,会根据指定的key删除该节点元素。
以下是remove方法的源代码:
Java代码 
 收藏代码
  1. /** 
  2.  * 删除指定key下内容 
  3.  */  
  4. public V remove(Object key) {  
  5.     Entry<K, V> e = removeEntryForKey(key);  
  6.     return (e == null ? null : e.value);  
  7. }  
  8.   
  9. /** 
  10.  * 根据指定key删除元素 
  11.  */  
  12. final Entry<K, V> removeEntryForKey(Object key) {  
  13.     int hash = (key == null) ? 0 : hash(key.hashCode());  
  14.     int i = indexFor(hash, table.length);  
  15.     Entry<K, V> prev = table[i];  
  16.     Entry<K, V> e = prev;  
  17.   
  18.     while (e != null) {  
  19.         Entry<K, V> next = e.next;  
  20.         Object k;  
  21.         if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {  
  22.             modCount++;  
  23.             size--;  
  24.             if (prev == e)  
  25.                 table[i] = next;  
  26.             else  
  27.                 prev.next = next;  
  28.             e.recordRemoval(this);  
  29.             return e;  
  30.         }  
  31.         prev = e;  
  32.         e = next;  
  33.     }  
  34.   
  35.     return e;  
  36. }  
        remove方法调用了另一个方法removeEntryForKey,removeEntryForKey方法会循环遍历指定下标下所有Entry节点元素,如果该key存在则修改该节点前一个节点的next指向,从而达到把该Entry节点移除Entry链的目的。
注意HashMap的remove操作一样不会引起“减容”操作,这样就不会影响性能。
        7.HashMap的遍历
通常情况下Map的使用者清楚该Map实例中有那些key,通过get(key)方法就可以直接将所有元素取出,但某些情况下这种做法产生的代码将是一次性代码,无法共用。
HashMap的遍历通常采用以下几种方式:
1)通过entrySet()方法可以获取HashMap实例所有Entry的Set返回,所以通过entrySet方法返回并迭代可以获取所有Entry元素:
Java代码 
 收藏代码
  1. Map map = new HashMap();  
  2. map.put("user1", "小明");  
  3. map.put("user2", "小强");  
  4. map.put("user3", "小红");  
  5. Iterator iter = map.entrySet().iterator();  
  6. while (iter.hasNext()) {  
  7.     Map.Entry entry = (Map.Entry) iter.next();  
  8.     Object key = entry.getKey();  
  9.     Object value = entry.getValue();  
  10.     System.out.println("key:" + key + ";value:" + value);  
  11.     // 然后移除元素  
  12.     if (key.toString().equals("user1")) {  
  13.         iter.remove();  
  14.     } else if (key.toString().equals("user2")) {  
  15.         entry.setValue("小海");  
  16.     }  
  17.   
  18. }  
  19. System.out.println(map.get("user1"));  
  20. System.out.println(map.get("user2"));  
  21. System.out.println(map.get("user3"));  
  22.   
  23. // 打印结果:  
  24. key:user2;value:小强  
  25. key:user1;value:小明  
  26. key:user3;value:小红  
  27. null  
  28. 小海  
  29. 小红  
        此种方式操作简单,代码量少,效率较高,且可以直接操作元素,是常用的手段之一。
2)Map还提供了keySet方法,用于返回所有key的Set形式,然后迭代此Set再通过get方法就可以获取相应元素的value:
Java代码 
 收藏代码
  1. Map map = new HashMap();  
  2. map.put("user1", "小明");  
  3. map.put("user2", "小强");  
  4. map.put("user3", "小红");  
  5. Iterator iter = map.keySet().iterator();  
  6. while (iter.hasNext()) {  
  7.     Object key = iter.next();  
  8.     Object value = map.get(key);  
  9.     System.out.println("key:" + key + ";value:" + value);  
  10.     // 然后移除元素  
  11.     if (key.toString().equals("user1")) {  
  12.         iter.remove();  
  13.     }  
  14. }  
  15. System.out.println(map.get("user1"));  
  16. System.out.println(map.get("user2"));  
  17. System.out.println(map.get("user3"));  
  18.   
  19. // 打印结果:  
  20. key:user2;value:小强  
  21. key:user1;value:小明  
  22. key:user3;value:小红  
  23. null  
  24. 小强  
  25. 小红  
        此种方式先需要将所有key遍历后返回,再通过get方法来获取元素,如果单纯需要操作Map实例中的个别节点元素时效率尚可,如果需要大规模获取和修改时效率不如第一种。所以两种方式选择那种需要视情况而言,并没有绝对。
3)通过values方法直接返回所有value:
Java代码 
 收藏代码
  1. Map map = new HashMap();  
  2. map.put("user1", "小明");  
  3. map.put("user2", "小强");  
  4. map.put("user3", "小红");  
  5. //转换成数组  
  6. String[] names= (String[]) map.values().toArray(new String[map.size()]);  
  7. for (String name : names){  
  8.     System.out.println(name);  
  9. }  
  10. //采用迭代  
  11. Collection nameArray =  map.values();  
  12.   
  13. Iterator iter = nameArray.iterator();  
  14.   
  15. while (iter.hasNext()) {  
  16.   
  17.  String name=iter.next().toString();  
  18.   
  19.  System.out.println(name);  
  20.   
  21. }  
  22. // 打印结果:  
  23. 小强  
  24. 小明  
  25. 小红  
        此种方式简单明了,适用于直接获取所有value的情况,可以直接迭代或者转换成数组,当直接显示value的情况下比较适用。
HashMap的基本结构及内部实现原理至此已经比较清晰,下一篇着重来了解下HashMap其内部几种算法的原理及相关性能。
http://286.iteye.com/blog/2187873

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/460445.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql对表中添加属性_菜鸟笔记—数据分析师MySQL篇(一)

简单说一下我写这份学习笔记的原因&#xff0c;由于工作的原因&#xff0c;想换一份工作&#xff0c;对于毕业已经快6年了&#xff0c;再次重新学习就需要付出很大的勇气和努力&#xff0c;如果态度还不能及时调整&#xff0c;最近找工作遇到的窘境就不言而喻了。去年底报了一个…

matlab打开笔记本摄像头_如何解决笔记本电脑摄像头异常问题

如果您遇到笔记本电脑相机异常问题(无法侦测视讯装置、视讯无画面、视讯画面异常、视讯画面颠倒等等)&#xff0c;请参考以下疑难解答方式依序尝试。提供应用程序权限 / 检查防病毒软件/ 更新Windows Update / 更新相机驱动程序/透过系统还原点还原系统/ 系统还原1. 提供应用程…

边框颜色为 tintColor 的 UIButton

创建一个 UIButton 的子类&#xff0c;重写其方法&#xff1a; - (void)drawRect:(CGRect)rect {[[self layer] setCornerRadius:CORNER_RADIUS];[[self layer] setMasksToBounds:YES]; [[self layer] setBorderWidth:1];[[self layer] setBorderColor:self.tintColor.CGColo…

netty SimpleChannelInboundHandler类继承使用

2019独角兽企业重金招聘Python工程师标准>>> 继承一个SimpleChannelInboundHandler来实现我们的Client&#xff0c;我们需要重写其中的三个方法&#xff1a; package NettyDemo.echo.handler;import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; …

高中关于人工智能方面的课题_如何看待计算机专业开始设置人工智能课程

首先&#xff0c;当前计算机专业当中设置与人工智能相关的课程是一个必然的趋势&#xff0c;随着人工智能技术开始逐渐实现落地应用&#xff0c;未来计算机相关专业的课程当中&#xff0c;人工智能课程的比重也会逐渐提升&#xff0c;而且一定要重视这些课程&#xff0c;这对于…

Linux命令-目录处理命令:mkdir

mkdir /tmp/beijing mkdir -p /tmp/shijiazhuang/yuhuaqu 一条命令可以同时创建父目录和子目录 mkdir /tmp/beijing/chaoyangqu /tmp/beijing/dongchengqu /tmp/beijing/tongzhouqu 同时创建多个目录

tableau 倒序都倒了_Tableau优秀作品拆解复刻01-是时候终结瘘管病了

写在最前面&#xff1a;这个复刻系列是学习tableau官网库中的优秀作品。学习他们亮眼图表的制作细节&#xff0c;仪表板的排版&#xff0c;颜色的搭配以及交互。tableau库的链接&#xff1a;优秀作品都在这里展示。库​public.tableau.com1 整体布局 颜色&#xff1a; 采用橙色…

目前最细致清晰的NSDictionary以及NSMutableDictionary用法总结(转)

做过Java语言 或者 C语言 开发的朋友应该很清楚 关键字map 吧&#xff0c;它可以将数据以键值对儿的形式储存起来&#xff0c;取值的时候通过KEY就可以直接拿到对应的值&#xff0c;非常方便。在Objective-C语言中 词典对象就是做这个事情的&#xff0c;不过在同一个词典对象中…

android string数组转json_移动端开发基础【20】pages.json的配置项pages

uni-app项目是通过pages节点配置应用由哪些页面组成&#xff0c;pages节点接收一个数组&#xff0c;数组每个项都是一个对象&#xff0c;其属性值如下&#xff1a;(1) 属性&#xff1a;path类型&#xff1a;String描述&#xff1a;配置页面路径(2) 属性&#xff1a;style类型&a…

Ubuntu Server 分区案例

为什么80%的码农都做不了架构师&#xff1f;>>> 只有一台服务器&#xff0c;配置硬盘1TB&#xff0c;内存4GB&#xff0c;既要做开发服务器&#xff0c;又要做Web服务器和数据库服务器&#xff0c;分区如下&#xff1a; 挂载点大小分区格式 /boot512MBext4/20GBext…

360网络修复工具_为什么大家都在骂360,但是360依旧是很强?

相信很多小伙伴也多多少少的听说过就是不要随便装360&#xff0c;因为装上360会如何如何。这些人往往说起来也是很有道理&#xff0c;我在以前上学的时候也是这么觉得&#xff0c;但是后来工作之后发现并不是这样。360先说一下360安全卫士的功能&#xff0c;有系统优化、垃圾清…

Java运行时内存

对于java程序员来说&#xff0c;并不必显示地对内存进行管理&#xff0c;一切都交给java虚拟机去做吧&#xff0c;而且&#xff0c;你也不一定做得比java虚拟机来得专业。好像所有内存管理都交给虚拟机去做就万事大吉了&#xff0c;但是&#xff0c;事实有时并非如此&#xff0…

中班机器人上课视频_家委会:出班费买智能扫地机器人,不用家长搞卫生了,莫名其妙...

【01】原来大家上学&#xff0c;几乎是很随意的样子&#xff0c;有人至没上过幼儿园&#xff0c;有的上过半年&#xff0c;然后就直接升小学。可即使是升小学了&#xff0c;家长也不会管&#xff0c;全凭自己了。可如今随着社会的发展&#xff0c;父母们都非常重视孩子们的学习…

Powerful Sleep(神奇的睡眠-睡眠生物钟的秘密:如何睡得更少却睡得更好)阅读笔记...

睡眠机制 我们活着的时候&#xff0c;大脑会产生脑电波。脑电图仪器通过贴在人头上的一些电极读出脑电波的活动&#xff0c;然后把活动用图表显示出来。 睡眠过程可以分为5个过程&#xff0c;划分依据与大脑发出的脑电波类型。 当人清醒时&#xff0c;大脑发出β脑电波&#xf…

ue4集合类型_UE4粒子系统渲染管线概述

本文基于UE4版本4.25.3&#xff0c;对Cascade粒子系统的移动端渲染管线进行简单的概括和描述。Game Thread部分粒子系统Actor被Spawn&#xff08;或所在的Level被加载&#xff09;的时候&#xff0c;UParticleSystemComponent注册和初始化&#xff0c;并通过CreateSceneProxy函…

2、Sprite,SpriteBatch,Texture,TextureRegion的初步认识

昨天搭建了环境&#xff0c;今天就初步接触这个4个控件&#xff08;Sprite&#xff0c;SpriteBatch&#xff0c;Texture&#xff0c;TextureRegion&#xff09; 1、SpriteBatch 这个吗&#xff0c;我没有看api文档&#xff0c;偶是直接看土豆的博客学习的&#xff0c;我看了代码…

莒南机器人_莒南42项重点建设项目公布!一定有你关注的

10日&#xff0c;县发改局公布莒南县2020年重点建设项目表&#xff01;涉及工业、服务业、基础设施、现代农业等行业&#xff0c;共42项。详↓工业1、山东钢铁集团永锋临港有限公司临港先进优特钢产业基地一期项目投资方&#xff1a;山东钢铁集团永锋临港有限公司建设地点&…

配置Mysql实现主从复制与读写分离

环境说明 主从复制使用mysql自带的master与slave机制&#xff1b;读写分离使用mysql-proxy实现&#xff01; 有服务器三台&#xff1a;s1,s2,s3。 s1为web服务器&#xff0c;装有httpd&#xff0c;php&#xff0c;mysql&#xff0c;mysql-proxy。 s2为主数据库服务器&#xff0…

arduino 土壤温湿度传感器_嫌arduino太贵?太大?试试ATTINY85!DIY温湿度计入门级教程...

ATTINY85做主控&#xff0c;OLED显示的温湿度计。简介&#xff1a;通过Arduino开发环境&#xff0c;对ATTINY85进行编程&#xff0c;利用DH11温湿度传感器&#xff0c;在SSD1306(128*64)OLED显示屏上显示温度和湿度。材料&#xff1a;ATTINY85DHT11SSD1306 (12864 OLED)电池盒纸…

认识事件冒泡和事件捕获

一.事件冒泡就是多个元素同时响应了同一个事件&#xff0c;前提是这些元素都绑定了这一个事件。这只是我的定义&#xff0c;感觉好理解些。 称它为冒泡&#xff0c;是因为事件会按照DOM元素的层次结构依次执行&#xff0c;就像水泡一样不断浮向顶端。所以称之为事件冒泡。 这个…