
HashMap是基于哈希表的Map接口的非同步实现。实现HashMap对数据的 *** 作,允许有一个null键,多个null值。
HashMap底层就是一个数组结构,数组中的每一项又是一个链表。数组+链表结构,新建一个HashMap的时候,就会初始化一个数组。Entry就是数组中的元素,每个Entry其实就是一个key-value的键值对,它持有一个指向下一个元素的引用,这就构成了链表,HashMap底层将key-value当成一个整体来处理,这个整体就是一个Entry对象。HashMap底层采用一个Entry数组来保存所有的key-value键值对,当需要存储一个Entry对象时,会根据hash算法来决定在其数组中的位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry对象时,也会根据hash算法找到其在数组中的存储位置, 在根据equals方法从该位置上的链表中取出Entry;
put: (key-value)方法是HashMap中最重要的方法,使用HashMap最主要使用的就是put,get两个方法。
判断键值对数组table[i]是否为空或者为null,否则执行resize()进行扩容;
根据键值key计算hash值得到插入的数组索引 i ,如果table[i] == null ,直接新建节点添加即可,转入6,如果table[i] 不为空,则转向3;
判断table[i] 的首个元素是否和key一样,如果相同(hashCode和equals)直接覆盖value,否则转向4;
判断table[i] 是否为treeNode,即table[i]是否为红黑树,如果是红黑树,则直接插入键值对,否则转向5;
遍历table[i] , 判断链表长度是否大于8,大于8的话把链表转换成红黑树 ,进行插入 *** 作,否则进行链表插入 *** 作;便利时遇到相同key直接覆盖value;
插入成功后,判断实际存在的键值对数量size是否超过了threshold,如果超过,则扩容;
也可参考HashSetput过程:
>
java根据Map的值(value)取键(key) 的实现方法有4种,分别为:
(1)使用for循环遍历
(2)使用Iterator迭代器
(3)使用KeySet迭代
(4)使用EnterySet迭代
下面为以上4种方法具体实现的代码:
1、使用for循环遍历
public static Object getKey(HashMap<Object,Object> map, String v) {
String key = "";
for (MapEntry<String, Object> m :mapentrySet()) {
if (mgetValue()equals(v)) {
key = mgetKey();
}}
return key;
}
2、使用Iterator迭代器
public static Object getKey(HashMap<Object,Object> map, String v) {
Set set = mapentrySet();
Iterator iterator=setiterator();
String key = "";
while (iteratorhasNext()) {
MapEntry<String, Object> enter = (Entry<String, Object>)
iteratornext();
if (entergetValue()equals(v)) {
key = entergetKey();
}}
return key;
}
3、使用KeySet迭代
public static Object getKey(HashMap<Object,Object> map, String v) {
Iterator<Object> it = mapkeySet()iterator();
while (ithasNext()) {
String key = itnext()toString();
if ((String) mapget(key)equals(v)) return key;
}
return null;
}
4、使用EnterySet迭代
public static Object getKey(HashMap<Object,Object> map, String v) {
String key = "";
Iterator it = mapentrySet()iterator();
while (ithasNext()) {
MapEntry entry = (Entry) itnext();
Object obj = entrygetValue();
if (obj != null && objequals(value)) {
key = (String) entrygetKey();
}}
return key;
}
扩展资料:
java获取map的key和value的方法:
(1) 通过mapkeySet()方法,先获取map的key,然后根据key获取对应的value。
for(String key : mapkeySet()){
String value = mapget(key);
Systemoutprintln(key+" "+value);
}
Iterator<String> iter = mapkeySet()iterator();
while(iterhasNext()){
String key=iternext();
String value = mapget(key);
Systemoutprintln(key+" "+value);
}
(2)通过mapentrySet()方法,循环map里面的每一对键值对,然后获取key和value。
for(Entry<String, String> vo : mapentrySet()) {
vogetKey();
vogetValue();
Systemoutprintln(vogetKey()+" "+vogetValue());
}
Iterator<Entry<String,String>> iter = mapentrySet()iterator();
while(iterhasNext()){
Entry<String,String> entry = iternext();
String key = entrygetKey();
String value = entrygetValue();
Systemoutprintln(key+" "+value);
}
参考资料来源:JAVA官方文档-Map
存在整数键值就不能使用这种 Sformat_map 的方式了,因为 format 中的 {n} 语法已经用来表示取第 n 个位置参数的值,如下:
>>> '{2}{1}{0}'format('a', 'b', 'c')'cba'
{n} 语法的优先级是高于 format_map 的。
Top-N 分析法是指从研究对象中得到所需的 N 个数据,并对这 N 个数据进行重点分析的方法。
对数据文件中的数据取最大 top-n。数据文件中的每个都是一个数据。
原始输入数据为:
10 3 8 7 6 5 1 2 9 4
11 12 17 14 15 20
19 18 13 16
输出结果为(最大的前 5 个):
20
19
18
17
16
要找出 top N, 核心是能够想到 reduce k Task 个数 一定只有一个。
因为一个 map task 就是一个进程,有几个 map task 就有几个中间文件,有几个 reduce task 就有几个最终输出文件。我们要找的 top N 是指的全局的前 N 条数据,那么不管中间有几个 map, reduce 最终只能有一个 reduce 来汇总数据,输出 top N。
在安装Hadoop集群的时候,我们在yarn-sitexml文件中配置了MapReduce的运行方式为yarnnodemanageraux-services=mapreduce_shuffle。本节就来详细介绍一下MapReduce的shuffle过程。
shuffle,即混洗、洗牌的意思,是指MapReduce程序在执行过程中,数据在各个Mapper(Combiner、Sorter、Partitioner)、Reducer等进程之间互相交换的过程。
关于上图Shuffle过程的几点说明:
说明:map节点执行map task任务生成map的输出结果。
shuffle的工作内容:
从运算效率的出发点,map输出结果优先存储在map节点的内存中。每个map task都有一个内存缓冲区,存储着map的输出结果,当达到内存缓冲区的阀值(80%)时,需要将缓冲区中的数据以一个临时文件的方式存到磁盘,当整个map task结束后再对磁盘中这个map task所产生的所有临时文件做合并,生成最终的输出文件。最后,等待reduce task来拉取数据。当然,如果map task的结果不大,能够完全存储到内存缓冲区,且未达到内存缓冲区的阀值,那么就不会有写临时文件到磁盘的 *** 作,也不会有后面的合并。
详细过程如下:
(1)map task任务执行,输入数据的来源是:HDFS的block。当然在mapreduce概念中,map task读取的是split分片。split与block的对应关系:一对一(默认)。
此处有必要说明一下block与split:
block(物理划分):文件上传到HDFS,就要划分数据成块,这里的划分属于物理的划分,块的大小可配置(默认:第一代为64M,第二代为128M)可通过 dfsblocksize配置。为保证数据的安 全,block采用冗余机制:默认为3份,可通过dfsreplication配置。注意:当更改块大小的配置后,新上传的文件的块大小为新配置的值,以前上传的文件的块大小为以前的配置值。
split(逻辑划分):Hadoop中split划分属于逻辑上的划分,目的只是为了让map task更好地获取数据。split是通过hadoop中的InputFormat接口中的getSplit()方法得到的。那么,split的大小具体怎么得到呢?
首先介绍几个数据量:
totalSize:整个mapreduce job所有输入的总大小。注意:基本单位是block个数,而不是Bytes个数。
numSplits:来自jobgetNumMapTasks(),即在job启动时用户利用 orgapachehadoopmapredJobConfsetNumMapTasks(int n)设置的值,从方法的名称上看,是用于设置map的个数。但是,最终map的个数也就是split的个数并不一定取用户设置的这个值,用户设置的map个数值只是给最终的map个数一个提示,只是一个影响因素,而不是决定因素。
goalSize:totalSize/numSplits,即期望的split的大小,也就是每个mapper处理多少的数据。但是仅仅是期望
minSize:split的最小值,该值可由两个途径设置:
最终取goalSize和minSize中的最大值!
最终:split大小的计算原则:finalSplitSize=max(minSize,min(goalSize,blockSize))
那么,map的个数=totalSize/finalSplitSize
注意: 新版的API中InputSplit划分算法不再考虑用户设定的Map Task个数,而是用mapredmaxsplitsize(记为maxSize)代替
即:InputSplit大小的计算公式为:splitSize=max{minSize,min{maxSize,blockSize}}
接下来就简答说说怎么根据业务需求,调整map的个数。
当我们用hadoop处理大批量的大数据时,一种最常见的情况就是job启动的mapper数量太多而超出系统限制,导致hadoop抛出异常终止执行。
解决方案:减少mapper的数量!具体如下:
a输入文件数量巨大,但不是小文件
这种情况可通过增大每个mapper的inputsize,即增大minSize或者增大blockSize来减少所需的mapper的数量。增大blocksize通常不可行,因为HDFS被hadoop namenode -format之后,blocksize就已经确定了(由格式化时dfsblocksize决定),如果要更改blocksize,需要重新格式化HDFS,这样当然会丢失已有的数据。所以通常情况下只能增大minSize,即增大mapredminsplitsize的值。
b输入文件数量巨大,且都是小文件
所谓小文件,就是单个文件的size小于blockSize。这种情况通过增大mapredminsplitsize不可行,需要使用FileInputFormat衍生的CombineFileInputFormat将多个input path合并成一个InputSplit送给mapper处理,从而减少mapper的数量。增加mapper的数量,可以通过减少每个mapper的输入做到,即减小blockSize或者减少mapredminsplitsize的值。
(2)map执行后,得到key/value键值对。接下来的问题就是,这些键值对应该交给哪个reduce做?注意:reduce的个数是允许用户在提交job时,通过设置方法设置的!
MapReduce提供partitioner接口解决上述问题。默认 *** 作是:对key hash后再以reduce task数量取模,返回值决定着该键值对应该由哪个reduce处理。这种默认的取模方式只是为了平均reduce的处理能力,防止数据倾斜,保证负载均衡。如果用户自己对Partition有需求,可以自行定制并设置到job上。
接下来,需要将key/value以及Partition结果都写入到缓冲区,缓冲区的作用:批量收集map结果,减少磁盘IO的影响。当然,写入之前,这些数据都会被序列化成字节数组。而整个内存缓冲区就是一个字节数组。这个内存缓冲区是有大小限制的,默认100MB。当map task的输出结果很多时,就可能撑爆内存。需将缓冲区的数据临时写入磁盘,然后重新利用这块缓冲区。
从内存往磁盘写数据被称为Spill(溢写),由单独线程完成,不影响往缓冲区写map结果的线程。溢写比例:spillpercent(默认08)。
当缓冲区的数据达到阀值,溢写线程启动,锁定这80MB的内存,执行溢写过程。剩下的20MB继续写入map task的输出结果。互不干涉!
当溢写线程启动后,需要对这80MB空间内的key做排序(Sort)。排序是mapreduce模型的默认行为,也是对序列化的字节做的排序。排序规则:字典排序!
map task的输出结果写入内存后,当溢写线程未启动时,对输出结果并没有做任何的合并。从官方图可以看出,合并是体现在溢写的临时磁盘文件上的,且这种合并是对不同的reduce端的数值做的合并。所以溢写过程一个很重要的细节在于,如果有很多个key/value对需要发送到某个reduce端,那么需要将这些键值对拼接到一块,减少与partition相关的索引记录。如果client设置Combiner,其会将有相同key的key/value对的value加起来,减少溢写到磁盘的数据量。注意:这里的合并并不能保证map结果中所有的相同的key值的键值对的value都合并了,它合并的范围只是这80MB,它能保证的是在每个单独的溢写文件中所有键值对的key值均不相同!
溢写生成的临时文件的个数随着map输出结果的数据量变大而增多,当整个map task完成,内存中的数据也全部溢写到磁盘的一个溢写文件。也就是说,不论任何情况下,溢写过程生成的溢写文件至少有一个!但是最终的文件只能有一个,需要将这些溢写文件归并到一起,称为merge。merge是将所有的溢写文件归并到一个文件,结合上面所描述的combiner的作用范围,归并得到的文件内键值对有可能拥有相同的key,这个过程如果client设置过Combiner,也会合并相同的key值的键值对,如果没有,merge得到的就是键值集合,如{“aaa”, [5, 8, 2, …]}。注意:combiner的合理设置可以提高效率,但是如果使用不当会影响效率!
至此,map端的所有工作都已经结束!
当mapreduce任务提交后,reduce task就不断通过RPC从JobTracker那里获取map task是否完成的信息,如果获知某台TaskTracker上的map task执行完成,Shuffle的后半段过程就开始启动。其实呢,reduce task在执行之前的工作就是:不断地拉取当前job里每个map task的最终结果,并对不同地方拉取过来的数据不断地做merge,也最终形成一个文件作为reduce task的输入文件。
1Copy过程,简单地拉取数据。Reduce进程启动一些数据copy线程(Fether),通过>
如果把数组的序列号当做 Map 的key ,值当做value,
那么取相应的值可以 mapget(key);
也可以便利Map取值
如:
for(String str:map){
String s = str;
}
这里str为map里value部分
hm已是一个HashMap的引用
如果你知道当前的这个key,可以通过hmget(key)方法来获得value
获得key的方法hmkeySet();因为你不知道key是哪个其实该方法就是获得一个key的集合
具体可以结合以下例子看看,里面有个迭代器用于遍历的
Set s=hmkeySet();//通过keySet方法可获得所有key的集合,放在一个容器Set里面
Iterator it=siterator();//获得一个迭代器引用it,通过siterator方法好比使“指针”指向
//set里面的第一个元素的位置
while(ithasNext())//set里面如果有下一个
{
Integer key=itnext();//返回当前set中的这个元素(因为set中都是放的key,“指针”指向下一个
Systemoutprintln(hmget(key));//利用hmget(key)方法获得该key对应的value
}
以上就是关于Map集合:HashMap、TreeMap全部的内容,包括:Map集合:HashMap、TreeMap、C++中,将map中的value存放在vector中进行排序之后,那么怎样取到相应的key值并顺序输出呢、java Map 根据Map的值(value)取键(key)等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)