Using Bitmasks to Efficiently Store Data

There are many times when programing where you need to store state, and when this state is simply on or off, developers usually uses booleans. However, as projects grow, there tends to be collections of similar boolean state values, such as turned on features, or user hidden/displayed content. On Mint.com we have the ability to hide/show certain accounts types in the sidebar. I will be using this as a test case for todays article. There are 5 account types on Mint: Overview: bank, credit card, investment, loans, and other; each of these accounts types has a section in the sidebar that can be hidden or displayed. When the user hides/shows these sections we want to remember the state between sessions, so we use a binary number to store the state and bitmasks to modify the binary number.

First, lets discuss several technologies for storing the state, each of which have associated costs and benefits: cookies, session, database, or some combo thereof. In the Mint: Overview case, we are storing a visual preference for the user, so initially I planned to use cookies. By using cookies, we have a light-weight solution that remembers the users preferences on a single computer. However, if they use two computers, have multiple people using the same computer, or occasionally clear out there cookies, then there is no way to ensure that the state is remembered. For something minor, such as a visual preference, this might be acceptable, as cookies are fast and light-weight (unless you have lots of them). Alternatively, you could store the state in the database, and load it into the users session whenever they login. This is better than cookies, because it fixes all the above mentioned issues, and is still relatively light-weight, as you only need to make one database call to fetch the preference when they login. The only issue would be if the user is is using two simultaneous computers, which would have two separate sessions. That means setting and getting from the database is the only way to ensure that the users experience is the same on all computers, all the time. The drawback is making frequent database calls is expensive. On Mint we solve this by using a database caching layer, but if you do not have a caching layer, then I recommend storing data in the session.

Below is a JavaScript BitWriter Object that manages writing/reading bitmasks on a binary number. This article will show how to store and modify a binary number usings cookies. For more information on the methods I use to write/read cookies, see the post Cookies.

Lets define five constants to be our bitmasks, where the bitmask is the 2^n value, and n represents the binary position we are masking. So, five values would be represented by the binary number 2^5 (or 11111). Having a 0 in any position, indicates that the value is not set (false), and having a 1 indicates that the value is set (true). The bitmask is the value of 2 to the power of the position you are masking. Here is an example:

Example 1: Defining Bitmask

var BITMASK = {
    BANK: 1, // 2^0
    CREDIT: 2, // 2^1
    INVESTMENT: 4, // 2^2
    LOAN: 8, // 2^3
    OTHER: 16 // 2^4
};

So in our example, we use these constants to update the binary value stored in the cookie anytime the state changes. One simply fetches the binary value that is stored in the cookie and update it by removing, or adding the desired bitmask:

Example 2: Updating the Cookies

var COOKIE_ACCOUNT_STATE = accountPreferences;

var update = function(name, b) {
    var n = parseInt(Core.Client.readCookie(COOKIE_ACCOUNT_STATE), 10);
    var bw = Core.Widget.BitWriter(n);
    bw[b ? addBitmask : removeBitmask](BITMASK[name]);
    Core.Client.createCookie(COOKIE_ACCOUNT_STATE, bw.getValue(), 365);
};

update(BANK, true);

Here we read the cookie named accountPreferences and create a new BitWriter from the value. The example update function requires two parameters: the constant name of the bitamsk, and the boolean value of the state (true = on, false = off). Then we use the add/remove bitmask methods of BitWriter to update the value, before updating the cookie value.

The BitWriter Object has the following public functions: addBitmask, getValue, hasBitmask, removeBitmask. Each function ensures that bitmask is valid (a value of 2^n), before attempting the operation. The addBitmask function tests to see if the state of the bitmask is 0, and adds the bitmask to the binary value (this is like the binary operator | in other languages). The removeBitmask function tests to see if the state of the bitmask is 1, and removes the bitmask from the binary value (this is like the binary operator ^ in other languages). The hasBitmask function tests to see if the state of the bitmask is 1, and returns the mask in that case or 0 otherwise (this is like the binary operator & in other languages). The getValue method just returns the current value of the binary number managed by BitWriter.

The logic that runs BitWriter is not fancy, just a lot of basic binary math (see the source code here). I have also thrown together a simple test page, so that you can experiment with values and validate the logic of BitWriter.

------
Following user feedback, I took another look at binary operations in JavaScript only to learn that it does properly support it. I have since upgraded BitWriter to properly use binary operators instead of manually doing it. It is much faster now, regardless of the size of your binary value and about 1k smaller.