Synchronization with Guava HashBiMap and synchronizedBiMap - java

I am getting an exception from a Guava BiMap's putIfAbsent method in a multi-thread situation. How should I correctly protect it from threading problems?
I create the map like this:
BiMap<Integer, java.net.URI> cache = com.google.common.collect.Maps.synchronizedBiMap(HashBiMap.create());
Then, the only times I ever modify the map are by cache.clear(); or cache.putIfAbsent(a,b)
I have occasionally seen this stack trace:
java.lang.IllegalArgumentException: value already present: http://example.com
at com.google.common.collect.HashBiMap.put(HashBiMap.java:279)
at com.google.common.collect.HashBiMap.put(HashBiMap.java:260)
at java.util.Map.putIfAbsent(Map.java:744)
at com.google.common.collect.Synchronized$SynchronizedMap.putIfAbsent(Synchronized.java:1120)
Is this a bug in HashBiMap or synchronizedBiMap? Or do I need to do extra work for thread safety?
Using guava-25.0-jre and Java(TM) SE Runtime Environment 1.8.0_152-b16

Because a BiMap provides a mapping from values to keys, as well as the usual Map mapping from keys to values, each value can only be paired with a single key. Trying to associate a value with more than one unique key will result in an IllegalArgumentException that you are seeing.
It does not sounds like your issue is threading related, rather data related.
As a example, this will throw a similar exception. The problem is the presence of value "Bar" with two separate keys "Foo" and "Baz":
public static void main(String[] args) {
BiMap<String, String> m = HashBiMap.create();
m.put("Foo", "Bar");
m.put("Baz", "Bar"); // Throws IllegalArgumentException "value already present"
}

This doesn't have anything to do with synchronization, but it's how BiMap works. You can reproduce it easily:
cache.putIfAbsent(1, URI.create("http://example.com"));
cache.putIfAbsent(2, URI.create("http://stackoverflow.com"));
System.out.println(cache);
// {1=http://example.com, 2=http://stackoverflow.com}
cache.putIfAbsent(3, URI.create("http://example.com"));
// java.lang.IllegalArgumentException: value already present: http://example.com
BiMap is "a map that preserves the uniqueness of its values as well as that of its keys." This means that you can't put example.com again, even under different key. See also wiki page describing BiMap:
BiMap.put(key, value) will throw an IllegalArgumentException if you attempt to map a key to an already-present value. If you wish to delete any preexisting entry with the specified value, use BiMap.forcePut(key, value) instead.
In your case you could use forcePut and not fail with an exception:
cache.forcePut(3, URI.create("http://example.com"));
System.out.println(cache);
// {2=http://stackoverflow.com, 3=http://example.com}

Related

Default methods and Lambda Supplier Callbacks

I welcome methods in the API to easily create default initialisations.
For example in HashMaps. But why have they not been provided with Supplier Lambda methods? - Or am I missing an important step, or did I not learn the latest java Api versions?
Standard (Java8) version:
Map<String,List<Integer>> datas = new HashMap<>();
List<Integer> integersList = datas.getOrDefault( "somekey", new ArrayList<>() );
which would instantiate a new ArrayList anytime the code is executed - no matter if the new list is needed or not.
Desired Lambda supplier version:
Map<String,List<Integer>> datas = new HashMap<>();
List<Integer> integersList = datas.getOrDefault( "somekey", ()->new ArrayList() );
Would instantiate (or execute some instantiation code) only in case demanded key is not within the map.
The code of the getOrDefault()-Method could look something like this:
public V getOrDefault( K key, Supplier<V> supplier ) {
if ( !super.containsKey( key ) && supplier != null ) {
super.put( key, supplier.get() );
}
return super.get( key );
}
Why did they(?) not build it that way initially or added such functionality later on?
I guess there is even more examples where Lambda would solve an unnecessary code execution - not just Maps as shown with this example.
By the way: sorry for re-asking a question but I would not know how to exactly look for my question with different terms...
Be welcome to post helpful links.
Thanks for your shared knowledge :-)
What you are looking for exists since Java 8. Take a look at the javadoc of the HashMap and specifically the method Hashmap.computeIfAbsent. This method allows for adding new entries to the HashMap if none can be found using the key provided.
Examaple:
Map<Integer, String> map = new HashMap();
String created = map.computeIfAbsent(1, k -> "Test");
System.out.println(created);
The code above will trigger the HashMap to call the provided Function to add a new entry since it cannot find an existing one. It both returns the new entry and call the Hashmap.put method to add it.

key definition for 'fetch.message.max.bytes' in Kafka

I am not sure how to define the key for the message size of my KafkaSpouts.
My example:
Map<String, Object> props = new HashMap<>();
props.put("fetch.message.max.bytes", "2097152"); // 2MB
props.put(KafkaSpoutConfig.Consumer.GROUP_ID, group);
I searched for the constant key definition of "fetch.message.max.bytes" without succeed.
I expect this key in KafkaSpoutConfig.Consumer or at least KafkaSpoutConfig.
Anyone know the correct location?
Storm's KafkaSpout does not offer all available keys as perdefined members. However, if you know the name of the key, you can safely use a String (as shown in your example) of use a Kafka class that defines the key.

How to maintain JSON's order in Groovy's JsonSlurper?

I am reading a simple JSON....
{"A":0,"B":0,"C":2,"D":0,"F":5}
into a map using JsonSlurper in Groovy...
Map gradeDistributon = jsonSlurper.parseText(jsonString)
But when iterating over this map with a closure..
gradeDistributon.each{ entry ->
println "From map got key ${entry.key}"
I am seeing the keys are not in the order they were in the original JSON, for example 'C' comes first. I think this is because Map does not maintain insertion order in Java. Is there a way I can keep the order of the original JSON?
If it means reading the JSON in a different way (instead of into a Map with JsonSlurper) then I am fine with that if you can show me how.
You can set JVM system property jdk.map.althashing.threshold to make JsonSlurper to use a LinkedHashMap instead of TreeMap as the internal Map implementation, e.g. -Djdk.map.althashing.threshold=512.
The reason is in source code of groovy.json.internal.LazyMap used by JsonSlurper.
private static final String JDK_MAP_ALTHASHING_SYSPROP = System.getProperty("jdk.map.althashing.threshold");
private void buildIfNeeded() {
if (map == null) {
/** added to avoid hash collision attack. */
if (Sys.is1_7OrLater() && JDK_MAP_ALTHASHING_SYSPROP != null) {
map = new LinkedHashMap<String, Object>(size, 0.01f);
} else {
map = new TreeMap<String, Object>();
}
}
}
Please note this solution should be used as a hack as it depends on Groovy's internal implementation details. So this behavior may change in future version of Groovy.
See my blog post for details.
So it was just a matter of sorting the keys after JsonSlurper built the Map, for that I just read into a TreeMap which sorts the keys by default..
TreeMap gradeDistributon = jsonSlurper.parseText(jsonString)
I can't reproduce your behaviour with groovy 2.4.5 but you can try using LinkedHashMap (allow to iterate over map keys maintaining the order in which the entries were inserted):
import groovy.json.*
def jsonText = '''
{"A":0,"B":0,"C":2,"D":0,"F":5,"G":7,"H":9}
'''
LinkedHashMap json = new JsonSlurper().parseText(jsonText)
json.each{ entry ->
println "${entry.key}"
}
NOTE: as stated by #XenoN the JsonSlurper() sort the json keys during the parsing process so independently of the input order (ie. {"H":0,"B":0,"A":2,"D":0,"G":5,"F":7,"C":9}) the output of JsonSlurper will be always: {"A":2,"B":0,"C":9,"D":0,"F":7,"G":5,"H":0}.
Using the LinkedHashMap instead of a HashMap we preserve the order given by JsonSlurper.
I run the same code on Groovy 2.4.x and on 3.0.x.
On 2.4 the order is preserved,but on 3.0 is sorted asc by default.
use the JsonSluperClassic().parse() instead it will preserve the order

Java: how to use Google's HashBiMap?

Keys are a file and a word. The file gives all words inside the file. The word gives all files having the word. I am unsure of the domain and co-domain parts. I want K to be of the type <String> and V to be of type <HashSet<FileObject>>.
public HashBiMap<K<String>,V<HashSet<FileObject>>> wordToFiles
= new HashBiMap<K<String>,V<HashSet<FileObject>>>();
public HashBiMap<K<String>,V<HashSet<FileObject>>> fileToWords
= new HashBiMap<K<String>,V<HashSet<FileObject>>>();
Google's HashBiMap.
change it to
public HashBiMap<String,HashSet<FileObject>> wordToFiles = HashBiMap.create ();
But still it looks very strange. I think you should use another collection. From BiMap documentation (HashBiMap impelements BiMap):
A bimap (or "bidirectional map") is a
map that preserves the uniqueness of
its values as well as that of its
keys. This constraint enables bimaps
to support an "inverse view", which is
another bimap containing the same
entries as this bimap but with
reversed keys and values.
I don't know the problem you want to solve but after looking at your code I can suggest to consider using Multimaps. From its docs:
A collection similar to a Map, but
which may associate multiple values
with a single key. If you call put(K,
V) twice, with the same key but
different values, the multimap
contains mappings from the key to both
values.
For example, you can do something like this:
Multimap<String, FileObject> wordToFiles = HashMultimap.create();
wordToFiles.put("first", somefile);
wordToFiles.put("first", anotherfile);
for (FileObject file : wordToFiles.get("first"){
doSomethingWithFile (file);
}
Add this dependency to your 'build.gradle'
compile 'com.google.guava:guava:19.0'
import BiMap and HashBiMap
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
Create a bimap
BiMap<String, String> myBiMap = HashBiMap.create();
Put some values
myBiMap.put("key", "value");
Get mapping value by key,
myBiMap.get("key");
Get mapping by value,
myBiMap.inverse().get("value");

Java JSON/object to array

I have a question about type casting. I have the following JSON String:
{"server":"clients","method":"whoIs","arguments":["hello"]}
I am parsing it to the following Map<String, Object>.
{arguments=[hello], method=whoIs, server=clients}
It is now possible to do the following:
request.get("arguments");
This works fine. But I need to get the array that is stored in the arguments. How can I accomplish this? I tried (for example) the following:
System.out.println(request.get("arguments")[0]);
But of course this didn't work..
How would this be possible?
Most likely, value is a java.util.List. So you would access it like:
System.out.println(((List<?>) request.get("arguments")).get(0));
But for more convenient access, perhaps have a look at Jackson, and specifically its Tree Model:
JsonNode root = new ObjectMapper().readTree(source);
System.out.println(root.get("arguments").get(0));
Jackson can of course bind to a regular Map too, which would be done like:
Map<?,?> map = new ObjectMapper().readValue(source, Map.class);
But accessing Maps is a bit less convenient due to casts, and inability to gracefully handle nulls.
Maybe
System.out.println( ((Object[]) request.get("arguments")) [0]);
? You could also try casting this to a String[].
Anyway, there are more civilized ways of parsing JSON, such as http://code.google.com/p/google-gson/.
StaxMan is correct that the type of the JSON array in Java is List (with ArrayList as implementation), assuming that the JSON is deserialized similar to
Map<String, Object> map = JSONParser.defaultJSONParser().parse(Map.class, jsonInput);
It is easy to determine such things by simply inspecting the types.
Map<String, Object> map = JSONParser.defaultJSONParser().parse(Map.class, jsonInput);
System.out.println(map);
for (String key : map.keySet())
{
Object value = map.get(key);
System.out.printf("%s=%s (type:%s)\n", key, value, value.getClass());
}
Output:
{arguments=[hello], method=whoIs, server=clients}
arguments=[hello] (type:class java.util.ArrayList)
method=whoIs (type:class java.lang.String)
server=clients (type:class java.lang.String)
Also, the svenson documentation on basic JSON parsing describes that "[b]y default, arrays will be parsed into java.util.List instances".

Categories

Resources