Переглянути джерело

[java/cs] Fix IntMap implementation when setting 0 keys

Reviewed the whole Map implementations, added a few more comments
Closes #6457
Cauê Waneck 8 роки тому
батько
коміт
5979a08ac2

+ 150 - 76
std/cs/_std/haxe/ds/IntMap.hx

@@ -57,64 +57,69 @@ import cs.NativeArray;
 
 	public function set( key : Int, value : T ) : Void
 	{
-		var x:Int;
+		var targetIndex:Int;
 		if (nOccupied >= upperBound)
 		{
 			if (nBuckets > (size << 1))
+			{
 				resize(nBuckets - 1); //clear "deleted" elements
-			else
+			} else {
 				resize(nBuckets + 1);
+			}
 		}
 
 		var flags = flags, _keys = _keys;
 		{
-			var mask = nBuckets - 1;
-			var site = x = nBuckets;
-			var k = hash(key);
-			var i = k & mask;
-
-			var delKey = -1;
-			//for speed up
-			if (flagIsEmpty(flags, i)) {
-				x = i;
+			var mask = nBuckets - 1,
+			    hashedKey = hash(key),
+			    curIndex = hashedKey & mask;
+
+			var delKey = -1,
+			    curFlag = 0;
+			// to speed things up, don't loop if the first bucket is already free
+			if (isEmpty(getFlag(flags, curIndex))) {
+				targetIndex = curIndex;
 			} else {
-				var inc = getInc(k, mask);
-				var last = i;
-				while (! (flagIsEmpty(flags, i) || _keys[i] == key) )
+				var inc = getInc(hashedKey, mask),
+				    last = curIndex;
+				while (! (_keys[curIndex] == key || isEmpty(curFlag = getFlag(flags, curIndex))) )
 				{
-					if (delKey == -1 && flagIsDel(flags,i))
-						delKey = i;
-					i = (i + inc) & mask;
-#if DEBUG_HASHTBL
-					if (i == last)
+					if (delKey == -1 && isDel(curFlag))
 					{
-						throw "assert";
+						delKey = curIndex;
 					}
+					curIndex = (curIndex + inc) & mask;
+#if debug
+					assert(curIndex != last);
 #end
 				}
 
-				if (flagIsEmpty(flags,i) && delKey != -1)
-					x = delKey;
-				else
-					x = i;
+				if (delKey != -1 && isEmpty(getFlag(flags, curIndex))) {
+					targetIndex = delKey;
+				} else {
+					targetIndex = curIndex;
+				}
 			}
 		}
 
-		if (flagIsEmpty(flags, x))
+		var flag = getFlag(flags, targetIndex);
+		if (isEmpty(flag))
 		{
-			_keys[x] = key;
-			vals[x] = value;
-			setIsBothFalse(flags, x);
+			_keys[targetIndex] = key;
+			vals[targetIndex] = value;
+			setIsBothFalse(flags, targetIndex);
 			size++;
 			nOccupied++;
-		} else if (flagIsDel(flags, x)) {
-			_keys[x] = key;
-			vals[x] = value;
-			setIsBothFalse(flags, x);
+		} else if (isDel(flag)) {
+			_keys[targetIndex] = key;
+			vals[targetIndex] = value;
+			setIsBothFalse(flags, targetIndex);
 			size++;
 		} else {
-			assert(_keys[x] == key);
-			vals[x] = value;
+#if debug
+			assert(_keys[targetIndex] == key);
+#end
+			vals[targetIndex] = value;
 		}
 	}
 
@@ -124,17 +129,28 @@ import cs.NativeArray;
 		{
 			var flags = flags, _keys = _keys;
 
-			var mask = nBuckets - 1, k = hash(key);
-			var i = k & mask;
-			var inc = getInc(k, mask); /* inc == 1 for linear probing */
-			var last = i;
-			while (!flagIsEmpty(flags, i) && (flagIsDel(flags, i) || _keys[i] != key))
+			var mask = nBuckets - 1,
+			    k = hash(key),
+			    index = k & mask,
+			    curFlag = -1,
+			    inc = getInc(k, mask), /* inc == 1 for linear probing */
+			    last = index;
+			do
 			{
-				i = (i + inc) & mask;
-				if (i == last)
-					return -1;
-			}
-			return isEither(flags, i) ? -1 : i;
+				if (_keys[index] == key) {
+					if (isEmpty(curFlag = getFlag(flags, index)))
+					{
+						index = (index + inc) & mask;
+						continue;
+					} else if (isDel(curFlag)) {
+						return -1;
+					} else {
+						return index;
+					}
+				} else {
+					index = (index + inc) & mask;
+				}
+			} while (index != last);
 		}
 
 		return -1;
@@ -226,15 +242,21 @@ import cs.NativeArray;
 		} else {
 #if !no_map_cache
 			if (cachedKey == key)
+			{
 				cachedIndex = -1;
+			}
 #end
-			if (!isEither(flags, idx))
+			if (!isEither(getFlag(flags, idx)))
 			{
 				setIsDelTrue(flags, idx);
 				--size;
 
 				vals[idx] = null;
-				_keys[idx] = 0;
+				// we do NOT reset the keys here, as unlike StringMap, we check for keys equality
+				// and stop if we find a key that is equal to the one we're looking for
+				// setting this to 0 will allow the hash to contain duplicate `0` keys
+				// (see #6457)
+				// _keys[idx] = 0;
 			}
 
 			return true;
@@ -256,17 +278,23 @@ import cs.NativeArray;
 				var nfSize = flagsSize(newNBuckets);
 				newFlags = new NativeArray( nfSize );
 				for (i in 0...nfSize)
-					newFlags[i] = 0xaaaaaaaa;
+				{
+					newFlags[i] = 0xaaaaaaaa; // isEmpty = true; isDel = false
+				}
 				if (nBuckets < newNBuckets) //expand
 				{
 					var k = new NativeArray(newNBuckets);
 					if (_keys != null)
+					{
 						arrayCopy(_keys, 0, k, 0, nBuckets);
+					}
 					_keys = k;
 
 					var v = new NativeArray(newNBuckets);
 					if (vals != null)
+					{
 						arrayCopy(vals, 0, v, 0, nBuckets);
+					}
 					vals = v;
 				} //otherwise shrink
 			}
@@ -286,12 +314,13 @@ import cs.NativeArray;
 			var newMask = newNBuckets - 1;
 			while (++j < nBuckets)
 			{
-				if (!isEither(flags, j))
+				if (!isEither(getFlag(flags, j)))
 				{
 					var key = _keys[j];
 					var val = vals[j];
 
-					_keys[j] = 0;
+					// do not set keys as 0 - see comment about #6457
+					// _keys[j] = 0;
 					vals[j] = cast null;
 					setIsDelTrue(flags, j);
 					while (true) /* kick-out process; sort of like in Cuckoo hashing */
@@ -299,11 +328,13 @@ import cs.NativeArray;
 						var k = hash(key);
 						var inc = getInc(k, newMask);
 						var i = k & newMask;
-						while (!flagIsEmpty(newFlags, i))
+						while (!isEmpty(getFlag(newFlags, i)))
+						{
 							i = (i + inc) & newMask;
+						}
 						setIsEmptyFalse(newFlags, i);
 
-						if (i < nBuckets && !isEither(flags, i)) /* kick out the existing element */
+						if (i < nBuckets && !isEither(getFlag(flags, i))) /* kick out the existing element */
 						{
 							{
 								var tmp = _keys[i];
@@ -400,34 +431,64 @@ import cs.NativeArray;
 	private static inline function defaultK():Int return 0;
 
 	private static inline function arrayCopy(sourceArray:cs.system.Array, sourceIndex:Int, destinationArray:cs.system.Array, destinationIndex:Int, length:Int):Void
+	{
 		cs.system.Array.Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
+	}
 
 	private static inline function getInc(k:Int, mask:Int):Int
+	{
 		return (((k) >> 3 ^ (k) << 3) | 1) & (mask);
+	}
 
 	private static inline function hash(i:Int):Int
+	{
 		return i;
+	}
 
-	private static inline function flagIsEmpty(flag:NativeArray<Int>, i:Int):Bool
-		return ( (flag[i >> 4] >>> ((i & 0xf) << 1)) & 2 ) != 0;
+	// flags represents a bit array with 2 significant bits for each index
+	// one bit for deleted (1), one for empty (2)
+	// so what this function does is:
+	//  * gets the integer with (flags / 16)
+	//  * shifts those bits to the right ((flags % 16) * 2) places
+	//  * masks it with 0b11
+	private static inline function getFlag(flags:NativeArray<Int>, i:Int):Int
+	{
+		return ( (flags[i >> 4] >>> ((i & 0xf) << 1)) & 3 );
+	}
 
-	private static inline function flagIsDel(flag:NativeArray<Int>, i:Int):Bool
-		return ((flag[i >> 4] >>> ((i & 0xf) << 1)) & 1) != 0;
+	private static inline function isDel(flag:Int):Bool
+	{
+		return (flag & 1) != 0;
+	}
 
-	private static inline function isEither(flag:NativeArray<Int>, i:Int):Bool
-		return ((flag[i >> 4] >>> ((i & 0xf) << 1)) & 3) != 0;
+	private static inline function isEmpty(flag:Int):Bool
+	{
+		return (flag & 2) != 0;
+	}
 
-	private static inline function setIsDelFalse(flag:NativeArray<Int>, i:Int):Void
-		flag[i >> 4] &= ~(1 << ((i & 0xf) << 1));
+	private static inline function isEither(flag:Int):Bool {
+		return flag != 0;
+	}
+
+	private static inline function setIsDelFalse(flags:NativeArray<Int>, i:Int):Void
+	{
+		flags[i >> 4] &= ~(1 << ((i & 0xf) << 1));
+	}
 
-	private static inline function setIsEmptyFalse(flag:NativeArray<Int>, i:Int):Void
-		flag[i >> 4] &= ~(2 << ((i & 0xf) << 1));
+	private static inline function setIsEmptyFalse(flags:NativeArray<Int>, i:Int):Void
+	{
+		flags[i >> 4] &= ~(2 << ((i & 0xf) << 1));
+	}
 
-	private static inline function setIsBothFalse(flag:NativeArray<Int>, i:Int):Void
-		flag[i >> 4] &= ~(3 << ((i & 0xf) << 1));
+	private static inline function setIsBothFalse(flags:NativeArray<Int>, i:Int):Void
+	{
+		flags[i >> 4] &= ~(3 << ((i & 0xf) << 1));
+	}
 
-	private static inline function setIsDelTrue(flag:NativeArray<Int>, i:Int):Void
-		flag[i >> 4] |= 1 << ((i & 0xf) << 1);
+	private static inline function setIsDelTrue(flags:NativeArray<Int>, i:Int):Void
+	{
+		flags[i >> 4] |= 1 << ((i & 0xf) << 1);
+	}
 
 	private static inline function roundUp(x:Int):Int
 	{
@@ -441,25 +502,32 @@ import cs.NativeArray;
 	}
 
 	private static inline function flagsSize(m:Int):Int
+	{
 		return ((m) < 16? 1 : (m) >> 4);
+	}
 }
 
 @:access(haxe.ds.IntMap)
 @:final
-private class IntMapKeyIterator<T> {
+private class IntMapKeyIterator<T>
+{
 	var m:IntMap<T>;
 	var i:Int;
 	var len:Int;
 
-	public function new(m:IntMap<T>) {
+	public function new(m:IntMap<T>)
+	{
 		this.i = 0;
 		this.m = m;
 		this.len = m.nBuckets;
 	}
 
-	public function hasNext():Bool {
-		for (j in i...len) {
-			if (!IntMap.isEither(m.flags, j)) {
+	public function hasNext():Bool
+	{
+		for (j in i...len)
+		{
+			if (!IntMap.isEither(IntMap.getFlag(m.flags, j)))
+			{
 				i = j;
 				return true;
 			}
@@ -467,7 +535,8 @@ private class IntMapKeyIterator<T> {
 		return false;
 	}
 
-	public function next():Int {
+	public function next():Int
+	{
 		var ret = m._keys[i];
 #if !no_map_cache
 		m.cachedIndex = i;
@@ -480,20 +549,24 @@ private class IntMapKeyIterator<T> {
 
 @:access(haxe.ds.IntMap)
 @:final
-private class IntMapValueIterator<T> {
+private class IntMapValueIterator<T>
+{
 	var m:IntMap<T>;
 	var i:Int;
 	var len:Int;
 
-	public function new(m:IntMap<T>) {
+	public function new(m:IntMap<T>)
+	{
 		this.i = 0;
 		this.m = m;
 		this.len = m.nBuckets;
 	}
 
 	public function hasNext():Bool {
-		for (j in i...len) {
-			if (!IntMap.isEither(m.flags, j)) {
+		for (j in i...len)
+		{
+			if (!IntMap.isEither(IntMap.getFlag(m.flags, j)))
+			{
 				i = j;
 				return true;
 			}
@@ -501,7 +574,8 @@ private class IntMapValueIterator<T> {
 		return false;
 	}
 
-	public inline function next():T {
+	public inline function next():T
+	{
 		return m.vals[i++];
 	}
 }

+ 43 - 25
std/cs/_std/haxe/ds/StringMap.hx

@@ -71,9 +71,11 @@ import cs.NativeArray;
 		if (nOccupied >= upperBound)
 		{
 			if (nBuckets > (size << 1))
+			{
 				resize(nBuckets - 1); //clear "deleted" elements
-			else
+			} else {
 				resize(nBuckets + 2);
+			}
 		}
 
 		var hashes = hashes, keys = _keys, hashes = hashes;
@@ -84,7 +86,7 @@ import cs.NativeArray;
 			var i = k & mask, nProbes = 0;
 
 			var delKey = -1;
-			//for speed up
+			// to speed things up, don't loop if the first bucket is already free
 			if (isEmpty(hashes[i])) {
 				x = i;
 			} else {
@@ -92,7 +94,9 @@ import cs.NativeArray;
 				while(! (isEmpty(flag = hashes[i]) || (flag == k && _keys[i] == key)) )
 				{
 					if (isDel(flag) && delKey == -1)
+					{
 						delKey = i;
+					}
 					i = (i + ++nProbes) & mask;
 #if DEBUG_HASHTBL
 					probeTimes++;
@@ -102,9 +106,11 @@ import cs.NativeArray;
 				}
 
 				if (isEmpty(flag) && delKey != -1)
+				{
 					x = delKey;
-				else
+				} else {
 					x = i;
+				}
 			}
 
 #if DEBUG_HASHTBL
@@ -147,7 +153,7 @@ import cs.NativeArray;
 			var mask = nBuckets - 1, hash = hash(key), k = hash, nProbes = 0;
 			var i = k & mask;
 			var last = i, flag;
-			//var inc = getInc(k, mask);
+			// if we hit an empty bucket, it means we're done
 			while (!isEmpty(flag = hashes[i]) && (isDel(flag) || flag != k || keys[i] != key))
 			{
 				i = (i + ++nProbes) & mask;
@@ -224,22 +230,23 @@ import cs.NativeArray;
 					while (true) /* kick-out process; sort of like in Cuckoo hashing */
 					{
 						var nProbes = 0;
-						//var inc = getInc(k, newMask);
 						var i = k & newMask;
 
 						while (!isEmpty(newHash[i]))
+						{
 							i = (i + ++nProbes) & newMask;
+						}
 
 						newHash[i] = k;
 
 						if (i < nBuckets && !isEither(k = hashes[i])) /* kick out the existing element */
 						{
-							{
+							{ // inlined swap
 								var tmp = _keys[i];
 								_keys[i] = key;
 								key = tmp;
 							}
-							{
+							{ // inlined swap
 								var tmp = vals[i];
 								vals[i] = val;
 								val = tmp;
@@ -257,12 +264,12 @@ import cs.NativeArray;
 
 			if (nBuckets > newNBuckets) /* shrink the hash table */
 			{
-				{
+				{ // inlined swap
 					var k = new NativeArray(newNBuckets);
 					arrayCopy(_keys, 0, k, 0, newNBuckets);
 					this._keys = k;
 				}
-				{
+				{ // inlined swap
 					var v = new NativeArray(newNBuckets);
 					arrayCopy(vals, 0, v, 0, newNBuckets);
 					this.vals = v;
@@ -361,7 +368,9 @@ import cs.NativeArray;
 		} else {
 #if !no_map_cache
 			if (cachedKey == key)
+			{
 				cachedIndex = -1;
+			}
 #end
 			hashes[idx] = FLAG_DEL;
 			_keys[idx] = null;
@@ -426,9 +435,6 @@ import cs.NativeArray;
 		return ++x;
 	}
 
-	@:extern private static inline function getInc(k:Int, mask:Int):Int //return 1 for linear probing
-		return (((k) >> 3 ^ (k) << 3) | 1) & (mask);
-
 	@:extern private static inline function isEither(v:HashType):Bool
 		return (v & 0xFFFFFFFE) == 0;
 
@@ -438,7 +444,7 @@ import cs.NativeArray;
 	@:extern private static inline function isDel(v:HashType):Bool
 		return v == FLAG_DEL;
 
-	//guarantee: Whatever this function is, it will never return 0 nor 1
+	// guarantee: Whatever this function is, it will never return 0 nor 1
 	@:extern private static inline function hash(s:String):HashType
 	{
 		var k:Int = untyped s.GetHashCode();
@@ -482,20 +488,25 @@ private typedef HashType = Int;
 
 @:final
 @:access(haxe.ds.StringMap)
-private class StringMapKeyIterator<T> {
+private class StringMapKeyIterator<T>
+{
 	var m:StringMap<T>;
 	var i:Int;
 	var len:Int;
 
-	public function new(m:StringMap<T>) {
+	public function new(m:StringMap<T>)
+	{
 		this.m = m;
 		this.i = 0;
 		this.len = m.nBuckets;
 	}
 
-	public function hasNext():Bool {
-		for (j in i...len) {
-			if (!StringMap.isEither(m.hashes[j])) {
+	public function hasNext():Bool
+	{
+		for (j in i...len)
+		{
+			if (!StringMap.isEither(m.hashes[j]))
+			{
 				i = j;
 				return true;
 			}
@@ -503,7 +514,8 @@ private class StringMapKeyIterator<T> {
 		return false;
 	}
 
-	public function next():String {
+	public function next():String
+	{
 		var ret = m._keys[i];
 #if !no_map_cache
 		m.cachedIndex = i;
@@ -516,20 +528,25 @@ private class StringMapKeyIterator<T> {
 
 @:final
 @:access(haxe.ds.StringMap)
-private class StringMapValueIterator<T> {
+private class StringMapValueIterator<T>
+{
 	var m:StringMap<T>;
 	var i:Int;
 	var len:Int;
 
-	public function new(m:StringMap<T>) {
+	public function new(m:StringMap<T>)
+	{
 		this.m = m;
 		this.i = 0;
 		this.len = m.nBuckets;
 	}
 
-	public function hasNext():Bool {
-		for (j in i...len) {
-			if (!StringMap.isEither(m.hashes[j])) {
+	public function hasNext():Bool
+	{
+		for (j in i...len)
+		{
+			if (!StringMap.isEither(m.hashes[j]))
+			{
 				i = j;
 				return true;
 			}
@@ -537,7 +554,8 @@ private class StringMapValueIterator<T> {
 		return false;
 	}
 
-	public inline function next():T {
+	public inline function next():T
+	{
 		return m.vals[i++];
 	}
 }

+ 219 - 113
std/java/_std/haxe/ds/IntMap.hx

@@ -44,74 +44,83 @@ import java.NativeArray;
 	private var nOccupied:Int;
 	private var upperBound:Int;
 
+#if !no_map_cache
 	private var cachedKey:Int;
 	private var cachedIndex:Int;
+#end
 
 	public function new() : Void
 	{
+#if !no_map_cache
 		cachedIndex = -1;
+#end
 	}
 
 	public function set( key : Int, value : T ) : Void
 	{
-		var x:Int;
+		var targetIndex:Int;
 		if (nOccupied >= upperBound)
 		{
 			if (nBuckets > (size << 1))
+			{
 				resize(nBuckets - 1); //clear "deleted" elements
-			else
+			} else {
 				resize(nBuckets + 1);
+			}
 		}
 
 		var flags = flags, _keys = _keys;
 		{
-			var mask = nBuckets - 1;
-			var site = x = nBuckets;
-			var k = hash(key);
-			var i = k & mask;
-
-			var delKey = -1;
-			//for speed up
-			if (flagIsEmpty(flags, i)) {
-				x = i;
+			var mask = nBuckets - 1,
+			    hashedKey = hash(key),
+			    curIndex = hashedKey & mask;
+
+			var delKey = -1,
+			    curFlag = 0;
+			// to speed things up, don't loop if the first bucket is already free
+			if (isEmpty(getFlag(flags, curIndex))) {
+				targetIndex = curIndex;
 			} else {
-				var inc = getInc(k, mask);
-				var last = i;
-				while (! (flagIsEmpty(flags, i) || _keys[i] == key) )
+				var inc = getInc(hashedKey, mask),
+				    last = curIndex;
+				while (! (_keys[curIndex] == key || isEmpty(curFlag = getFlag(flags, curIndex))) )
 				{
-					if (flagIsDel(flags,i) && delKey == -1)
-						delKey = i;
-					i = (i + inc) & mask;
-#if DEBUG_HASHTBL
-					if (i == last)
+					if (delKey == -1 && isDel(curFlag))
 					{
-						throw "assert";
+						delKey = curIndex;
 					}
+					curIndex = (curIndex + inc) & mask;
+#if debug
+					assert(curIndex != last);
 #end
 				}
 
-				if (flagIsEmpty(flags, i) && delKey != -1)
-					x = delKey;
-				else
-					x = i;
+				if (delKey != -1 && isEmpty(getFlag(flags, curIndex))) {
+					targetIndex = delKey;
+				} else {
+					targetIndex = curIndex;
+				}
 			}
 		}
 
-		if (flagIsEmpty(flags, x))
+		var flag = getFlag(flags, targetIndex);
+		if (isEmpty(flag))
 		{
-			_keys[x] = key;
-			vals[x] = value;
-			setIsBothFalse(flags, x);
+			_keys[targetIndex] = key;
+			vals[targetIndex] = value;
+			setIsBothFalse(flags, targetIndex);
 			size++;
 			nOccupied++;
-		} else if (flagIsDel(flags, x)) {
-			_keys[x] = key;
-			vals[x] = value;
-			setIsBothFalse(flags, x);
+		} else if (isDel(flag)) {
+			_keys[targetIndex] = key;
+			vals[targetIndex] = value;
+			setIsBothFalse(flags, targetIndex);
 			size++;
 		} else {
-			assert(_keys[x] == key);
-			vals[x] = value;
+#if debug
+			assert(_keys[targetIndex] == key);
+#end
+			vals[targetIndex] = value;
 		}
 	}
 
@@ -121,17 +130,28 @@ import java.NativeArray;
 		{
 			var flags = flags, _keys = _keys;
 
-			var mask = nBuckets - 1, k = hash(key);
-			var i = k & mask;
-			var inc = getInc(k, mask); /* inc == 1 for linear probing */
-			var last = i;
-			while (!flagIsEmpty(flags, i) && (flagIsDel(flags, i) || _keys[i] != key))
+			var mask = nBuckets - 1,
+			    k = hash(key),
+			    index = k & mask,
+			    curFlag = -1,
+			    inc = getInc(k, mask), /* inc == 1 for linear probing */
+			    last = index;
+			do
 			{
-				i = (i + inc) & mask;
-				if (i == last)
-					return -1;
-			}
-			return isEither(flags, i) ? -1 : i;
+				if (_keys[index] == key) {
+					if (isEmpty(curFlag = getFlag(flags, index)))
+					{
+						index = (index + inc) & mask;
+						continue;
+					} else if (isDel(curFlag)) {
+						return -1;
+					} else {
+						return index;
+					}
+				} else {
+					index = (index + inc) & mask;
+				}
+			} while (index != last);
 		}
 
 		return -1;
@@ -140,17 +160,20 @@ import java.NativeArray;
 	public function get( key : Int ) : Null<T>
 	{
 		var idx = -1;
+#if !no_map_cache
 		if (cachedKey == key && ( (idx = cachedIndex) != -1 ))
 		{
 			return vals[idx];
 		}
+#end
 
 		idx = lookup(key);
 		if (idx != -1)
 		{
+#if !no_map_cache
 			cachedKey = key;
 			cachedIndex = idx;
-
+#end
 			return vals[idx];
 		}
 
@@ -160,17 +183,20 @@ import java.NativeArray;
 	private function getDefault( key : Int, def : T ) : T
 	{
 		var idx = -1;
+#if !no_map_cache
 		if (cachedKey == key && ( (idx = cachedIndex) != -1 ))
 		{
 			return vals[idx];
 		}
+#end
 
 		idx = lookup(key);
 		if (idx != -1)
 		{
+#if !no_map_cache
 			cachedKey = key;
 			cachedIndex = idx;
-
+#end
 			return vals[idx];
 		}
 
@@ -180,16 +206,20 @@ import java.NativeArray;
 	public function exists( key : Int ) : Bool
 	{
 		var idx = -1;
+#if !no_map_cache
 		if (cachedKey == key && ( (idx = cachedIndex) != -1 ))
 		{
 			return true;
 		}
+#end
 
 		idx = lookup(key);
 		if (idx != -1)
 		{
+#if !no_map_cache
 			cachedKey = key;
 			cachedIndex = idx;
+#end
 
 			return true;
 		}
@@ -200,7 +230,9 @@ import java.NativeArray;
 	public function remove( key : Int ) : Bool
 	{
 		var idx = -1;
+#if !no_map_cache
 		if (! (cachedKey == key && ( (idx = cachedIndex) != -1 )))
+#end
 		{
 			idx = lookup(key);
 		}
@@ -209,16 +241,23 @@ import java.NativeArray;
 		{
 			return false;
 		} else {
+#if !no_map_cache
 			if (cachedKey == key)
+			{
 				cachedIndex = -1;
-
-			if (!isEither(flags, idx))
+			}
+#end
+			if (!isEither(getFlag(flags, idx)))
 			{
 				setIsDelTrue(flags, idx);
 				--size;
 
 				vals[idx] = null;
-				_keys[idx] = 0;
+				// we do NOT reset the keys here, as unlike StringMap, we check for keys equality
+				// and stop if we find a key that is equal to the one we're looking for
+				// setting this to 0 will allow the hash to contain duplicate `0` keys
+				// (see #6457)
+				// _keys[idx] = 0;
 			}
 
 			return true;
@@ -240,17 +279,23 @@ import java.NativeArray;
 				var nfSize = flagsSize(newNBuckets);
 				newFlags = new NativeArray( nfSize );
 				for (i in 0...nfSize)
-					newFlags[i] = 0xaaaaaaaa;
+				{
+					newFlags[i] = 0xaaaaaaaa; // isEmpty = true; isDel = false
+				}
 				if (nBuckets < newNBuckets) //expand
 				{
 					var k = new NativeArray(newNBuckets);
 					if (_keys != null)
+					{
 						arrayCopy(_keys, 0, k, 0, nBuckets);
+					}
 					_keys = k;
 
 					var v = new NativeArray(newNBuckets);
 					if (vals != null)
+					{
 						arrayCopy(vals, 0, v, 0, nBuckets);
+					}
 					vals = v;
 				} //otherwise shrink
 			}
@@ -258,9 +303,11 @@ import java.NativeArray;
 
 		if (j != 0)
 		{ //rehashing is required
+#if !no_map_cache
 			//resetting cache
 			cachedKey = 0;
 			cachedIndex = -1;
+#end
 
 			j = -1;
 			var nBuckets = nBuckets, _keys = _keys, vals = vals, flags = flags;
@@ -268,12 +315,13 @@ import java.NativeArray;
 			var newMask = newNBuckets - 1;
 			while (++j < nBuckets)
 			{
-				if (!isEither(flags, j))
+				if (!isEither(getFlag(flags, j)))
 				{
 					var key = _keys[j];
 					var val = vals[j];
 
-					_keys[j] = 0;
+					// do not set keys as 0 - see comment about #6457
+					// _keys[j] = 0;
 					vals[j] = cast null;
 					setIsDelTrue(flags, j);
 					while (true) /* kick-out process; sort of like in Cuckoo hashing */
@@ -281,11 +329,13 @@ import java.NativeArray;
 						var k = hash(key);
 						var inc = getInc(k, newMask);
 						var i = k & newMask;
-						while (!flagIsEmpty(newFlags, i))
+						while (!isEmpty(getFlag(newFlags, i)))
+						{
 							i = (i + inc) & newMask;
+						}
 						setIsEmptyFalse(newFlags, i);
 
-						if (i < nBuckets && !isEither(flags, i)) /* kick out the existing element */
+						if (i < nBuckets && !isEither(getFlag(flags, i))) /* kick out the existing element */
 						{
 							{
 								var tmp = _keys[i];
@@ -333,59 +383,18 @@ import java.NativeArray;
 		Returns an iterator of all keys in the hashtable.
 		Implementation detail: Do not set() any new value while iterating, as it may cause a resize, which will break iteration
 	**/
-	public function keys() : Iterator<Int>
+	public inline function keys() : Iterator<Int>
 	{
-		var i = 0;
-		var len = nBuckets;
-		return {
-			hasNext: function() {
-				for (j in i...len)
-				{
-					if (!isEither(flags, j))
-					{
-						i = j;
-						return true;
-					}
-				}
-				return false;
-			},
-			next: function() {
-				var ret = _keys[i];
-				cachedIndex = i;
-				cachedKey = ret;
-
-				i = i + 1;
-				return ret;
-			}
-		};
+		return new IntMapKeyIterator(this);
 	}
 
 	/**
 		Returns an iterator of all values in the hashtable.
 		Implementation detail: Do not set() any new value while iterating, as it may cause a resize, which will break iteration
 	**/
-	public function iterator() : Iterator<T>
+	public inline function iterator() : Iterator<T>
 	{
-		var i = 0;
-		var len = nBuckets;
-		return {
-			hasNext: function() {
-				for (j in i...len)
-				{
-					if (!isEither(flags, j))
-					{
-						i = j;
-						return true;
-					}
-				}
-				return false;
-			},
-			next: function() {
-				var ret = vals[i];
-				i = i + 1;
-				return ret;
-			}
-		};
+		return new IntMapValueIterator(this);
 	}
 
 	public function copy() : IntMap<T> {
@@ -431,26 +440,50 @@ import java.NativeArray;
 	private static inline function hash(i:Int):Int
 		return i;
 
-	private static inline function flagIsEmpty(flag:NativeArray<Int>, i:Int):Bool
-		return ( (flag[i >> 4] >>> ((i & 0xf) << 1)) & 2 ) != 0;
+	// flags represents a bit array with 2 significant bits for each index
+	// one bit for deleted (1), one for empty (2)
+	// so what this function does is:
+	//  * gets the integer with (flags / 16)
+	//  * shifts those bits to the right ((flags % 16) * 2) places
+	//  * masks it with 0b11
+	private static inline function getFlag(flags:NativeArray<Int>, i:Int):Int
+	{
+		return ( (flags[i >> 4] >>> ((i & 0xf) << 1)) & 3 );
+	}
 
-	private static inline function flagIsDel(flag:NativeArray<Int>, i:Int):Bool
-		return ((flag[i >> 4] >>> ((i & 0xf) << 1)) & 1) != 0;
+	private static inline function isDel(flag:Int):Bool
+	{
+		return (flag & 1) != 0;
+	}
 
-	private static inline function isEither(flag:NativeArray<Int>, i:Int):Bool
-		return ((flag[i >> 4] >>> ((i & 0xf) << 1)) & 3) != 0;
+	private static inline function isEmpty(flag:Int):Bool
+	{
+		return (flag & 2) != 0;
+	}
+
+	private static inline function isEither(flag:Int):Bool {
+		return flag != 0;
+	}
 
-	private static inline function setIsDelFalse(flag:NativeArray<Int>, i:Int):Void
-		flag[i >> 4] &= ~(1 << ((i & 0xf) << 1));
+	private static inline function setIsDelFalse(flags:NativeArray<Int>, i:Int):Void
+	{
+		flags[i >> 4] &= ~(1 << ((i & 0xf) << 1));
+	}
 
-	private static inline function setIsEmptyFalse(flag:NativeArray<Int>, i:Int):Void
-		flag[i >> 4] &= ~(2 << ((i & 0xf) << 1));
+	private static inline function setIsEmptyFalse(flags:NativeArray<Int>, i:Int):Void
+	{
+		flags[i >> 4] &= ~(2 << ((i & 0xf) << 1));
+	}
 
-	private static inline function setIsBothFalse(flag:NativeArray<Int>, i:Int):Void
-		flag[i >> 4] &= ~(3 << ((i & 0xf) << 1));
+	private static inline function setIsBothFalse(flags:NativeArray<Int>, i:Int):Void
+	{
+		flags[i >> 4] &= ~(3 << ((i & 0xf) << 1));
+	}
 
-	private static inline function setIsDelTrue(flag:NativeArray<Int>, i:Int):Void
-		flag[i >> 4] |= 1 << ((i & 0xf) << 1);
+	private static inline function setIsDelTrue(flags:NativeArray<Int>, i:Int):Void
+	{
+		flags[i >> 4] |= 1 << ((i & 0xf) << 1);
+	}
 
 	private static inline function roundUp(x:Int):Int
 	{
@@ -466,3 +499,76 @@ import java.NativeArray;
 	private static inline function flagsSize(m:Int):Int
 		return ((m) < 16? 1 : (m) >> 4);
 }
+
+@:access(haxe.ds.IntMap)
+@:final
+private class IntMapKeyIterator<T>
+{
+	var m:IntMap<T>;
+	var i:Int;
+	var len:Int;
+
+	public function new(m:IntMap<T>)
+	{
+		this.i = 0;
+		this.m = m;
+		this.len = m.nBuckets;
+	}
+
+	public function hasNext():Bool
+	{
+		for (j in i...len)
+		{
+			if (!IntMap.isEither(IntMap.getFlag(m.flags, j)))
+			{
+				i = j;
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public function next():Int
+	{
+		var ret = m._keys[i];
+#if !no_map_cache
+		m.cachedIndex = i;
+		m.cachedKey = ret;
+#end
+		i++;
+		return ret;
+	}
+}
+
+@:access(haxe.ds.IntMap)
+@:final
+private class IntMapValueIterator<T>
+{
+	var m:IntMap<T>;
+	var i:Int;
+	var len:Int;
+
+	public function new(m:IntMap<T>)
+	{
+		this.i = 0;
+		this.m = m;
+		this.len = m.nBuckets;
+	}
+
+	public function hasNext():Bool {
+		for (j in i...len)
+		{
+			if (!IntMap.isEither(IntMap.getFlag(m.flags, j)))
+			{
+				i = j;
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public inline function next():T
+	{
+		return m.vals[i++];
+	}
+}

+ 97 - 43
std/java/_std/haxe/ds/ObjectMap.hx

@@ -46,8 +46,10 @@ import java.NativeArray;
 	private var nOccupied:Int;
 	private var upperBound:Int;
 
+#if !no_map_cache
 	private var cachedKey:K;
 	private var cachedIndex:Int;
+#end
 
 #if DEBUG_HASHTBL
 	private var totalProbes:Int;
@@ -58,7 +60,9 @@ import java.NativeArray;
 
 	public function new() : Void
 	{
+#if !no_map_cache
 		cachedIndex = -1;
+#end
 	}
 
 	public function set( key : K, value : V ) : Void
@@ -129,8 +133,10 @@ import java.NativeArray;
 			vals[x] = value;
 		}
 
+#if !no_map_cache
 		cachedIndex = x;
 		cachedKey = key;
+#end
 	}
 
 	@:final private function lookup( key : K ) : Int
@@ -196,8 +202,10 @@ import java.NativeArray;
 		if (j != 0)
 		{ //rehashing is required
 			//resetting cache
+#if !no_map_cache
 			cachedKey = null;
 			cachedIndex = -1;
+#end
 
 			j = -1;
 			var nBuckets = nBuckets, _keys = _keys, vals = vals, hashes = hashes;
@@ -272,16 +280,20 @@ import java.NativeArray;
 	public function get( key : K ) : Null<V>
 	{
 		var idx = -1;
+#if !no_map_cache
 		if (cachedKey == key && ( (idx = cachedIndex) != -1 ))
 		{
 			return vals[idx];
 		}
+#end
 
 		idx = lookup(key);
 		if (idx != -1)
 		{
+#if !no_map_cache
 			cachedKey = key;
 			cachedIndex = idx;
+#end
 
 			return vals[idx];
 		}
@@ -292,16 +304,20 @@ import java.NativeArray;
 	private function getDefault( key : K, def : V ) : V
 	{
 		var idx = -1;
+#if !no_map_cache
 		if (cachedKey == key && ( (idx = cachedIndex) != -1 ))
 		{
 			return vals[idx];
 		}
+#end
 
 		idx = lookup(key);
 		if (idx != -1)
 		{
+#if !no_map_cache
 			cachedKey = key;
 			cachedIndex = idx;
+#end
 
 			return vals[idx];
 		}
@@ -312,16 +328,20 @@ import java.NativeArray;
 	public function exists( key : K ) : Bool
 	{
 		var idx = -1;
+#if !no_map_cache
 		if (cachedKey == key && ( (idx = cachedIndex) != -1 ))
 		{
 			return true;
 		}
+#end
 
 		idx = lookup(key);
 		if (idx != -1)
 		{
+#if !no_map_cache
 			cachedKey = key;
 			cachedIndex = idx;
+#end
 
 			return true;
 		}
@@ -332,7 +352,9 @@ import java.NativeArray;
 	public function remove( key : K ) : Bool
 	{
 		var idx = -1;
+#if !no_map_cache
 		if (! (cachedKey == key && ( (idx = cachedIndex) != -1 )))
+#end
 		{
 			idx = lookup(key);
 		}
@@ -341,8 +363,10 @@ import java.NativeArray;
 		{
 			return false;
 		} else {
+#if !no_map_cache
 			if (cachedKey == key)
 				cachedIndex = -1;
+#end
 
 			hashes[idx] = FLAG_DEL;
 			_keys[idx] = null;
@@ -359,29 +383,7 @@ import java.NativeArray;
 	**/
 	public function keys() : Iterator<K>
 	{
-		var i = 0;
-		var len = nBuckets;
-		return {
-			hasNext: function() {
-				for (j in i...len)
-				{
-					if (!isEither(hashes[j]))
-					{
-						i = j;
-						return true;
-					}
-				}
-				return false;
-			},
-			next: function() {
-				var ret = _keys[i];
-				cachedIndex = i;
-				cachedKey = ret;
-
-				i = i + 1;
-				return ret;
-			}
-		};
+		return new ObjectMapKeyIterator(this);
 	}
 
 	/**
@@ -390,26 +392,7 @@ import java.NativeArray;
 	**/
 	public function iterator() : Iterator<V>
 	{
-		var i = 0;
-		var len = nBuckets;
-		return {
-			hasNext: function() {
-				for (j in i...len)
-				{
-					if (!isEither(hashes[j]))
-					{
-						i = j;
-						return true;
-					}
-				}
-				return false;
-			},
-			next: function() {
-				var ret = vals[i];
-				i = i + 1;
-				return ret;
-			}
-		};
+		return new ObjectMapValueIterator(this);
 	}
 
 
@@ -501,4 +484,75 @@ import java.NativeArray;
 	}
 }
 
+@:access(haxe.ds.ObjectMap)
+@:final
+private class ObjectMapKeyIterator<T:{},V> {
+	var m:ObjectMap<T,V>;
+	var i:Int;
+	var len:Int;
+
+	public function new(m:ObjectMap<T,V>) {
+		this.i = 0;
+		this.m = m;
+		this.len = m.nBuckets;
+	}
+
+	public function hasNext():Bool {
+		for (j in i...len)
+		{
+			if (!ObjectMap.isEither(m.hashes[j]))
+			{
+				i = j;
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public function next() : T {
+		var ret = m._keys[i];
+
+#if !no_map_cache
+		m.cachedIndex = i;
+		m.cachedKey = ret;
+#end
+
+		i = i + 1;
+		return ret;
+	}
+}
+
+@:access(haxe.ds.ObjectMap)
+@:final
+private class ObjectMapValueIterator<K:{},T> {
+	var m:ObjectMap<K,T>;
+	var i:Int;
+	var len:Int;
+
+	public function new(m:ObjectMap<K,T>)
+	{
+		this.i = 0;
+		this.m = m;
+		this.len = m.nBuckets;
+	}
+
+	public function hasNext() : Bool {
+		for (j in i...len)
+		{
+			if (!ObjectMap.isEither(m.hashes[j]))
+			{
+				i = j;
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public inline function next():T {
+		var ret = m.vals[i];
+		i = i + 1;
+		return ret;
+	}
+}
+
 private typedef HashType = Int;

+ 119 - 56
std/java/_std/haxe/ds/StringMap.hx

@@ -46,8 +46,10 @@ import java.NativeArray;
 	private var nOccupied:Int;
 	private var upperBound:Int;
 
+#if !no_map_cache
 	private var cachedKey:String;
 	private var cachedIndex:Int;
+#end
 
 #if DEBUG_HASHTBL
 	private var totalProbes:Int;
@@ -58,7 +60,9 @@ import java.NativeArray;
 
 	public function new() : Void
 	{
+#if !no_map_cache
 		cachedIndex = -1;
+#end
 	}
 
 	public function set( key : String, value : T ) : Void
@@ -67,9 +71,11 @@ import java.NativeArray;
 		if (nOccupied >= upperBound)
 		{
 			if (nBuckets > (size << 1))
+			{
 				resize(nBuckets - 1); //clear "deleted" elements
-			else
+			} else {
 				resize(nBuckets + 2);
+			}
 		}
 
 		var hashes = hashes, keys = _keys, hashes = hashes;
@@ -80,16 +86,17 @@ import java.NativeArray;
 			var i = k & mask, nProbes = 0;
 
 			var delKey = -1;
-			//for speed up
+			// to speed things up, don't loop if the first bucket is already free
 			if (isEmpty(hashes[i])) {
 				x = i;
 			} else {
-				//var inc = getInc(k, mask);
 				var last = i, flag;
 				while(! (isEmpty(flag = hashes[i]) || (flag == k && _keys[i] == key)) )
 				{
 					if (isDel(flag) && delKey == -1)
+					{
 						delKey = i;
+					}
 					i = (i + ++nProbes) & mask;
 #if DEBUG_HASHTBL
 					probeTimes++;
@@ -99,9 +106,11 @@ import java.NativeArray;
 				}
 
 				if (isEmpty(flag) && delKey != -1)
+				{
 					x = delKey;
-				else
+				} else {
 					x = i;
+				}
 			}
 
 #if DEBUG_HASHTBL
@@ -129,8 +138,10 @@ import java.NativeArray;
 			vals[x] = value;
 		}
 
+#if !no_map_cache
 		cachedIndex = x;
 		cachedKey = key;
+#end
 	}
 
 	@:final private function lookup( key : String ) : Int
@@ -142,7 +153,7 @@ import java.NativeArray;
 			var mask = nBuckets - 1, hash = hash(key), k = hash, nProbes = 0;
 			var i = k & mask;
 			var last = i, flag;
-			//var inc = getInc(k, mask);
+			// if we hit an empty bucket, it means we're done
 			while (!isEmpty(flag = hashes[i]) && (isDel(flag) || flag != k || keys[i] != key))
 			{
 				i = (i + ++nProbes) & mask;
@@ -196,8 +207,10 @@ import java.NativeArray;
 		if (j != 0)
 		{ //rehashing is required
 			//resetting cache
+#if !no_map_cache
 			cachedKey = null;
 			cachedIndex = -1;
+#end
 
 			j = -1;
 			var nBuckets = nBuckets, _keys = _keys, vals = vals, hashes = hashes;
@@ -211,15 +224,18 @@ import java.NativeArray;
 					var key = _keys[j];
 					var val = vals[j];
 
+					_keys[j] = null;
+					vals[j] = cast null;
 					hashes[j] = FLAG_DEL;
 					while (true) /* kick-out process; sort of like in Cuckoo hashing */
 					{
 						var nProbes = 0;
-						//var inc = getInc(k, newMask);
 						var i = k & newMask;
 
 						while (!isEmpty(newHash[i]))
+						{
 							i = (i + ++nProbes) & newMask;
+						}
 
 						newHash[i] = k;
 
@@ -270,17 +286,19 @@ import java.NativeArray;
 	public function get( key : String ) : Null<T>
 	{
 		var idx = -1;
+#if !no_map_cache
 		if (cachedKey == key && ( (idx = cachedIndex) != -1 ))
 		{
 			return vals[idx];
 		}
-
+#end
 		idx = lookup(key);
 		if (idx != -1)
 		{
+#if !no_map_cache
 			cachedKey = key;
 			cachedIndex = idx;
-
+#end
 			return vals[idx];
 		}
 
@@ -290,16 +308,20 @@ import java.NativeArray;
 	private function getDefault( key : String, def : T ) : T
 	{
 		var idx = -1;
+#if !no_map_cache
 		if (cachedKey == key && ( (idx = cachedIndex) != -1 ))
 		{
 			return vals[idx];
 		}
+#end
 
 		idx = lookup(key);
 		if (idx != -1)
 		{
+#if !no_map_cache
 			cachedKey = key;
 			cachedIndex = idx;
+#end
 
 			return vals[idx];
 		}
@@ -310,17 +332,20 @@ import java.NativeArray;
 	public function exists( key : String ) : Bool
 	{
 		var idx = -1;
+#if !no_map_cache
 		if (cachedKey == key && ( (idx = cachedIndex) != -1 ))
 		{
 			return true;
 		}
+#end
 
 		idx = lookup(key);
 		if (idx != -1)
 		{
+#if !no_map_cache
 			cachedKey = key;
 			cachedIndex = idx;
-
+#end
 			return true;
 		}
 
@@ -330,7 +355,9 @@ import java.NativeArray;
 	public function remove( key : String ) : Bool
 	{
 		var idx = -1;
+#if !no_map_cache
 		if (! (cachedKey == key && ( (idx = cachedIndex) != -1 )))
+#end
 		{
 			idx = lookup(key);
 		}
@@ -339,9 +366,12 @@ import java.NativeArray;
 		{
 			return false;
 		} else {
+#if !no_map_cache
 			if (cachedKey == key)
+			{
 				cachedIndex = -1;
-
+			}
+#end
 			hashes[idx] = FLAG_DEL;
 			_keys[idx] = null;
 			vals[idx] = null;
@@ -355,61 +385,20 @@ import java.NativeArray;
 		Returns an iterator of all keys in the hashtable.
 		Implementation detail: Do not set() any new value while iterating, as it may cause a resize, which will break iteration
 	**/
-	public function keys() : Iterator<String>
+	public inline function keys() : Iterator<String>
 	{
-		var i = 0;
-		var len = nBuckets;
-		return {
-			hasNext: function() {
-				for (j in i...len)
-				{
-					if (!isEither(hashes[j]))
-					{
-						i = j;
-						return true;
-					}
-				}
-				return false;
-			},
-			next: function() {
-				var ret = _keys[i];
-				cachedIndex = i;
-				cachedKey = ret;
-
-				i = i + 1;
-				return ret;
-			}
-		};
+		return new StringMapKeyIterator(this);
 	}
 
 	/**
 		Returns an iterator of all values in the hashtable.
 		Implementation detail: Do not set() any new value while iterating, as it may cause a resize, which will break iteration
 	**/
-	public function iterator() : Iterator<T>
+	public inline function iterator() : Iterator<T>
 	{
-		var i = 0;
-		var len = nBuckets;
-		return {
-			hasNext: function() {
-				for (j in i...len)
-				{
-					if (!isEither(hashes[j]))
-					{
-						i = j;
-						return true;
-					}
-				}
-				return false;
-			},
-			next: function() {
-				var ret = vals[i];
-				i = i + 1;
-				return ret;
-			}
-		};
+		return new StringMapValueIterator(this);
 	}
-	
+
 	public function copy() : StringMap<T> {
 		var copied = new StringMap();
 		for(key in keys()) copied.set(key, get(key));
@@ -499,3 +488,77 @@ import java.NativeArray;
 }
 
 private typedef HashType = Int;
+
+@:final
+@:access(haxe.ds.StringMap)
+private class StringMapKeyIterator<T>
+{
+	var m:StringMap<T>;
+	var i:Int;
+	var len:Int;
+
+	public function new(m:StringMap<T>)
+	{
+		this.m = m;
+		this.i = 0;
+		this.len = m.nBuckets;
+	}
+
+	public function hasNext():Bool
+	{
+		for (j in i...len)
+		{
+			if (!StringMap.isEither(m.hashes[j]))
+			{
+				i = j;
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public function next():String
+	{
+		var ret = m._keys[i];
+#if !no_map_cache
+		m.cachedIndex = i;
+		m.cachedKey = ret;
+#end
+		i++;
+		return ret;
+	}
+}
+
+@:final
+@:access(haxe.ds.StringMap)
+private class StringMapValueIterator<T>
+{
+	var m:StringMap<T>;
+	var i:Int;
+	var len:Int;
+
+	public function new(m:StringMap<T>)
+	{
+		this.m = m;
+		this.i = 0;
+		this.len = m.nBuckets;
+	}
+
+	public function hasNext():Bool
+	{
+		for (j in i...len)
+		{
+			if (!StringMap.isEither(m.hashes[j]))
+			{
+				i = j;
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public inline function next():T
+	{
+		return m.vals[i++];
+	}
+}

+ 30 - 0
tests/unit/src/unit/issues/Issue6457.hx

@@ -0,0 +1,30 @@
+package unit.issues;
+
+class Issue6457 extends Test {
+  public function test() {
+    var m = new Map();
+    m.set(24, 124);
+    m.set(0, 11);
+    m.remove(24);
+    m.set(0, 10);
+    var keys = [ for (k in m.keys()) k];
+    eq(keys.length, 1);
+    var vals = [ for (k in m) k];
+    eq(vals.length, 1);
+    eq(vals[0], 10);
+    eq(m.toString(), '{0 => 10}');
+
+    var m = new Map();
+    m.set("c", 1);
+    m.set("z", 1);
+    var keys = [ for (k in m.keys()) k];
+    m.remove("c");
+    m.set("z", 10);
+    var keys = [ for (k in m.keys()) k];
+    eq(keys.length, 1);
+    var vals = [ for (k in m) k];
+    eq(vals.length, 1);
+    eq(vals[0], 10);
+    eq(m.toString(), '{z => 10}');
+  }
+}