This question already has answers here:
Java Performance: Map vs List
(4 answers)
Closed 1 year ago.
I have a doubt about the performance difference between these two things, get an object directly from a hashmap with the key vs get it from an Optional from an ArrayList. I will use these to save big amounts of data.
Note: the example below is only to show what I mean; I don't use static except in utils or specific things, I say this to prevent comments about static.
public class Main {
private static final List<User> users = Arrays.asList(new User(UUID.randomUUID()), new User(UUID.randomUUID()), new User(UUID.randomUUID()));
public static Optional<User> getUserByUUID(final UUID uuid){
return users.stream().filter(user -> user.getUuid().equals(uuid)).findFirst();
}
#RequiredArgsConstructor
#Getter#Setter
private static class User{
private final UUID uuid;
private int points;
private int gems;
}
}
vs
public class Main {
private static final Map<UUID, User> users = new HashMap<UUID, User>(){{
put(UUID.randomUUID(), new User());
put(UUID.randomUUID(), new User());
}};
public static User getUserByUUID(final UUID uuid){
if(users.containsKey(uuid))
return users.get(uuid);
return null;
}
#RequiredArgsConstructor
#Getter#Setter
private static class User{
private int points;
private int gems;
}
}
My point is, if one is better than the another one in terms of performance, is it insignificant?
Map#get will always be more performant than creating a Stream from a List and looking for a specific entry.
Map#get will give you a time complexity of O(1) basically
List#stream instead will give you a time complexity of O(n) plus extra space complexity: creation of a Stream and creation of an Optional
That said, if you have big amounts of data loaded in memory, this might lead to performance problems / OutOfMemoryErrors
It would be interesting to dig further in the problem and see if there isn't another way to handle this specific problem without loading big amounts of data directly in memory of the JVM
Related
This is hard for me to explain as I'm not native to the English language, so I will try setting up an example.
I am trying to save some data about a player in a class called PlayerData. It has three variables with getters and setters.
public class PlayerData {
private String player;
private String username;
private UUID uuid;
public String getPlayer() {
return player;
}
public void setPlayer(String player) {
this.player = player;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public UUID getUuid() {
return uuid;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
}
For each player in the game, there will be generated a PlayerData object. Normally I would store this in a Map, so I can get the data about a player from eg. the UUID. However, I could use a way to be able to use any variable in the PlayerData object as "key", so I don't require the UUID to get the PlayerData. A way to do this (and my usual approach) would be to have multiple maps, something like this.
Map<String, PlayerData> playerMap;
Map<String, PlayerData> usernameMap;
Map<UUID, PlayerData> uuidMap;
The problem is, when it scales up with multiple variables, this gets annoying, and perhaps even eats up the RAM? I'm not entirely sure, as it stores references.
It similar to SQL, where you can also get specific colums based on the content of the rows. That's what I'm looking for, but without the SQL database.
I made a table explanation below in an attempt to explain it further:
Player
Username
UUID
Peter
Peter1234
657f6c48-655f-11eb-ae93-0242ac130002
Stephen
DogLover69
657f6efa-655f-11eb-ae93-0242ac130002
Joshua
XxFlowerPotxX
657f6fea-655f-11eb-ae93-0242ac130002
Short edition
I'm looking for a way to store multiple objects of the same type, where I (unlike Maps, that only take a single object as Key) can use multiple assigned variables as keys.
I hope the explaination was clear, I have absoloutly no idea how to explain it, which is probably also why I can't solve it by googling.
Thank you for your time.
As far as I understand, it's need to store various data for a specific user (and not just to update old values)
One way is through a custom map. Since only need a key (unique), could assume that username is doing that (eg:login). MyData can be customized further with what ever wanted to store.
Each key/username will contain a distinct list where new data is added.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class TestPData {
public static void main(String[] args)
{
TestPData t = new TestPData();
MyMap m = t.new MyMap();
//key can be just user name, if unique is assured
m.putMyData("player_1", t.new MyData("p1_data1"));
m.putMyData("player_1", t.new MyData("p1_data2"));
m.putMyData("player_2", t.new MyData("p2_data3"));
m.putMyData("player_3", t.new MyData("p2_data4"));
m.putMyData("player_3", t.new MyData("p2_data5"));
m.putMyData("player_3", t.new MyData("p2_data6"));
m.forEach((k,v)->{for(MyData d: v) {System.out.println(k+":"+d);}});
}
class MyData
{
String s;
public MyData(String s)
{
this.s = s;
}
public String toString()
{
return s;
}
}
class MyMap extends HashMap<String, List<MyData>>
{
private static final long serialVersionUID = 1L;
public void putMyData(String k, MyData d)
{
if(!this.containsKey(k))
{
this.put(k, new ArrayList<MyData>());
this.get(k).add(d);
}
else
{
this.get(k).add(d);
}
}
}
}
Output
player_1:p1_data1
player_1:p1_data2
player_3:p2_data4
player_3:p2_data5
player_3:p2_data6
player_2:p2_data3
If you are dealing with few records (some thousands), you can use a list and iterative search as suggested by #gilbert-le-blanc, but if you need to manage huge amounts of records/attributes, it is better to use a database anyway. You can also use an in-memory database like Derby or H2.
https://www.h2database.com/
https://db.apache.org/derby/
With some effort you can create a custom collection with multi-indexed properties also, but it is not worth the pain.
I would use a map of maps, with the first mapping by the name of the property and the second map by its value.
In code:
Map<String, Map<String, PlayerData>> index = new HashMap<>();
To add a mapping:
PlayerData peterData = new PlayerData(
"Peter",
"Peter1234",
"657f6c48-655f-11eb-ae93-0242ac130002");
index.computeIfAbsent("player", k -> new HashMap<>())
.put("Peter", peterData);
index.computeIfAbsent("username", k -> new HashMap<>())
.put("Peter1234", peterData);
index.computeIfAbsent("uuid", k -> new HashMap<>())
.put("657f6c48-655f-11eb-ae93-0242ac130002", peterData);
This navigates to the different inner maps (one per indexed property) by means of the Map.computeIfAbsent method, which creates an empty inner map and puts it into the outer map if it doesn't exist, or returns it if already present. Then, we add the mapping to the inner map by using Map.put as usual.
To remove a mapping:
index.computeIfAbsent("username", k -> new HashMap<>()).remove("Peter1234");
This is completely dynamic, as you don't have to change the data structure when you need to map by more properties. Instead, all you have to do is add mappings as needed.
The downside of this approach is that you'd need to use strings for the keys of the inner maps, but I think this is a reasonable trade-off.
I have this scenario. I have one paginated API which gives me the data for last 12 months. The response of the API is like:
public class PagedTransfersDto {
private List<Transfer> content;
private Page page;
#Getter
public static class Transfer {
private String id;
private Long transferId;
private Long transferRequestId;
private String status;
private BigDecimal accountReceivable;
private BigDecimal accountPayable;
private BigDecimal netReceivable;
private BigDecimal netPayable;
private String currency;
private Long transferDate;
}
#Getter
public static class Page {
private Integer size;
private Integer number;
private Integer totalElements;
private Integer totalPages;
}
}
Now I have to collect all the data and then calculate the sum of all the netReceivable and return as a Mono<CompanyIncome>. This pojo is like
public class CompanyIncome {
private BigDecimal inferredIncome = new BigDecimal(0);
}
To do this I have written something like:
CompanyIncome initialIncome = new CompanyIncome();
return myService.getTransfers(0, 50, fromDate, toDate)
.expand(pagedTransfersDto -> {
if (pagedTransfersDto.getPage().getNumber().equals(pagedTransfersDto.getPage().getTotalPages())) {
return Mono.empty();
}
return myService.getTransfers(pagedTransfersDto.getPage().getNumber() + 1, 50, fromDate, toDate);
})
.flatMap(pagedTransfersDto -> Flux.fromIterable(pagedTransfersDto.getContent()))
.reduce(initialIncome, ((companyIncome, transfer) -> {
companyIncome.setInferredIncome(companyIncome.getInferredIncome().add(transfer.getNetReceivable()));
return companyIncome;
}));
Now the catch is that it is possible that this data is only for 3 months in which case I have to extrapolate this to 12 months by multiplying by 4.
What I am thinking is to get the first item of transfers list and the last one and the see if the data is not for a whole year but cant think of a place where to perform this operation.
Since after reduce the transfers data is gone. Before that I cannot seem to find a way how to get this info and still reduce from transfers flux
I am a little new to reactive way and cant seem to find a way to do this. Any help will be greatly appreciated. Thanks
For that purpose, the best solution is to store the necessary "metadata" in the reduced object. You already have a CompanyIncome object, so maybe that is a good place? Otherwise I'd introduce either a Tuple2 or some intermediate business object (eg. CompanyIncomeAggregator) into which to store both the aggregated income and the information that you need to decide at the end if further processing is necessary.
Then in a map step, you'd read that information, act on it and either return the computed income as is or modified according to your criterion.
Important note: Using variables external to the reactive chain is a code smell, as it introduces leaky shared state: if two subscriptions are made to the same Mono, they'll work on the same CompanyIncome object. You can remediate here by using reduceWith, which takes a Supplier for the initial value: reduceWith(CompanyIncome::new, ...).
I always strive to make my code fully immutable and not utilize setters at all.
When I need to update an object, dto or entity I use #Builder(toBuilder = true) instead of setters.
public Car updateCar(final String id) {
final Car existing = carRepository.getById(id);
final Car.CarBuilder builder = existing.toBuilder().make("Mercedes-Benz");
if (anyCondition) {
builder.status("READY");
}
final Car updatedCar = builder.build();
return carRepository.save(updatedCar);
}
I would like to ask you is it really bad from a performance perspective that instead of setting a value into an already instantiated object, I create a new one?
Maybe in the above piece of code, it's not significant but I also may require to change one field in all objects in a collection thus the space complexity would be linear.
What approach do you prefer: setters or toBuilder?
P.S.: the code snippet above is just for better understanding of how I utilize toBuilder
I would like to ask you is it really bad from a performance
perspective that instead of setting a value into an already
instantiated object, I create a new one?
You're asking us, and yourself, the wrong question.
You shouldn't really be concerned about performance for such simple, data-carrier, classes.
Benchmark (see JMH) and see.
But from a purely theoretical standpoint, the only overhead is the creation of another object (which size in memory depends on its members' layout), the additional step of transferring the primitive values/references from the builder instance to the resulting instance (maybe also more work for the GC? I wouldn't even consider that).
Thus some more CPU cycles.
Look also at Immutables, look at how many people and companies are using it, and ask yourself if for your simple usecases you should really consider asking this question.
Take this class
public class Car {
private String maker;
private int year;
private int kms;
// Getter - setters
}
The size is approximately 16 + 4 + 4 + 4 = 28 bytes.
Now, using a builder such as
public class CarBuilder {
private String maker;
private int year;
private int kms;
public CarBuilder setMaker(final String maker) {
this.maker = maker;
return this;
}
public CarBuilder setYear(final int year) {
this.year = year;
return this;
}
public CarBuilder setKms(final int kms) {
this.kms = kms;
return this;
}
public Car createCar() {
return new Car(maker, year, kms);
}
}
The size is still approximately 16 + 4 + 4 + 4 = 28 bytes.
That means you'll have at least doubled the bytes used in the heap for just the class using a builder.
However, what you need to think about is Java being reference-based. That means all the created objects' pointers will simply be copied to the produced Car instance. The objects will still be unique in memory.
After your edit, maybe what you're trying to do is Object Pooling?
In that case, consider Apache Commons Pool.
I'm having trouble with creating a DataFrame from an RDD.
To start off, I'm using Spark to create the data I'm using (via simulations on the workers) and in return I get Report objects.
These Report object consist of two HashMaps where the keys are near identical between the maps and custom made and the values are Integer / Double. Worth noting is that I currently need these keys and maps to efficiently add and update the values during the simulations, so changing this to a "flat" object may lose a lot of efficiency.
public class Key implements Serializable, Comparable<Key> {
private final States states;
private final String event;
private final double age;
...
}
And the States are
public class States implements Serializable, Comparable<States> {
private String stateOne;
private String stateTwo;
...
}
The states used to be Enums, but as it turns out, DataFrame doesn't like that. (The Strings are still set from Enums to ensure the values are correct.)
The problem is that I want to convert these maps to DataFrames so that I can use SQL etc to manipulate/filter the data.
I am able to create DataFrames by creating a Bean like so
public class Event implements Serializable {
private String stateOne;
private String stateTwo;
private String event;
private Double age;
private Integer value;
...
}
with getters and setters, but is there a way that I can just use Tuple2 (or something similar) to create my DataFrame? Which could even give me a nice structure for the db?
I have tried using Tuple2 like this
JavaRDD<Report> reports = dataSet.map(new SimulationFunction(REPLICATIONS_PER_WORKER)).cache();
JavaRDD<Tuple2<Key, Integer>> events = reports.flatMap(new FlatMapFunction<Report, Tuple2<Key, Integer>>() {
#Override
public Iterable<Tuple2<Key, Integer>> call(Report t) throws Exception {
List<Tuple2<Key, Integer>> list = new ArrayList<>(t.getEvents().size());
for(Entry<Key, Integer> entry : t.getEvents().entrySet()) {
list.add(new Tuple2<>(entry.getKey(), entry.getValue()));
}
return list;
}
});
DataFrame schemaEvents = sqlContext.createDataFrame(events, ????);
But I don't know what to put where the question marks are.
Hopefully I've made myself clear enough and that you'll be able to shed some light on this. Thank you in advance!
As zero323 says, it's not possible to do what I'm trying to do. I'll just stick with the beans from now on.
I'm a java beginner and have a question concerning how to best structure a cooking program.
I have a class called Ingredient, this class currently looks like this:
public class Ingredient {
private String identifier;
private double ingredientFactor;
private String titleInterface;
public Ingredient(String identifier, double ingredientFactor,String titleInterface) {
this.identifier = identifier;
this.ingredientFactor = ingredientFactor;
this.titleInterface = titleInterface;
}
I want to initialize several objects (about 40) with certain values as instance variables and save them in a Map, for example
Map<String, Ingredient> allIngredients = new HashMap<String, Ingredient>();
allIngredients.put("Almonds (ground)", new Ingredient("Almonds (ground)", 0.7185, "Almonds (ground)");
Later on I want to retrieve all these objects in the form of a Map/HashMap in a different class.
I'm not sure how to proceed best, initialize all these objects in the Ingredient class itself or provide a method that initializes it or would it be better to create an super class (AllIngredients or something like that?) that has a Map with Ingredients as instance variables?
Happy for any suggestions, thanks in advance :)
Please do not initialize all these objects in the Ingredient class itself. That would be a bad practice for oops.
Just think your class is a template from which you create copies(objects) with different values for attributes. In real world if your class represent model for a toy plane which you would use to create multiple toy planes but each bearing different name and color then think how such a system would be designed. You will have a model(class). Then a system(another class) for getting required color and name from different selection of colors and names present(like in database,files,property file ) etc.
Regarding your situation .
If predetermined values store the values in a text file,properties file,database,constants in class etc depending on the sensitivity of the data.
Create Ingredient class with constructors
Create a class which will have methods to initialize Ingredient class using predetermined values,update the values if required,save the values to text file -database etc and in your case return as map .
Also check the links below
http://www.tutorialspoint.com/design_pattern/data_access_object_pattern.htm
http://www.oracle.com/technetwork/java/dataaccessobject-138824.html
Sounds to me like you are looking for a static Map.
public class Ingredient {
private String identifier;
private double ingredientFactor;
private String titleInterface;
public Ingredient(String identifier, double ingredientFactor, String titleInterface) {
this.identifier = identifier;
this.ingredientFactor = ingredientFactor;
this.titleInterface = titleInterface;
}
static Map<String, Ingredient> allIngredients = new HashMap<String, Ingredient>();
static {
// Build my main set.
allIngredients.put("Almonds (ground)", new Ingredient("Almonds (ground)", 0.7185, "Almonds (ground)"));
}
}