Approach the problem from the other end. Instead of asking myself: "How can I reduce this data structure and still have tens of millions of them?" ask yourself: "How can I present this data using a completely different data structure, which is much more compact?"
It looks like you are building a doubly linked bools list, which, as you noticed, uses thirty to fifty times more memory than you need. Is there a reason why you are not just using BitArray
to store a list of bools?
UPDATE:
I actually tried to implement a sparse Boolean two-dimensional matrix
Well, why didn't you say that in the first place?
When I want to create a sparse boolean matrix with two huge sizes, I create an immutable constant logical quadrino with a memoized factory. If the array is sparse or even tight, but self-similar in some way, you can achieve huge compressions. Square arrays of 2 64 x 2 64 Booleans are easily represented, although, obviously, as a real array that will have more memory than exists in the world.
I worked on the idea of doing a series of blog articles on this technique; I will most likely do this at the end of March.
In short, the idea is to create an abstract Quad class that has two subclasses: Single and Multi. "Single" is a doubleton - as a singleton, but with exactly two instances called True and False. Multi is a Quad that has four subframes called NorthEast, SouthEast, SouthWest, and NorthWest.
Each square has an integer "level"; the level of a single zero is zero, and a multilevel level n is required for all his children to be Quads of level n-1.
Multi factory is remembered; when you ask him to create a new Multi with four children, he turns to the cache to find out if he has done this before. If he is, he does not create a new one; he passes the old one. Since Quads are immutable, you don’t have to worry about someone swapping Quad for you after it's in the cache.
Now consider how many words of memory (a word of 4 or 8 bytes depending on the architecture) "all false" consume many levels of n. Level 1 "all false" multi consumes four words to refer to its children, a word to calculate the level (if necessary, you do not need to maintain the level in multi, although this helps for debugging) and a few words for the synchronization block and so on. Call it eight words. (Plus, the memory for the False Single quad that we can count is constant two or three words, and therefore can be ignored.)
Level 2 “all false” multi uses the same eight words, but each of its four children is the same level 1. Thus, the total consumption of level 2 “all false” multites allows you to say 16 words.
The same goes for level 3, 4, ... and so on. The total memory consumption for a layered level of 64, which logically represents a 2 64 x 2 64 square array of Boolean objects, is just 64 x 16 words of memory!
Make sense? Hope this sketch is enough for you to get started. If not, see my blog at the end of March.