Search code examples
javadata-structuresdequecircular-bufferarraydeque

ArrayDeque operations


I am having some free time and trying to understand how ArrayDeque works internally. I've read a couple of articles and questions/answers here and I think I'm pretty close. I used debugging to follow the workflow and there is something that's bothering me. I created an empty deque which resulted in an array with 16 elements which are null. When I used addFirst it added an element on position 16 in the array and addLast added in the beggining on position 0. What am I missing, can you please share some knowledge or point me in the right direction so I can fully understand what is happening behind the curtains. Thanks in advance!


Solution

  • An array-based deque is typically implemented using a data structure called a circular buffer. The idea is that we maintain an array of elements, but pretend that the ends of the array are glued together to form a ring.

    From your debugging, it seems like the ArrayDeque internally maintains an array of 16 elements, which we can view like this:

    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    

    We maintain two different pointers, a head pointer and a tail pointer, keeping track of the position of the first element of the deque and the position one past the last element of the deque. Initially, they'll point to the start of the array:

     head
      |
      v
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
      ^
      |
     tail
    

    Whenever we do an addFirst, we back up the head pointer by one step, then write the element to the location we find. Since we're pretending that the two ends of the array are linked together, backing up one step here moves the head pointer to the last position:

                                                                 head
                                                                  |
                                                                  v
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | X |
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
      ^
      |
     tail
    

    To do an addLast, we write to the tail position, then advance it forward:

                                                                 head
                                                                  |
                                                                  v
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    | X |   |   |   |   |   |   |   |   |   |   |   |   |   |   | X |
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
          ^
          |
         tail
    

    Here's what it would look like if we did two more addFirsts:

                                                         head
                                                          |
                                                          v
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    | X |   |   |   |   |   |   |   |   |   |   |   |   | X | X | X |
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
          ^
          |
         tail
    

    And here's what it would look like if we did two more addLasts:

                                                         head
                                                          |
                                                          v
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    | X | X | X |   |   |   |   |   |   |   |   |   |   | X | X | X |
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
                  ^
                  |
                 tail
    

    We read the elements of the deque by starting at the head pointer and proceeding forward until we reach the tail pointer. So in this case, we start reading from the slot pointed at by head, not the first position in the array.

    Eventually, the two pointers will meet in the middle. When that happens, we create a brand-new array that's larger than the original one (usually, 150% bigger), then copy the elements over into the new array to free up some space.