Can somebody explain how SharedPreferences stores a string set - java

I was trying to save a list of names in shared preferences and wanted to make use of the SharedPreferences putStringSet so that I won't need multiple key-value pairs.
I know a regular HashSet doesn't guarantee iteration order so I used a LinkedHashSet to maintain the iteration order as the order of insertion and saved that to shared preferences.
When retrieving the same String Set I also used a LinkedHashSet but the order was not the same as when originally inserted.
I solved the problem by just storing the names in a comma separated string then parsing that, so that's not my question.
I would like to know what SharedPreferences does to a set of strings so that it does not maintain the correct ordering (at least not the one I want)?

To answer your question in short it un-links your linked hash set by creating a simple HashSet from your values... from that point on - no links of order.
From source code:
public Editor putStringSet(String key, Set<String> values) {
synchronized (this) {
mModified.put(key,
(values == null) ? null : new HashSet<String>(values));
return this;
}
}
Thats why, like you did, if you want to preserve order you need to store as string with separators (you used coma) and then split the separator to get an array.

There is one trick I use often is JSON.
It is very easy to convert complex objects to JSON and that save them as simple string into SharedPreferences.
You just need few lines of code to do this.
Have a look at popular JSON libraries like GSON and Jackson
Here is an util class you can use to convert object into JSON and vice versa.
public class JsonUtils {
private JsonUtils() {
}
public static String convertObjectToJSONString(Object object) {
ObjectMapper objectMapper = new ObjectMapper();
String jsonObject = "";
try {
jsonObject = objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return jsonObject;
}
public static<T> T parseObjectFromString(String json,Class<T> objectClass) {
ObjectMapper objectMapper = new ObjectMapper();
T object = null;
try {
object = objectMapper.readValue(json, objectClass);
} catch (IOException e) {
e.printStackTrace();
}
return object;
}
}
If you still want to save String Set into shared preference this is on thing you should not miss is to remove old set and than insert new one.
Set<String> stringSet = sharedPrefs.getStringSet("set-key", new HashSet<String>());
stringSet.add("Hello World");
Editor edit = sharedPrefs.edit();
edit.remove("set-key");
edit.apply();
edit.putStringSet("set-key", stringSet);
Also you can just use plain old ObjectOutputStream to convert object into byte[] or String and than save it. But as a lot of people noticed this is out of the scope of this question.

Related

Building Object collection taking very long

What I am trying to do is build a collection of UserObjects from an ArrayList<String> that I've read from a BufferedReader
UserObject simply consists of these fields:
int UserId
ArrayList<Integer> AssociatesId
My current code is using a BufferedReader to read in file.edgelist and building an ArrayList<String> which has entries of this format: "1 1200"
I am splitting that string into a String[] by its whitespace and building a new UserObject with UserId = 1 and initializing a new ArrayList<Integer> that holds any integers in the second element that has the same UserId
My problem is that file.edgelist has around 20,000,000 entries and while the BufferedReader takes under 10 seconds to read the file, it takes forever to build the collection of UserObjects. In fact, I haven't even gotten to the end of the file because it takes so long. I can confirm that I am successfully building these entries as I've run the code in debug and dropped an occasional breakpoint to find that the UserId is increasing and the UserObject's AssociatesId collections contain data.
Is there a quicker and/or better way to build this collection?
This is currently my code:
private ArrayList<UserObject> tempUsers;
public Utilities(){
tempUsers = new ArrayList<UserObject>();
}
//reading file through BufferedReader and returns ArrayList of strings formatted like "1 1200"
public ArrayList<String> ReadFile(){
BufferedReader reader = null;
ArrayList<String> userStr = new ArrayList<String>();
try {
File file = new File("file.edgelist");
reader = new BufferedReader(new FileReader(file));
String line;
while ((line = reader.readLine()) != null) {
userStr.add(line);
}
return userStr;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
//Where the problem actually lies
public ArrayList<UserObject> BuildUsers(ArrayList<String> userStrings){
for (String s : userStrings){
String[] ids = s.split("\\s+");
UserObject exist = getUser(Integer.parseInt(ids[0]));
if (exist == null){ //builds new UserObject if it doesn't exist in tempUsers
UserObject newUser = new UserObject(Integer.parseInt(ids[0]));
newUser.associate(Integer.parseInt(ids[1]));
tempUsers.add(newUser);
} else{ //otherwise adds "associate" Id to UserObject's AssociatesId collection
exist.associate(Integer.parseInt(ids[1]));
}
}
return tempUsers;
}
//helper method that uses Stream to find and return existing UserObject
private UserObject getUser(int id){
if (tempUsers.isEmpty()) return null;
try{
return tempUsers.stream().filter(t -> t.equals(new UserObject(id))).findFirst().get();
} catch (NoSuchElementException ex){
return null;
}
}
Everytime you call getUser, you iterate through the whole list to check whether given user exist. This is very inefficient, as the size of the list is growing (linear complexity in the worst case). You might want to replace it with HashMap (the lookup has a constant complexity).
private Map<Integer, UserObject> tempUsers = new HashMap();
//helper method that uses Stream to find and return existing UserObject
private UserObject getUser(int id){
return users.get(id);
}
Moreover, creating intermediate ArrayList<String> userStr with 20,000,000 million of entries is completely unnecessary and wastes lots of memory. You should create UserObject instances as you read lines from the reader.
Wow, you are just wasting memory and performance there.
First, don't load the entire file into memory as a List<String>. That is just a total waste of memory. Load the file directly into UserObject objects.
Next, don't store them as List<UserObject> and perform a sequential search for object by id. That's just .... sllloooooooooowwwww....
You should store them in a Map<Integer, UserObject> for fast access by id.
Actually, you don't even need UserObject. From what you've said, you just need a Map<Integer, List<Integer>>, which is also called a MultiMap. It's simple enough to do yourself, or you can find third-party libraries with MultiMap implementations.
Also, don't use split() is you know each line will contain exactly 1 space. Use indexOf() and substring()
You code fits the definition of a "pipeline", and thus could benefit enormously from a more judicious usage of the Streams API. For example, you don't need to read the whole file into memory, just use Files.lines to get a Stream<String> with every line in the file. Furthermore, you could do your parsing like:
//Where the problem actually lies
public ArrayList<UserObject> BuildUsers(Stream<String> userStrings){
java.util.Map<Integer,UserObject> users = userStrings // Stream<String>
.map(str -> s.split("\\s+")) // Stream<String[]>
.map(ids -> {
UserObject newUser = new UserObject(Integer.parseInt(ids[0]));
newUser.associate(Integer.parseInt(ids[1]));
return newUser;
}) // Stream<UserObject>, all new (maybe with duplicated ids)
.collect(Collectors.groupingBy(
uObj -> uObj.getId(), // whatever returns the "ids[0]" value
java.util.HashMap::new,
Collectors.reducing((uo1, uo2) -> {
// This lambda "merges" uo2 into uo1
uo2.getAssociates().forEach(uo1::associate);
return uo1;
})));
return new ArrayList<>(users.values());
}
Where I've made up the "getId" and "getAssociates" functions in UserObject to return the values that came originally from the elements of the ids array. This function first splits each line into a String array, then parses each of those 2-element arrays into new UserObject instances. The final collectors perform two functions:
Grouping by the Id property, so you would get a Map<Integer,List<UserObject>> with all the UserObjects with the same primary id.
The reducing (squashing) the several UserObject instances with the same primary id into a single instance (per Collectors.reducing) so that in the end you actually get a Map<Integer,UserObject>. The function passed to reducing takes two UserObject instances and returns one that contains the associate IDs of both of its "parents".
Finally, since apparently you want an ArrayList with the values, the code just takes them from the map and dumps them into the desired container type.

Mapping JSON with varying object name

I'm quite new to JSON, and I've looked around trying to work out what to do but not sure if I fully understand. I am making an external API call returning:
2015-12-21 01:22:09 INFO RiotURLSender:60 - Total json:
{"USERNAME":{"profileIconId":984,"revisionDate":1450655430000,"name":"USERNAME2","id":38584682,"summonerLevel":30}}
Where 'USERNAME' (And USERNAME2 - which can be very slightly different to USERNAME) will vary depending on what you pass the call's parameters. I was using Jackson Object Mapper to map the individual values within the USERNAME object - but didn't realise I had to map the object as well.
I've been using annotations in the DTOs like:
#JsonProperty("profileIconId")
private Long profileIconId;
and mapping using:
summonerRankedInfoDTO = mapper.readValue(jsonString, SummonerRankedInfoDTO.class);
How do I map using a value of USERNAME which is changing every single time?
Also this seems a bit odd, is this bad practice to have the actual varying key rather than just have the same key and different value?
Thanks
you can use following mentioned annotation #JsonAnyGetter And #JsonAnySetter.
Add this code into ur domain class. So any non-mapped attribute will get populated into "nonMappedAttributes" map while serializing and deserializing the Object.
#JsonIgnore
protected Map<String, Object> nonMappedAttributes;
#JsonAnyGetter
public Map<String, Object> getNonMappedAttributes() {
return nonMappedAttributes;
}
#JsonAnySetter
public void setNonMappedAttributes(String key, Object value) {
if (nonMappedAttributes == null) {
nonMappedAttributes = new HashMap<String, Object>();
}
if (key != null) {
if (value != null) {
nonMappedAttributes.put(key, value);
} else {
nonMappedAttributes.remove(key);
}
}
}
You should try to keep the keys the exact same if possible and change values, otherwise you'll have to change your JSON. Since JSON returns a value from the key, the value can change to anything it wants, but you'll be able to return it from the key. This doesn't work the other way around though.
Anyway to your question, you may have a little better luck using something like the GSON library, its pretty simple to use.
You can create the instance and pass it the JSON string:
Gson gson = new Gson();
JsonObject obj = gson.fromJson(JSON_DOCUMENT, JsonObject.class);
Then you can get certain elements from that now parsed JSON object.
For example, in your JSON string, username returns another JSON element, so you can do:
JsonObject username = obj.get("USERNAME").getAsJsonObject();
Then just repeat the same steps from there to get whatever value you need.
So to get the name which returns "USERNAME2":
username.get("name").getAsString();
Coming together with:
JsonObject obj = gson.fromJson(JSON_DOCUMENT, JsonObject.class);
JsonObject username = obj.get("USERNAME").getAsJsonObject();
username.get("name").getAsString();

Parsing dynamic JSON values to Java objects

In my application I have lot of overviews (tables) with sorting and filtering capabilities. And becuase the different column can hold different value type (strings, numbers, dates, sets, etc.) the filter for these columns also can bring different values. Let me show you few examples (converted to JSON already as is sent to server via REST request):
For simple string value it is like:
{"<column_name>":"<value>"}
For number and date column the filter looks like:
{"<column_name>":[{"operator":"eq","value":"<value>"}]}
{"<column_name>":[{"operator":"eq","value":"<value1>"},{"operator":"gt","value":"<value2>"}]}
For set the filter looks like
{"<column_name>":["<value1>","<value2>"(,...)]}
Now I need to parse that JSON within a helper class that will build the WHERE clause of SQL query. In PHP this is not a problem as I can call json_decode and then simply check whether some value is array, string or whatever else... But how to do this simply in Java?
So far I am using Spring's JsonJsonParser (I didn't find any visible difference between different parsers coming with Spring like Jackson, Gson and others).
I was thinking about creating an own data object class with three different constructors or having three data object classes for all of the three possibilities, but yet I have no clue how to deal with the value returned for column_name after the JSON is parsed by parser...
Simply looking on the examples it gives me three possibilities:
Map<String, String>
Map<String, Map<String, String>>
Map<String, String[]>
Any idea or clue?
Jackson's ObjectMapper treeToValue should be able to help you.
http://fasterxml.github.io/jackson-databind/javadoc/2.2.0/com/fasterxml/jackson/databind/ObjectMapper.html#treeToValue%28com.fasterxml.jackson.core.TreeNode,%20java.lang.Class%29
Your main problem is that the first version of you JSON is not the same construction than the two others. Picking the two others you could deserialize your JSON into a Map<String, Map<String, String> as you said but the first version fits a Map.
There are a couple solutions available to you :
You change the JSON format to always match the Map<String, Map<String, String> pattern
You first parse the JSON into a JsonNode, check the type of the value and deserialize the whole thing into the proper Map pattern.
(quick and dirty) You don't change the JSON, but you try with one of the Map patterns, catch JsonProcessingException, then retry with the other Map pattern
You'll have to check the type of the values in runtime. You can work with a Map<String, Object> or with JsonNode.
Map<String, Object>
JsonParser parser = JsonParserFactory.getJsonParser();
Map<String, Object> map = parser.parseMap(str);
Object filterValue = filter.get("<column_name>");
if (filterValue instanceof String) {
// str is like "{\"<column_name>\":\"<value>\"}"
} else if (filterValue instanceof Collection) {
for (Object arrayValue : (Collection<Object>) filterValue) {
if (arrayValue instanceof String) {
// str is like "{\"<column_name>\":[\"<value1>\",\"<value2>\"]}"
} else if (arrayValue instanceof Map) {
// str is like "{\"<column_name>\":[{\"operator\":\"eq\",\"value\":\"<value>\"}]}"
}
}
}
JsonNode
ObjectMapper mapper = new ObjectMapper();
JsonNode filter = mapper.readTree(str);
JsonNode filterValue = filter.get("<column_name>");
if (filterValue.isTextual()) {
// str is like "{\"<column_name>\":\"<value>\"}"
} else if (filterValue.isArray()) {
for (JsonNode arrayValue : filterValue.elements()) {
if (arrayValue.isTextual()) {
// str is like "{\"<column_name>\":[\"<value1>\",\"<value2>\"]}"
} else if (arrayValue.isObject()) {
// str is like "{\"<column_name>\":[{\"operator\":\"eq\",\"value\":\"<value>\"}]}"
}
}
}

Best way to save some data and then retrieve it

I have a project where I save some data coming from different channels of a Soap Service, for example:
String_Value Long_timestamp Double_value String_value String_value Int_value
I can have many lines (i.e. 200), with different values, like the one above.
I thought that I could use an ArrayList, however data can have a different structure than the one above, so an ArrayList maybe isn't a good solution in order to retrieve data from it.
For example above I have, after the first two values that are always fixed, 4 values, but in another channel I may have 3, or 5, values. What I want retrieve data, I must know how many values have a particular line, and I think that Arraylist doesn't help me.
What solution could I use?
When you have a need to uniquely identify varying length input, a HashMap usually works quite well. For example, you can have a class:
public class Record
{
private HashMap<String, String> values;
public Record()
{
// create your hashmap.
values = new HashMap<String, String>();
}
public String getData(String key)
{
return values.get(key);
}
public void addData(String key, String value)
{
values.put(key, value);
}
}
With this type of structure, you can save as many different values as you want. What I would do is loop through each value passed from Soap and simply add to the Record, then keep a list of Record objects.
Record rec = new Record();
rec.addData("timestamp", timestamp);
rec.addData("Value", value);
rec.addData("Plans for world domination", dominationPlans);
You could build your classes representing the entities and then build a parser ... If it isn't in a standard format (eg JSON, YAML, ecc...) you have no choice to develop your own parser .
Create a class with fields.
class ClassName{
int numberOfValues;
String dataString;
...
}
Now create an ArrayList of that class like ArrayList<ClassName> and for each record fill that class object with numberOfValues and dataString and add in Arraylist.

Parsing differently named elements as a single list using SIMPLE XML library for android

I was wondering if there was anyway to treat col_1,col_2...etc as a list rather than separate elements, using the SIMPLE XML Library for Android. I have been reading a bit about substitutions but I'm still confused.
Current Format :
<box-headers
dataTable="boxscore"
col_1="FINAL"
col_2="1"
col_3="2"
col_4="3"
col_5="4"
col_6="5"
col_7="6"
col_8="7"
col_9="8"
col_10="9"
col_11="R"
col_12="H"
col_13="E">
table
</box-headers>
I want to be able to parse out the col's as a list of some sort so I can handle any number of cols. Is this possible?
As ng said before: Use a Converter for this. Simple is brilliant in letting you customize every step of processing (while on the other hand it's possible to let you (de-)serialize even complex structures with some lines of code).
So here's an example:
A Class that will hold the values from the list:
#Root(name = "example")
#Convert(value = ListConverter.class) // Specify the Converter that's used for this class
public class Example
{
// This element will be set with the values from 'box-headers' element
#ElementList(name = "box-headers")
private List<String> values;
// This constructor is used to set the values while de-serializing
// You can also use setters instead
Example(List<String> values)
{
this.values = values;
}
//...
}
The Converter:
public class ExampleConverter implements Converter<Example>
{
#Override
public Example read(InputNode node) throws Exception
{
List<String> list = new ArrayList<>(); // List to insert the 'col_' values
NodeMap<InputNode> attributes = node.getAttributes(); // All attributes of the node
Iterator<String> itr = attributes.iterator();
while( itr.hasNext() ) // Iterate over all attributes
{
final String name = itr.next(); // The name of the attribute
if( name.startsWith("col_") ) // Check if it is a 'col' attribute
{
// Insert the value of the 'col_' attribute
list.add(attributes.get(name).getValue());
}
}
// Return the result - instead of a constructor you can use setter(s) too
return new Example(list);
}
#Override
public void write(OutputNode node, Example value) throws Exception
{
// TODO: Implement serializing here - only required if you want to serialize too
}
}
How to use:
// Serializer, don't forget `AnnotationStrategy` - without it wont work
Serializer ser = new Persister(new AnnotationStrategy());
// Deserialize the object - here the XML is readen from a file, other sources are possible
Example ex = ser.read(Example.class, new File("test.xml"));
This example uses only col_xy attributes, everything else is dropped. If you need those values too it's easy to implement them. You only have to retrieve them from the InputNode and set them into your output.

Categories