I'm adding millions of entries of a custom object to a List. This turns out to be very slow and the graphical user interface keeps freezing randomly as well (not responding on Windows) even though the adding operation is wrapped in a SwingWorker so it should not affect the EDT. Furthermore, the CPU utilization goes up to around 70% - 90%.
I'm initializing the list as follows:
List<SearchResult> updatedSearchResults = new ArrayList<>();
Then I'm repeatedly calling
SearchResult searchResult = new SearchResult(...);
updatedSearchResults.add(searchResult);
to add elements.
I'm aware that the ArrayList internally uses an array which is resized when it is full and all elements are copied over. However, using a LinkedList has shown to be slower than the ArrayList though and no other List implementation exists.
According to this answer, adding to an ArrayList has a performance of O(1). The question arises why my approach of collecting all results is still so awful performance-wise. SearchResult is a simple data object encapsulating fields and initializing it is cheap since only fields are set.
As comparison, adding to the list is noticeably slower than sending the same amount of data over a network socket. This doesn't make any sense since local operations should always be way faster than network related operations.
1 million elements finishes in about 0.2 seconds but 67 million won't even finish in a reasonable amount of time. It should finish like a few seconds at maximum.
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
public class ListPerformanceTester
{
    public enum ValueSize
    {
        EIGHT_BIT("8-Bit"),
        SIXTEEN_BIT("16-Bit"),
        THIRTY_TWO_BIT("32-Bit"),
        SIXTY_FOUR_BIT("64-Bit"),
        NINETY_SIX_BIT("96-Bit");
        private String name;
        ValueSize(String name)
        {
            this.name = name;
        }
        public int getBytesCount()
        {
            switch (this)
            {
                case EIGHT_BIT:
                    return 1;
                case SIXTEEN_BIT:
                    return 2;
                case THIRTY_TWO_BIT:
                    return 4;
                case SIXTY_FOUR_BIT:
                    return 8;
                case NINETY_SIX_BIT:
                    return 12;
            }
            throw new IllegalStateException("Bytes count undefined for " + this);
        }
        @Override
        public String toString()
        {
            return name;
        }
        public static ValueSize parse(String name)
        {
            ValueSize[] valueSizes = values();
            for (ValueSize valueSize : valueSizes)
            {
                String currentName = valueSize.toString();
                if (currentName.equals(name))
                {
                    return valueSize;
                }
            }
            throw new IllegalArgumentException("No value size associated");
        }
    }
    public static class SearchResult implements Cloneable, Comparable
    {
        private int address;
        private ValueSize valueSize;
        private BigInteger previousValue;
        private BigInteger currentValue;
        private BigInteger valueDifference;
        public SearchResult(int address, BigInteger previousValue,
                            BigInteger currentValue, ValueSize valueSize)
        {
            this.address = address;
            this.valueSize = valueSize;
            this.previousValue = previousValue;
            this.currentValue = currentValue;
            setValueDifference();
        }
        private void setValueDifference()
        {
            BigInteger subtractionResult = previousValue.subtract(currentValue);
            valueDifference = subtractionResult.abs();
        }
        private byte[] getBytes(BigInteger bigInteger) throws IOException
        {
            byte[] retrieved = bigInteger.toByteArray();
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            int bytesCount = getValueSize().getBytesCount();
            int paddingBytes = bytesCount - retrieved.length;
            if (paddingBytes >= 0)
            {
                byteArrayOutputStream.write(new byte[paddingBytes]);
                byteArrayOutputStream.write(retrieved);
            } else
            {
                writeWithoutLeadingNullBytes(byteArrayOutputStream, retrieved);
            }
            return byteArrayOutputStream.toByteArray();
        }
        private void writeWithoutLeadingNullBytes(ByteArrayOutputStream byteArrayOutputStream, byte[] bytes)
        {
            int index = 0;
            boolean nonNullByteFound = false;
            while (index < bytes.length)
            {
                byte value = bytes[index];
                if (value != 0 || nonNullByteFound)
                {
                    nonNullByteFound = true;
                    byteArrayOutputStream.write(value);
                }
                index++;
            }
        }
        public int getAddress()
        {
            return address;
        }
        @Override
        public boolean equals(Object object)
        {
            if (!(object instanceof SearchResult))
            {
                return false;
            }
            SearchResult searchResult = (SearchResult) object;
            return searchResult.getAddress() == getAddress();
        }
        @Override
        public int hashCode()
        {
            return Integer.hashCode(address);
        }
        public ValueSize getValueSize()
        {
            return valueSize;
        }
        @Override
        public SearchResult clone()
        {
            return new SearchResult(address, previousValue, currentValue, valueSize);
        }
        @Override
        public int compareTo(Object object)
        {
            return new Integer(address).compareTo(((SearchResult) object).getAddress());
        }
    }
    public static void main(String[] arguments)
    {
        long milliseconds = System.currentTimeMillis();
        int elementsCount = 2000000;
        /*List<Integer> list = new ArrayList<>();
        for (int elementIndex = 0; elementIndex < elementsCount; elementIndex++)
        {
            list.add(0);
        }*/
        List<SearchResult> searchResults = new ArrayList<>();
        for (int elementIndex = 0; elementIndex < elementsCount; elementIndex++)
        {
            SearchResult searchResult = new SearchResult(0x12345678, new BigInteger("3"), new BigInteger("1"), ValueSize.EIGHT_BIT);
            searchResults.add(searchResult);
        }
        System.out.println((System.currentTimeMillis() - milliseconds) / (double) 1000 + " seconds");
    }
}
What is the big issue here?
 
     
    