从map到堆栈

下面这些问题是从map的一些思考到堆栈的一些内容,自问自答,为了让自己理解更深一些。

1、问:Map这个数据结构,有几种实现方式?
     HashMap和TreeMap。
     HashMap是使用数组+链表的方式实现的。在冲突少的情况下,O(1)的时间就能够定位到节点,效率杠杠的。
     TreeMap是使用红黑树来实现的,由于红黑树是平衡二叉树查找树,所以,获取数据的get操作和添加数据的put都需要O(logN)的时间复杂度,效率自然不如HashMap了。但由于是树的结构,天生具有排序的特征,所以,对于有排序需求的场景,就十分适用了。

2、HashMap具体是怎么实现的?
     首先定义一个长度为N的对象数组,一个哈希函数,将该对象的key进行哈希,求出的哈希值数组的下标,然后看该位置是否已经有对象存在,若没有,则将其添加到该位置,若已经有对象(冲突),则通过该对象的next寻找到该链表最末尾的对象,将该对象链接到最末端(其实也可以直接挂到链表的头部,这样更节省时间)。

3、java中的HashMap和LinkedHashMap有什么区别呢?
     HashMap的实现如上所述,LinkedHashMap,刚看到这个名词时,以为是直接使用链表而非数组来实现map的,震惊了一下,但其实不然。他的主题实现方式和HashMap是一样的,只不过,LinkedHashMap可以按照插入顺序或者访问顺序进行排序(构造函数中可以配),这是由于LinkedHashMap维护的是一个双向的链表而非单向的,一个after指针,用于记录冲突时的链表的下一个元素;一个before 指针用于记录插入或者访问的顺序。 默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。

4、如果频繁且大量的对HashMap进行put和remove的操作,会造成什么影响?怎么解决?
     内存碎片。由于每次put的时候都需要new一个对象,remove的时候把这个对象移除,这样,在堆中不断地添加删除对象,会导致大量的内存碎片,这样,就需要更频繁的进行内存整理(java中是GC)。
     由于内存碎片是由于不断地new和delete对象而产生的,那么,找个办法不要让他重复new和delete就行了。我想到的是使用对象池来管理对象,当一个一个对象从map中移除时,并不真正delete掉,而是放入对象池,那么,当往map里put一个元素时,直接从对象池中获取对象,并把值set进去。这种直接设置值的方式,比new一个对象快,同时,由于不需要在堆上不断new和delete对象,可以大大减少内存碎片的产生。当然,这种方式实现起来更复杂,并且需要对对象池里面存活的对象数进行有效的管理,否则,如果空闲对象太多,会占用内存而浪费资源。

5、堆的数据结构是怎样的?他是如何管理对象的,当new和delete的时候,堆内部发生了什么?
     堆是一个链表结构(本来以为是个二叉树或者B树的结构,但似乎只是用了简单的链表进行管理),用于记录空闲的内存地址。
     当程序new一个对象时,需要在堆上申请申请一块内存空间,系统会遍历该链表,寻找第一个大于申请空间的堆节点,将该节点从空闲链表中删除(将该节点没 用完的空间再加入空闲链表中),并将该节点空间分配给程序。
     当delete一个对象时,系统会将该对象在堆上的内存释放,把他重新加入空闲链表,并与相邻的链表(如果有的话)合并???
     以上都是在C语言中内存的分配,而在java中,只有new没有delete的说法,都是由JVM自动管理的。


6、堆和栈,在哪个上分配更快,为什么?
      (针对C++)栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

7、栈和堆的大小如何设置?
     栈和堆的初始化一般可以进行配置。比如,JVM中,可以通过-Xmx来设置堆的大小和-Xss来设置每个线程的栈大小。由于栈仅仅存储指向堆中的某个对象或者保存方法的入口,所以他的每个元素是定长的且占用空间很小,因此栈一般初始化为1M甚至更小。
    在C++中,栈是操作系统提供的,而堆是C++函数库提供的。所以,程序运行时,你可以配置堆的大小,但无法调整每个程序栈的大小。

8、为什么堆和栈要区分开?二者有什么区别?
     栈存在于程序中的目的主要是为了控制程序的主流程,这点在方法调用中特别明显,当在一个方法中A调用另一个方法B时,会把执行完B方法后的下一个执行语句地址push入栈,用于程序返回时使用(整个为A的栈帧),在执行方法B时,若有B的局部变量,也继续push入栈进行记录。当B调用完毕时,将B的栈帧pop出栈,然后继续执行A方法。在这个过程里面,由于栈先进后出的特性,能够很方便的控制一个程序的线性的流程,并且在B方法返回后,将的栈帧pop出栈,使得B方法的局部变量,一切都发生的很自然。
     而堆存在的目的,由于栈的每个元素都是固定长度(定长使得内存分配管理都十分方便),并不适合存储变长的对象,所以,使用了堆来应对这种程序中动态变化的需求。当程序需要一块内存存放对象时,会向堆进行申请,然后将指针存入栈中进行记录,所以堆中的每个元素都应该被栈中的某个节点所引用,否则就是失效的节点。另外,在堆上存储对象,而其他地方则只引用他的指针,这大大方便了对象的传递。
     所以,总结下,堆栈的分离,就是将行为与数据分离开。栈用于控制流程、处理逻辑,堆用于存储数据、管理内存,让擅长的人做擅长的事,协作的多美好。

9、除了堆栈,数据区还包括哪些区域?
     在C++中,除了堆、栈,还有常量区(所有的常量都放这里),全局/静态存储区(顾名思义,就是存储全局变量和静态变量的),自由存储区(由malloc分配的内存块,和堆十分相似,但他们用free结束)。
     在JVM中,则主要由Java虚拟机栈、堆,还有方法区(用于存储类信息、常量、静态变量,方法区里面还包括了运行时常量池),程序计数器(用于记录当前线程指令的位置)、本地方法栈

10、从B方法返回A方法时,是如何将A方法中的变量读取进来的呢?在栈中查找对象是怎么做的?
     比如,程序如下,
     A( ){
     int a=1;
     int b=2;
     int c = add(a,b);
     a = c+b;
     }
     B(int a,int b){
          return a+b;
     }
     在A方法执行到 int c = add(a,b);  这一句时,需要调用B方法,会将参数a=1,b=2 ,变量c 分别push进栈,同时,将B方法之后要执行的语句( a = c+b; )地址push进栈,之后进入B方法的栈帧实体进行程序执行。B执行完后,会将B方法的栈帧pop出栈兵更新栈中c的值。然后,程序返回A,继续执行下一条语句。




TreeMap+红黑树的介绍   http://blog.csdn.net/chenssy/article/details/26668941
LinkedHashMap  http://uule.iteye.com/blog/1522291
关于栈和堆中分配空间小结  http://blog.csdn.net/lisiyong/article/details/1552279
C++内存管理  http://blog.csdn.net/lesky/article/details/1461032
JVM栈  http://blog.chinaunix.net/uid-9789791-id-3350479.html

你可能感兴趣的:(数据结构,map,堆栈)