Make the best choice for application performance by learning Java Collections Framework
TERALA CHITTIBABU
Software Engineer at Zeta || Ex - Skuad || Ex - Teksands.AI || Freelancer - Java Developer, Tech Tutor || HackerRank Java 5?|| website: chittibabutechlearn.liveblog365
Bad programmers worry about the code. Good programmers worry about data structures and their relationships. — Linus Torvalds
The Java collections framework is a set of classes and interfaces for implementing a commonly used collection data structure. Examples include lists, queues, etc.
Because these collections are well tested, we do not have to implement every algorithm and data structure from scratch.
Almost all collections except the map can be derived from the java.util.Collection interface. The toArray() method converts any Collection into a one-dimensional array. We can use the for-each loop because the Collection interface extends the java.lang.Iterable interface.
The data structure allows us to store and manipulate data efficiently. With proper data structure selection, we can make algorithms faster. Without the proper data structure (PriorityQueue) , we cannot solve Dijkstra’s shortest path problem in O((E+V)LogV) rather it will take O(V2) when List is used.
Therefore, data structures have a significant impact on the final performance of the software. Let’s examine them in more detail.
List
ArrayList
package com.democollections;
import java.util.ArrayList;
import java.util.List;
public class MyArrayList {
public static void main(String[] args) {
List<String> animals = new ArrayList<>();
animals.add("Elephant");
animals.add("Lion");
animals.add("Tiger");
// Random access - O(1)
System.out.println(animals.get(2));
// Insert item into a given index - O(N)
animals.add(0,"Monkey");
// Delete item into a given index - O(N)
animals.remove(0);
//NOTE: Insertion/Deletion at the end is faster compared to other index
// Because of the Iterable interface
for(String animal: animals) {
System.out.println(animal);
}
}
}
LinkedList
package com.democollections;
import java.util.LinkedList;
public class MyLinkedList {
public static void main(String[] args) {
LinkedList<String> animals = new LinkedList<>();
animals.add("Horse");
animals.add("deer");
animals.add("snake");
// Access item - O(N)
System.out.println(animals.get(1));
// Insert/Delete item at start/end - O(1)
animals.addFirst("Lion");
animals.addLast("Elephant");
animals.removeFirst();
animals.removeLast();
// Insert/Delete item at a given index - O(N)
animals.remove(2);
animals.add(2, "Rhino");
// Because of the Iterable interface
for (String animal : animals) {
System.out.println(animal);
}
}
}
ArrayList vs LinkedList Performance
Let’s see the performance of ArrayList and LinkedList when insertion happens at the start of the list.
package com.democollections;
import java.util.ArrayList;
import java.util.LinkedList;
public class ArrayListLinkedListPerformance {
public static void main(String[] args) {
ArrayList<Integer> array = new ArrayList<>();
long before = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
array.add(0, i);
}
long after = System.currentTimeMillis();
System.out.println("Time taken by ArrayList : " + (after - before));
LinkedList<Integer> llist = new LinkedList<>();
before = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
llist.addFirst(i);
}
after = System.currentTimeMillis();
System.out.println("Time taken by LinkedList : " + (after - before));
}
}
// Output on my system
Time taken by ArrayList : 519
Time taken by LinkedList : 4
LinkedList performance is way better than ArrayList as no data shift happened.
Vector
Stack
package com.democollections;
import java.util.Stack;
public class MyStack {
public static void main(String[] args) {
Stack<String> animals = new Stack<>();
// LIFO
animals.push("Elephant");
animals.push("Lion");
animals.push("Tiger");
System.out.println("Size: " + animals.size());
System.out.println("Peek Element: " + animals.peek());
System.out.println("Size: " + animals.size());
System.out.println("Pop Element: " + animals.pop());
System.out.println("Size: " + animals.size());
while(!animals.isEmpty()) {
System.out.println(animals.pop());
}
}
}
// Output
Size: 3
Peek Element: Tiger
Size: 3
Pop Element: Tiger
Size: 2
Lion
Elephant
Stack extends the Vector class which means that stacks are inherently synchronized. However, if synchronization is not needed, in that case, prefer using ArrayDeque.
Queues
package com.democollections;
import java.util.LinkedList;
import java.util.Queue;
public class MyQueue {
public static void main(String[] args) {
Queue<String> animals = new LinkedList<>();
// FIFO
animals.add("Elephant");
animals.add("Lion");
animals.add("Tiger");
System.out.println("Size: " + animals.size());
System.out.println("Check Element: " + animals.element());
System.out.println("Size: " + animals.size());
System.out.println("Remove Element: " + animals.remove());
System.out.println("Size: " + animals.size());
for (String animal : animals) {
System.out.println(animal);
}
}
}
//Output
Size: 3
Check Element: Elephant
Size: 3
Remove Element: Elephant
Size: 2
Lion
Tiger
PriorityQueue
package com.democollections;
import java.util.PriorityQueue;
import java.util.Queue;
public class MyPriorityQueue {
public static void main(String[] args) {
Queue<String> animals = new PriorityQueue<>();
animals.add("Panda");
animals.add("Deer");
animals.add("Monkey");
for (String animal : animals) {
System.out.println(animal);
}
}
}
// Output
Deer
Panda
Monkey
Deque
ArrayDeque
package com.democollections;
import java.util.ArrayDeque;
import java.util.Deque;
public class MyArrayDequeQueue {
public static void main(String[] args) {
// Double ended queue - Deque
// FIFO - Queue
Deque<String> animalsQueue = new ArrayDeque<>();
animalsQueue.addLast("Panda");
animalsQueue.addLast("Deer");
animalsQueue.addLast("Monkey");
while (!animalsQueue.isEmpty()) {
System.out.println(animalsQueue.removeFirst());
}
}
}
// Output
Panda
Deer
Monkey
package com.democollections;
import java.util.ArrayDeque;
import java.util.Deque;
public class MyArrayDequeStack {
public static void main(String[] args) {
// Double ended queue - Deque
// LIFO - Stack
Deque<String> animalsStack = new ArrayDeque<>();
animalsStack.addFirst("Panda");
animalsStack.addFirst("Deer");
animalsStack.addFirst("Monkey");
while (!animalsStack.isEmpty()) {
System.out.println(animalsStack.removeFirst());
}
}
}
// Output
Monkey
Deer
Panda
ArrayDeque vs Stack Performance
Because Stack is synchronized as it extends the Vector class this is why it is going to be slower than the ArrayDequeue. So it's advisable to use ArrayDeque if we want a LIFO structure.
package com.democollections;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Stack;
public class MyArrayDequeStackPerformance {
public static void main(String[] args) {
Deque<Integer> stack = new ArrayDeque<>();
long before = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
stack.push(i);
}
while (!stack.isEmpty()) {
stack.pop();
}
long after = System.currentTimeMillis();
System.out.println("Time taken with ArrayDeque: " + (after - before) + "ms");
Stack<Integer> stackObj = new Stack<>();
before = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
stackObj.push(i);
}
while (!stackObj.isEmpty()) {
stackObj.pop();
}
after = System.currentTimeMillis();
System.out.println("Time taken with Stack: " + (after - before) + "ms");
}
}
领英推荐
// Output on my system
Time taken with ArrayDeque: 9ms
Time taken with Stack: 14ms
Set
HashSet
package com.democollections;
import java.util.HashSet;
import java.util.Set;
public class MyHashSet {
public static void main(String[] args) {
// O(1), in case of collision - O(logN)
Set<String> animals = new HashSet<>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
animals.add("Tiger");
for (String animal : animals) {
System.out.println(animal);
}
}
}
// Output
Elephant
Lion
Tiger
LinkedHashSet
package com.democollections;
import java.util.LinkedHashSet;
import java.util.Set;
public class MyLinkedHashSet {
public static void main(String[] args) {
// LinkedHashSet maintains the insertion order
// Doubly linked list is used
Set<String> animals = new LinkedHashSet<>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
animals.add("Tiger");
for (String animal : animals) {
System.out.println(animal);
}
}
}
// Output
Elephant
Tiger
Lion
SortedSet
TreeSet
package com.democollections;
import java.util.SortedSet;
import java.util.TreeSet;
public class MyTreeSet {
public static void main(String[] args) {
// O(logN) - Underlying data structure is Red-Black tree
SortedSet<String> animals = new TreeSet<>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
animals.add("Monkey");
for (String animal : animals) {
System.out.println(animal);
}
}
}
// Output
Elephant
Lion
Monkey
Tiger
HashSet vs LinkedHashSet vs TreeSet
Map
Hashtable
HashMap
package com.democollections;
import java.util.HashMap;
import java.util.Map;
public class MyHashMap {
public static void main(String[] args) {
Map<Integer, String> animalsMap = new HashMap<>();
// Insert takes O(1) if there are no collision
animalsMap.put(1, "Elephant");
animalsMap.put(10, "Tiger");
animalsMap.put(25, "Lion");
// Removes takes O(1)
animalsMap.remove(25);
// Retrieve value based on key takes O(1)
System.out.println(animalsMap.get(10));
for (Map.Entry<Integer, String> entry : animalsMap.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
}
// Output
Tiger
1:Elephant
10:Tiger
LinkedHashMap
package com.democollections;
import java.util.LinkedHashMap;
import java.util.Map;
public class MyLinkedHashMap {
public static void main(String[] args) {
// There is doubly linked list connecting the inserted items
Map<Integer, String> animalsMap = new LinkedHashMap<>();
// Insertion order is preserved
animalsMap.put(1, "Elephant");
animalsMap.put(20, "Tiger");
animalsMap.put(3, "Lion");
animalsMap.put(40, "Deer");
animalsMap.put(5, "Monkey");
animalsMap.put(60, "Panda");
for (Map.Entry<Integer, String> entry : animalsMap.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
}
// Output
1:Elephant
20:Tiger
3:Lion
40:Deer
5:Monkey
60:Panda
TreeMap
Before we talk about TreeMap let's first understand the binary search trees.
What if the array data structure is sorted?
We can search for an arbitrary item in O(logN) time complexity, which is the concept behind the binary search.
Binary Search Trees
Red-Black Trees
TreeMap
package com.democollections;
import java.util.Map;
import java.util.TreeMap;
public class MyTreeMap {
public static void main(String[] args) {
Map<Integer, String> animalsMap = new TreeMap<>();
// Items will inserted in sorted order based on key
animalsMap.put(1, "Elephant");
animalsMap.put(20, "Tiger");
animalsMap.put(3, "Lion");
animalsMap.put(40, "Deer");
animalsMap.put(5, "Monkey");
animalsMap.put(60, "Panda");
for (Map.Entry<Integer, String> entry : animalsMap.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
}
// Output
1:Elephant
3:Lion
5:Monkey
20:Tiger
40:Deer
60:Panda
HashMap vs TreeMap performance
package com.democollections;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class MyHashMapTreeMapPerformance {
public static void main(String[] args) {
// O(logN)
Map<Integer, Integer> treemap = new TreeMap<>();
long before = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
treemap.put(i, i);
}
for (int i = 0; i < 100000; i++) {
treemap.get(i);
}
long after = System.currentTimeMillis();
System.out.println("Time taken with TreeMap: " + (after - before) + "ms");
// O(1)
Map<Integer, Integer> hashmap = new HashMap<>();
before = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
hashmap.put(i, i);
}
for (int i = 0; i < 100000; i++) {
hashmap.get(i);
}
after = System.currentTimeMillis();
System.out.println("Time taken with HashMap: " + (after - before) + "ms");
}
}
// Output on my system
Time taken with TreeMap: 53ms
Time taken with HashMap: 22ms
HashMap vs LinkedHashMap vs TreeMap