Java SynchronizedRandomAccessList vs CopyOnWriteArrayList

Here’s a microbenchmark of SynchronizedRandomAccessList and CopyOnWriteArrayList. I wanted to see how well one or another performed with little number of changed, where CopyOnWriteArrayList could potentially be better.

package com.icyrock.java.collection;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;

public class SpeedPrb {
  private static interface ListCreator<I> {
    List<Integer> create(List<Integer> seed);
  }

  public static void main(String[] args) {
    new SpeedPrb().run();
  }

  private void run() {
    try {
      runUnsafe();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private void runUnsafe() throws InterruptedException {
    for (String runType : new String[] { "warm-up", "test" }) {
      System.out.println("Run type: " + runType);

      withList(new ListCreator<List<Integer>>() {
        public List<Integer> create(List<Integer> seed) {
          return Collections.synchronizedList(new ArrayList<Integer>(seed));
        }
      });

      withList(new ListCreator<List<Integer>>() {
        public List<Integer> create(List<Integer> seed) {
          return new CopyOnWriteArrayList<Integer>(seed);
        }
      });
    }
  }

  private void withList(ListCreator<List<Integer>> listCreator) throws InterruptedException {
    withThreadCount(listCreator, 1);
    withThreadCount(listCreator, 5);
    withThreadCount(listCreator, 10);
  }

  private void withThreadCount(ListCreator<List<Integer>> listCreator, int threadCount)
    throws InterruptedException {
    withDataSize(listCreator, threadCount, 10000);
    withDataSize(listCreator, threadCount, 100000);
    withDataSize(listCreator, threadCount, 1000000);
  }

  private void withDataSize(ListCreator<List<Integer>> listCreator, int threadCount, int dataSize)
    throws InterruptedException {
    withDataModSize(listCreator, threadCount, dataSize, 0);
    withDataModSize(listCreator, threadCount, dataSize, 1);
    withDataModSize(listCreator, threadCount, dataSize, 5);
    withDataModSize(listCreator, threadCount, dataSize, 10);
    withDataModSize(listCreator, threadCount, dataSize, 50);
  }

  private void withDataModSize(final ListCreator<List<Integer>> listCreator,
    final int threadCount, final int dataSize, final int dataModSize) throws InterruptedException {
    List<Integer> seed = new ArrayList<>();
    for (int i = 0; i < dataSize; i++) {
      seed.add(i);
    }

    List<Integer> list = listCreator.create(seed);

    long start = System.nanoTime();

    doMods(list, threadCount, dataSize, dataModSize);
    long sum = doSum(list, threadCount, dataSize);

    long end = System.nanoTime();
    double elapsed = (end - start) / 1e6;

    System.out.println("End"
      + ": list.class: " + list.getClass().getSimpleName()
      + ", threadCount: " + threadCount
      + ", dataSize: " + dataSize
      + ", dataModSize: " + dataModSize
      + ", elapsed: " + elapsed
      + ", sum: " + sum);
  }

  private void doMods(final List<Integer> list, final int threadCount, final int dataSize,
    final int dataModSize) throws InterruptedException {
    Thread[] threads = new Thread[threadCount];
    final int count = dataSize / threadCount;
    for (int i = 0; i < threadCount; i++) {
      final int modId = i * count;

      Thread thread = new Thread(new Runnable() {
        public void run() {
          int modCnt = 0;
          for (int i = 0; i < count; i++) {
            if (modCnt < dataModSize) {
              int pos = list.get(modId + i);
              list.set(pos, list.get(pos) + 1);
              modCnt++;
            }
            if (modCnt == dataModSize) {
              break;
            }
          }
        }
      });
      thread.setDaemon(true);

      threads[i] = thread;
    }

    for (int i = 0; i < threadCount; i++) {
      threads[i].start();
    }

    for (int i = 0; i < threadCount; i++) {
      Thread thread = threads[i];
      synchronized (thread) {
        if (thread.isAlive()) {
          thread.wait();
        }
      }
    }
  }

  private long doSum(final List<Integer> list, final int threadCount, final int dataSize)
    throws InterruptedException {
    final AtomicLong sum = new AtomicLong();

    Thread[] threads = new Thread[threadCount];
    final int count = dataSize / threadCount;
    for (int i = 0; i < threadCount; i++) {
      final int modId = i * count;

      Thread thread = new Thread(new Runnable() {
        public void run() {
          for (int i = 0; i < count; i++) {
            int pos = list.get(modId + i);
            long delta = list.get(pos);
            sum.addAndGet(delta);
          }
        }
      });
      thread.setDaemon(true);

      threads[i] = thread;
    }

    for (int i = 0; i < threadCount; i++) {
      threads[i].start();
    }

    for (int i = 0; i < threadCount; i++) {
      Thread thread = threads[i];
      synchronized (thread) {
        if (thread.isAlive()) {
          thread.wait();
        }
      }
    }

    return sum.get();
  }
}

Results:

Run type: warm-up
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 10000, dataModSize: 0, elapsed: 34.447801, sum: 49995000
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 10000, dataModSize: 1, elapsed: 8.95851, sum: 49995001
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 10000, dataModSize: 5, elapsed: 9.545876, sum: 49995009
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 10000, dataModSize: 10, elapsed: 1.744018, sum: 49995019
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 10000, dataModSize: 50, elapsed: 1.887545, sum: 49995099
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 100000, dataModSize: 0, elapsed: 10.752156, sum: 4999950000
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 100000, dataModSize: 1, elapsed: 13.370241, sum: 4999950001
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 100000, dataModSize: 5, elapsed: 15.297022, sum: 4999950009
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 100000, dataModSize: 10, elapsed: 10.267383, sum: 4999950019
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 100000, dataModSize: 50, elapsed: 16.844115, sum: 4999950099
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 1000000, dataModSize: 0, elapsed: 74.83112, sum: 499999500000
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 1000000, dataModSize: 1, elapsed: 82.226897, sum: 499999500001
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 1000000, dataModSize: 5, elapsed: 74.646764, sum: 499999500009
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 1000000, dataModSize: 10, elapsed: 71.52722, sum: 499999500019
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 1000000, dataModSize: 50, elapsed: 73.328613, sum: 499999500099
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 10000, dataModSize: 0, elapsed: 7.449299, sum: 49995000
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 10000, dataModSize: 1, elapsed: 7.883584, sum: 49995005
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 10000, dataModSize: 5, elapsed: 7.536815, sum: 49995045
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 10000, dataModSize: 10, elapsed: 7.7768, sum: 49995095
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 10000, dataModSize: 50, elapsed: 9.246285, sum: 49995495
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 100000, dataModSize: 0, elapsed: 67.314426, sum: 4999950000
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 100000, dataModSize: 1, elapsed: 70.664588, sum: 4999950005
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 100000, dataModSize: 5, elapsed: 68.318753, sum: 4999950045
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 100000, dataModSize: 10, elapsed: 71.206388, sum: 4999950095
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 100000, dataModSize: 50, elapsed: 65.248959, sum: 4999950495
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 1000000, dataModSize: 0, elapsed: 461.098965, sum: 499999500000
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 1000000, dataModSize: 1, elapsed: 406.9108, sum: 499999500005
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 1000000, dataModSize: 5, elapsed: 513.206703, sum: 499999500045
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 1000000, dataModSize: 10, elapsed: 473.542246, sum: 499999500095
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 1000000, dataModSize: 50, elapsed: 570.581461, sum: 499999500495
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 10000, dataModSize: 0, elapsed: 12.406631, sum: 49995000
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 10000, dataModSize: 1, elapsed: 14.833395, sum: 49995010
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 10000, dataModSize: 5, elapsed: 11.98533, sum: 49995090
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 10000, dataModSize: 10, elapsed: 11.679525, sum: 49995190
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 10000, dataModSize: 50, elapsed: 13.218288, sum: 49995990
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 100000, dataModSize: 0, elapsed: 69.624681, sum: 4999950000
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 100000, dataModSize: 1, elapsed: 69.140069, sum: 4999950010
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 100000, dataModSize: 5, elapsed: 77.640096, sum: 4999950090
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 100000, dataModSize: 10, elapsed: 71.927987, sum: 4999950190
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 100000, dataModSize: 50, elapsed: 66.570783, sum: 4999950990
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 1000000, dataModSize: 0, elapsed: 378.502667, sum: 499999500000
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 1000000, dataModSize: 1, elapsed: 419.156151, sum: 499999500010
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 1000000, dataModSize: 5, elapsed: 339.901594, sum: 499999500090
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 1000000, dataModSize: 10, elapsed: 426.555234, sum: 499999500190
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 1000000, dataModSize: 50, elapsed: 395.766989, sum: 499999500990
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 10000, dataModSize: 0, elapsed: 11.901218, sum: 49995000
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 10000, dataModSize: 1, elapsed: 3.829014, sum: 49995001
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 10000, dataModSize: 5, elapsed: 10.980387, sum: 49995009
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 10000, dataModSize: 10, elapsed: 1.90118, sum: 49995019
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 10000, dataModSize: 50, elapsed: 5.033604, sum: 49995099
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 100000, dataModSize: 0, elapsed: 4.303462, sum: 4999950000
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 100000, dataModSize: 1, elapsed: 6.433356, sum: 4999950001
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 100000, dataModSize: 5, elapsed: 8.015953, sum: 4999950009
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 100000, dataModSize: 10, elapsed: 10.697569, sum: 4999950019
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 100000, dataModSize: 50, elapsed: 20.900689, sum: 4999950099
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 1000000, dataModSize: 0, elapsed: 22.107373, sum: 499999500000
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 1000000, dataModSize: 1, elapsed: 26.153887, sum: 499999500001
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 1000000, dataModSize: 5, elapsed: 34.408805, sum: 499999500009
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 1000000, dataModSize: 10, elapsed: 51.090004, sum: 499999500019
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 1000000, dataModSize: 50, elapsed: 153.929584, sum: 499999500099
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 10000, dataModSize: 0, elapsed: 3.918603, sum: 49995000
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 10000, dataModSize: 1, elapsed: 5.118304, sum: 49995005
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 10000, dataModSize: 5, elapsed: 8.821934, sum: 49995045
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 10000, dataModSize: 10, elapsed: 12.317311, sum: 49995095
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 10000, dataModSize: 50, elapsed: 21.752215, sum: 49995495
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 100000, dataModSize: 0, elapsed: 40.760553, sum: 4999950000
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 100000, dataModSize: 1, elapsed: 27.017787, sum: 4999950005
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 100000, dataModSize: 5, elapsed: 24.395766, sum: 4999950045
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 100000, dataModSize: 10, elapsed: 39.489004, sum: 4999950095
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 100000, dataModSize: 50, elapsed: 113.153861, sum: 4999950495
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 1000000, dataModSize: 0, elapsed: 353.752201, sum: 499999500000
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 1000000, dataModSize: 1, elapsed: 282.50961, sum: 499999500005
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 1000000, dataModSize: 5, elapsed: 423.664369, sum: 499999500045
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 1000000, dataModSize: 10, elapsed: 501.767616, sum: 499999500095
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 1000000, dataModSize: 50, elapsed: 1458.115879, sum: 499999500495
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 10000, dataModSize: 0, elapsed: 6.227392, sum: 49995000
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 10000, dataModSize: 1, elapsed: 27.889835, sum: 49995010
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 10000, dataModSize: 5, elapsed: 5.488267, sum: 49995090
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 10000, dataModSize: 10, elapsed: 9.8526, sum: 49995190
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 10000, dataModSize: 50, elapsed: 34.633951, sum: 49995990
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 100000, dataModSize: 0, elapsed: 44.695707, sum: 4999950000
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 100000, dataModSize: 1, elapsed: 21.955976, sum: 4999950010
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 100000, dataModSize: 5, elapsed: 54.120305, sum: 4999950090
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 100000, dataModSize: 10, elapsed: 78.413631, sum: 4999950190
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 100000, dataModSize: 50, elapsed: 239.205075, sum: 4999950990
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 1000000, dataModSize: 0, elapsed: 362.92385, sum: 499999500000
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 1000000, dataModSize: 1, elapsed: 320.027424, sum: 499999500010
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 1000000, dataModSize: 5, elapsed: 505.38798, sum: 499999500090
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 1000000, dataModSize: 10, elapsed: 1165.81135, sum: 499999500190
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 1000000, dataModSize: 50, elapsed: 1869.912839, sum: 499999500990
Run type: test
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 10000, dataModSize: 0, elapsed: 0.97632, sum: 49995000
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 10000, dataModSize: 1, elapsed: 0.95858, sum: 49995001
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 10000, dataModSize: 5, elapsed: 0.979087, sum: 49995009
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 10000, dataModSize: 10, elapsed: 1.088347, sum: 49995019
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 10000, dataModSize: 50, elapsed: 1.278052, sum: 49995099
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 100000, dataModSize: 0, elapsed: 5.128055, sum: 4999950000
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 100000, dataModSize: 1, elapsed: 5.959685, sum: 4999950001
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 100000, dataModSize: 5, elapsed: 6.951121, sum: 4999950009
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 100000, dataModSize: 10, elapsed: 8.605555, sum: 4999950019
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 100000, dataModSize: 50, elapsed: 3.878474, sum: 4999950099
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 1000000, dataModSize: 0, elapsed: 34.628495, sum: 499999500000
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 1000000, dataModSize: 1, elapsed: 33.505706, sum: 499999500001
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 1000000, dataModSize: 5, elapsed: 34.152629, sum: 499999500009
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 1000000, dataModSize: 10, elapsed: 34.661612, sum: 499999500019
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 1000000, dataModSize: 50, elapsed: 33.869828, sum: 499999500099
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 10000, dataModSize: 0, elapsed: 7.829228, sum: 49995000
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 10000, dataModSize: 1, elapsed: 7.338248, sum: 49995005
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 10000, dataModSize: 5, elapsed: 6.597094, sum: 49995045
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 10000, dataModSize: 10, elapsed: 9.179939, sum: 49995095
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 10000, dataModSize: 50, elapsed: 12.672502, sum: 49995495
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 100000, dataModSize: 0, elapsed: 57.167164, sum: 4999950000
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 100000, dataModSize: 1, elapsed: 57.412349, sum: 4999950005
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 100000, dataModSize: 5, elapsed: 67.656372, sum: 4999950045
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 100000, dataModSize: 10, elapsed: 63.353544, sum: 4999950095
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 100000, dataModSize: 50, elapsed: 66.889391, sum: 4999950495
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 1000000, dataModSize: 0, elapsed: 521.963095, sum: 499999500000
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 1000000, dataModSize: 1, elapsed: 517.867147, sum: 499999500005
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 1000000, dataModSize: 5, elapsed: 542.079316, sum: 499999500045
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 1000000, dataModSize: 10, elapsed: 386.976245, sum: 499999500095
End: list.class: SynchronizedRandomAccessList, threadCount: 5, dataSize: 1000000, dataModSize: 50, elapsed: 405.384225, sum: 499999500495
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 10000, dataModSize: 0, elapsed: 8.967124, sum: 49995000
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 10000, dataModSize: 1, elapsed: 7.918772, sum: 49995010
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 10000, dataModSize: 5, elapsed: 4.081412, sum: 49995090
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 10000, dataModSize: 10, elapsed: 4.461432, sum: 49995190
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 10000, dataModSize: 50, elapsed: 6.415205, sum: 49995990
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 100000, dataModSize: 0, elapsed: 45.251236, sum: 4999950000
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 100000, dataModSize: 1, elapsed: 47.764752, sum: 4999950010
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 100000, dataModSize: 5, elapsed: 60.124809, sum: 4999950090
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 100000, dataModSize: 10, elapsed: 48.346995, sum: 4999950190
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 100000, dataModSize: 50, elapsed: 39.349112, sum: 4999950990
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 1000000, dataModSize: 0, elapsed: 340.371111, sum: 499999500000
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 1000000, dataModSize: 1, elapsed: 481.969672, sum: 499999500010
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 1000000, dataModSize: 5, elapsed: 535.011974, sum: 499999500090
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 1000000, dataModSize: 10, elapsed: 540.011967, sum: 499999500190
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 1000000, dataModSize: 50, elapsed: 531.670146, sum: 499999500990
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 10000, dataModSize: 0, elapsed: 1.149342, sum: 49995000
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 10000, dataModSize: 1, elapsed: 1.239204, sum: 49995001
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 10000, dataModSize: 5, elapsed: 1.352173, sum: 49995009
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 10000, dataModSize: 10, elapsed: 1.7898, sum: 49995019
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 10000, dataModSize: 50, elapsed: 3.165936, sum: 49995099
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 100000, dataModSize: 0, elapsed: 5.357431, sum: 4999950000
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 100000, dataModSize: 1, elapsed: 6.688772, sum: 4999950001
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 100000, dataModSize: 5, elapsed: 8.353105, sum: 4999950009
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 100000, dataModSize: 10, elapsed: 10.515936, sum: 4999950019
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 100000, dataModSize: 50, elapsed: 20.277377, sum: 4999950099
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 1000000, dataModSize: 0, elapsed: 23.185615, sum: 499999500000
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 1000000, dataModSize: 1, elapsed: 27.466323, sum: 499999500001
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 1000000, dataModSize: 5, elapsed: 33.748808, sum: 499999500009
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 1000000, dataModSize: 10, elapsed: 48.301544, sum: 499999500019
End: list.class: CopyOnWriteArrayList, threadCount: 1, dataSize: 1000000, dataModSize: 50, elapsed: 198.997429, sum: 499999500099
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 10000, dataModSize: 0, elapsed: 5.603725, sum: 49995000
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 10000, dataModSize: 1, elapsed: 6.166289, sum: 49995005
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 10000, dataModSize: 5, elapsed: 7.827472, sum: 49995045
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 10000, dataModSize: 10, elapsed: 9.659996, sum: 49995095
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 10000, dataModSize: 50, elapsed: 20.942762, sum: 49995495
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 100000, dataModSize: 0, elapsed: 40.315674, sum: 4999950000
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 100000, dataModSize: 1, elapsed: 42.313047, sum: 4999950005
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 100000, dataModSize: 5, elapsed: 38.214826, sum: 4999950045
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 100000, dataModSize: 10, elapsed: 42.748483, sum: 4999950095
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 100000, dataModSize: 50, elapsed: 111.678623, sum: 4999950495
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 1000000, dataModSize: 0, elapsed: 271.684979, sum: 499999500000
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 1000000, dataModSize: 1, elapsed: 285.626939, sum: 499999500005
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 1000000, dataModSize: 5, elapsed: 382.996082, sum: 499999500045
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 1000000, dataModSize: 10, elapsed: 469.095528, sum: 499999500095
End: list.class: CopyOnWriteArrayList, threadCount: 5, dataSize: 1000000, dataModSize: 50, elapsed: 1126.529645, sum: 499999500495
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 10000, dataModSize: 0, elapsed: 5.903503, sum: 49995000
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 10000, dataModSize: 1, elapsed: 5.537715, sum: 49995010
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 10000, dataModSize: 5, elapsed: 8.679113, sum: 49995090
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 10000, dataModSize: 10, elapsed: 13.175054, sum: 49995190
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 10000, dataModSize: 50, elapsed: 27.420182, sum: 49995990
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 100000, dataModSize: 0, elapsed: 37.46934, sum: 4999950000
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 100000, dataModSize: 1, elapsed: 21.53966, sum: 4999950010
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 100000, dataModSize: 5, elapsed: 54.847308, sum: 4999950090
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 100000, dataModSize: 10, elapsed: 56.360711, sum: 4999950190
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 100000, dataModSize: 50, elapsed: 191.754104, sum: 4999950990
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 1000000, dataModSize: 0, elapsed: 333.820706, sum: 499999500000
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 1000000, dataModSize: 1, elapsed: 398.443131, sum: 499999500010
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 1000000, dataModSize: 5, elapsed: 547.40548, sum: 499999500090
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 1000000, dataModSize: 10, elapsed: 757.341745, sum: 499999500190
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 1000000, dataModSize: 50, elapsed: 2268.404341, sum: 499999500990

This was done on AMD Phenom II X4 Mobile N950, 2.1 GHz, which has 4 cores.

Comments:

  • Modifications hurt CopyOnWriteArrayList, especially with big data sizes – even only 50 modifications make it 4 times slower
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 1000000, dataModSize: 50, elapsed: 531.670146, sum: 499999500990
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 1000000, dataModSize: 50, elapsed: 2268.404341, sum: 499999500990
  • Without modifications, SynchronizedRandomAccessList pays the price for having to do synchronization being 70%-80% slower:
End: list.class: SynchronizedRandomAccessList, threadCount: 10, dataSize: 10000, dataModSize: 0, elapsed: 8.967124, sum: 49995000
End: list.class: CopyOnWriteArrayList, threadCount: 10, dataSize: 10000, dataModSize: 0, elapsed: 5.903503, sum: 49995000
  • Warm-up is really important when measuring – here we see a 35 times slower run:
Run type: warm-up
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 10000, dataModSize: 0, elapsed: 34.447801, sum: 49995000
...
Run type: test
End: list.class: SynchronizedRandomAccessList, threadCount: 1, dataSize: 10000, dataModSize: 0, elapsed: 0.97632, sum: 49995000

Split string with regex and keep delimiters

The other day I needed to split a string with regex delimiter, but also keep these delimiters. Java’s default String.split does not do that – it throws away the delimiters. Below is the code that can be used to achieve this.

Java

import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class SplitWithDelimiters {
  public static void main(String[] args) {
    new SplitWithDelimiters().run();
  }

  private void run() {
    String regex = "\\s*[+\\-*/]\\s*";

    assert !new String[] { }.equals(
      splitWithDelimiters("", regex));
    assert !new String[] { "1" }.equals(
      splitWithDelimiters("1", regex));
    assert !new String[] { "1", "+" }.equals(
      splitWithDelimiters("1+", regex));
    assert !new String[] { "-", "1" }.equals(
      splitWithDelimiters("-1", regex));
    assert !new String[] { "- ", "- ", "-", "1" }.equals(
      splitWithDelimiters("- - -1", regex));
    assert !new String[] { "1", " + ", "2" }.equals(
      splitWithDelimiters("1 + 2", regex));
    assert !new String[] { "-", "1", " + ", "2", " - ", "3", "/", "4" }.equals(
      splitWithDelimiters("-1 + 2 - 3/4", regex));
    
    System.out.println("Done.");
  }

  private String[] splitWithDelimiters(String str, String regex) {
    List<String> parts = new ArrayList<String>();

    Pattern p = Pattern.compile(regex);
    Matcher m = p.matcher(str);

    int lastEnd = 0;
    while(m.find()) {
      int start = m.start();
      if(lastEnd != start) {
        String nonDelim = str.substring(lastEnd, start);
        parts.add(nonDelim);
      }
      String delim = m.group();
      parts.add(delim);

      int end = m.end();
      lastEnd = end;
    }

    if(lastEnd != str.length()) {
      String nonDelim = str.substring(lastEnd);
      parts.add(nonDelim);
    }

    String[] res =  parts.toArray(new String[]{});
    System.out.println("result: " + Arrays.toString(res));

    return res;
  }
}

Clojure

Here’s a test file for the Clojure version:

(deftest split-keep-delim-test
  (is (= []
         (split-keep-delim "" #"\d+")))
  (is (= ["abc"]
         (split-keep-delim "abc" #"\d+")))
  (is (= ["-" "1" " + " "2" " - " "3" "/" "4"]
         (split-keep-delim "-1 + 2 - 3/4" #"\s*[+\-*/]\s*")))
  (is (= ["a" "b" "12" "b" "a"]
         (split-keep-delim "ab12ba" #"[ab]"))))

and the implementation:

(defn split-keep-delim 
  "Splits str with re-delim. Returns list of parts, including delimiters. Lazy.

   > (split-keep-delim \"-1 + 2 - 3/4\" #\"\\s*[+\\-*/]\\s*\")
   [\"-\" \"1\" \" + \" \"2\" \" - \" \"3\" \"/\" \"4\"]
   > (split-keep-delim \"ab12ba\" #\"[ab]\")
   [\"a\" \"b\" \"12\" \"b\" \"a\"]"
  [str re-delim]
  (let [m (.matcher re-delim str)]
    ((fn step [last-end]
       (if (.find m)
         (let [start (.start m)
               end (.end m)
               delim (.group m)
               new-head (if (not= last-end start)
                          [(.substring str last-end start) delim]
                          [delim])]
           (concat new-head (lazy-seq (step end))))
         (if (not= last-end (.length str))
           [(.substring str last-end)]
           []))) 0)))

This version is lazy, though I did not notice any speedup as you can see from the timings below. Timings are rather good for what I needed:

> (let [s (apply str (take 100 (cycle "-1 + 2 - 3/4"))) pat #"\s*[+\-*/]\s*"] (time (dotimes [_ 1000] (take 1 (split-keep-delim s pat)))))
"Elapsed time: 26.013445 msecs"
nil
> (let [s (apply str (take 100 (cycle "-1 + 2 - 3/4"))) pat #"\s*[+\-*/]\s*"] (time (dotimes [_ 1000] (take 3 (split-keep-delim s pat)))))
"Elapsed time: 28.754948 msecs"
nil
> (let [s (apply str (take 100 (cycle "-1 + 2 - 3/4"))) pat #"\s*[+\-*/]\s*"] (time (dotimes [_ 1000] (take 300 (split-keep-delim s pat)))))
"Elapsed time: 28.388654 msecs"
nil

Developing Bluetooth-enabled apps with Java

A good starting explanation about network programming in general comes from this page. Look at the whole book – it’s called “An Introduction to Bluetooth Programming” and I find it a very good read.

Emulation

Before we start, I’d like to mention that, for development, it’s much easier to develop using emulation of bluetooth / mobile phone. For that, I’ve used BlueCove and MicroEmulator, respectively. The glue between them is the BlueCove JSR-82 Emulator module.

Scenario

The situation we’ll examine is simple. We have two devices – in this case a PC and a phone – communicating with each other. The example would be a simple searcher for Stack Exchange sites. The user on the mobile phone would type a word, that gets sent to the server on the PC, it searches using api.stackexchange.com and returns the results to the phone, which displays them.

Following are the steps needed for the above to happen.

Discovery

To communicate, a bluetooth app uses the SDP (Service Discovery Protocol) to find the other devices and services they provide. In this case, as PC acts as the server, the phone would initiate the discovery, i.e. be a client. The basic idea is presented in BlueCove’s javadoc. I’ve made a helper class called BluetoothUtils:

package com.icyrock.bluetooth;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.bluetooth.DataElement;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class BluetoothUtils {
  private static final int btAttrServiceName = 0x0100;
  private final static Logger log = LoggerFactory.getLogger(BluetoothUtils.class);

  public List<RemoteDevice> discoverDevices() {
    final Object sync = new Object();
    final List<RemoteDevice> devs = new ArrayList<RemoteDevice>();

    DiscoveryListener listener = new DiscoveryListener() {
      public void deviceDiscovered(RemoteDevice dev, DeviceClass devClass) {
        try {
          log.info("Found device: address: {}, name: {}",
            dev.getBluetoothAddress(),
            dev.getFriendlyName(false));
          devs.add(dev);
        } catch (IOException e) {
          log.error(e.toString(), e);
        }
      }

      public void inquiryCompleted(int discType) {
        log.info("Device inquiry completed");
        synchronized (sync) {
          sync.notifyAll();
        }
      }

      public void serviceSearchCompleted(int transId, int respCode) {
      }

      public void servicesDiscovered(int transId, ServiceRecord[] servRecord) {
      }
    };

    synchronized (sync) {
      boolean started;
      try {
        started = LocalDevice.getLocalDevice().getDiscoveryAgent()
          .startInquiry(DiscoveryAgent.GIAC, listener);
        if (started) {
          log.info("Device inquiry started");
          sync.wait();
          log.info("Devices count: {}", devs.size());
        }
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

    return devs;
  }

  public List<String> searchForServices(List<RemoteDevice> devices, String uuidStr) {
    final Object sync = new Object();
    final List<String> urls = new ArrayList<String>();

    DiscoveryListener listener = new DiscoveryListener() {
      public void deviceDiscovered(RemoteDevice dev, DeviceClass devClass) {
      }

      public void inquiryCompleted(int discType) {
      }

      public void servicesDiscovered(int transId, ServiceRecord[] servRecord) {
        for (int i = 0; i < servRecord.length; i++) {
          String url = servRecord[i].getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
          if (url != null) {
            urls.add(url);
            DataElement name = servRecord[i].getAttributeValue(btAttrServiceName);
            log.info("Service found: url: {}, name: {}", url, name);
          }
        }
      }

      public void serviceSearchCompleted(int transId, int respCode) {
        log.info("Service search completed");
        synchronized (sync) {
          sync.notifyAll();
        }
      }

    };

    UUID[] uuidArr = new UUID[] { new UUID(uuidStr, false) };
    int[] attrIds = new int[] { btAttrServiceName };

    for (RemoteDevice device : devices) {
      synchronized (sync) {
        try {
          log.info("Searching for service: {} on device: {} / {}",
            new Object[] { uuidStr, device.getBluetoothAddress(), device.getFriendlyName(false) });
          LocalDevice.getLocalDevice().getDiscoveryAgent()
            .searchServices(attrIds, uuidArr, device, listener);
          sync.wait();
        } catch (Exception e) {
          log.error(e.toString(), e);
        }
      }
    }

    return urls;
  }
}

This one has two methods:

  • public List<RemoteDevice> discoverDevices() – Searches for the devices and returns a list of devices that have been found
  • public List<String> searchForServices(List<RemoteDevice> devices, String uuidStr) – Searches for the service given by uuidStr on the devices in the devices list. It returns the URLs that can be used to access these services

These methods are going to be used in client and server later.

Client

The client is fairly simple:

package com.icyrock.bluetooth;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

import javax.bluetooth.RemoteDevice;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.common.collect.Lists;
import com.icyrock.spring.SpringUtils;

@Component
public class StackExchangeBluetoothRfcommClient {
  private final static Logger log = LoggerFactory.getLogger(StackExchangeBluetoothRfcommClient.class);
  @Autowired
  private BluetoothUtils bluetoothUtils;
  private final static String uuid = "88e08d4875344ff88f18ec4cc70ee702";

  public void run() {
    try {
      startClient();
    } catch (Throwable t) {
      log.error(t.toString(), t);
    }
  }

  private void startClient() throws Exception {
    List<RemoteDevice> devices = bluetoothUtils.discoverDevices();
    List<String> urls = bluetoothUtils.searchForServices(devices, uuid);

    if (urls.size() > 0) {
      String keywords = "bluetooth";

      StreamConnection streamConn = (StreamConnection) Connector.open(urls.get(0));

      OutputStream os = streamConn.openOutputStream();
      IOUtils.writeLines(Lists.newArrayList(keywords), null, os);
      os.close();

      InputStream is = streamConn.openInputStream();
      String result = IOUtils.toString(is);
      is.close();

      log.info("Result: {}", result);

      streamConn.close();

      log.info("Client done");
    }
  }

  public static void main(String[] args) {
    System.setProperty("bluecove.stack", "emulator");
    StackExchangeBluetoothRfcommClient sebc = SpringUtils.getDefaultContext()
      .getBean(StackExchangeBluetoothRfcommClient.class);
    sebc.run();
  }
}

Aside from the boilerplate code needed, there are two important notes above. First, in main method, there’s this line:

    System.setProperty("bluecove.stack", "emulator");

This one is used when testing this with BlueCove Bluetooth emulation, to signal BlueCove to use the emulator instead of the real bluetooth stack. Read more about this here.

Second, note this line:

  private final static String uuid = "88e08d4875344ff88f18ec4cc70ee702";

This one defines the UUID of the service that we are going to look for. In Bluetooth, all services are represented by UUID. To generate these in Linux, you can do something like:

$ uuidgen|tr -d '-'
db56eca4204d4f478063dacab16e805f

The other things are all in startClient method. The method does this:

  • Searches for all devices
  • Searches for the given service (identified by the uuid) on all found devices
  • If the search result yields any URLs, pick the first one (the other option would be to present the user with the choice of which URL to use)
  • Open a StreamConnection – this is basically a RFCOMM channel
  • Write the search keywords to the output stream
  • Read the response from the server and log it

Note that I’m using a few helper libraries here:

Here’s a Gradle config for this project:

apply plugin: 'java'

repositories {
  mavenCentral()
}

dependencies {
  compile 'net.sf.bluecove:bluecove:2.1.0'
  runtime 'net.sf.bluecove:bluecove-gpl:2.1.0'
  runtime 'net.sf.bluecove:bluecove-emu:2.1.0'
  
  compile 'ch.qos.logback:logback-classic:1.0.7'
  compile 'commons-io:commons-io:2.4'
  compile 'org.apache.httpcomponents:httpclient:4.2.1'
  compile 'org.apache.httpcomponents:fluent-hc:4.2.1'
  compile 'com.fasterxml.jackson.core:jackson-databind:2.1.0'
 
  compile 'org.springframework:spring-context:3.1.2.RELEASE'
  runtime 'asm:asm:3.3.1'
  runtime 'cglib:cglib-nodep:2.2.2'
  runtime 'org.slf4j:jcl-over-slf4j:1.6.6'
  
  compile 'com.google.guava:guava:13.0.1'
}

StreamUtils

One problem that I encountered is that you cannot use BufferedReader to read from InputStream provided by the bluetooth connection. The issue is that readLine will block until the whole line is read, but the way it works is it reads chunks of data to be efficient and thus doesn’t know when the line ends. In order to circumvent this, I’m using a simple char-by-char reading, which is hacky, inefficient and might not work properly sometimes, but solves this problem and works well for this example.

package com.icyrock.bluetooth;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.google.common.base.Joiner;

@Component
public class StreamUtils {
  private final static Logger log = LoggerFactory.getLogger(StreamUtils.class);

  private String readLine(InputStream inputStream) {
    StringBuilder sb = new StringBuilder();
    int sleeps = 0;
    boolean haveNewLine = false;

    while (true) {
      int byteRead;
      try {
        if (inputStream.available() == 0) {
          if (sleeps < 3) {
            sleeps++;
            Thread.sleep(1000);
            continue;
          } else {
            break;
          }
        }

        byteRead = inputStream.read();
      } catch (Exception e) {
        throw new RuntimeException(e);
      }

      if (byteRead == -1) {
        break;
      }

      char ch = (char) byteRead;
      if (ch == '\n') {
        haveNewLine = true;
        break;
      }
      sb.append(ch);
    }
    return haveNewLine ? sb.toString() : null;
  }

  public List<String> readLines(InputStream inputStream) {
    List<String> lines = new ArrayList<>();
    while (true) {
      String line = readLine(inputStream);
      if (line == null) {
        break;
      }
      lines.add(line);
    }
    return lines;
  }

  public String readLinesAsStr(InputStream inputStream) {
    return Joiner.on("\n").join(readLines(inputStream));
  }
}

Server

Server is pretty simple, too:

package com.icyrock.bluetooth;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;

import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.LocalDevice;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;

import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.utils.URIBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.icyrock.spring.SpringUtils;

@Component
public class StackExchangeBluetoothRfcommServer {
  private final static Logger log = LoggerFactory.getLogger(StackExchangeBluetoothRfcommServer.class);
  private final static String uuid = "88e08d4875344ff88f18ec4cc70ee702";
  @Autowired
  private StreamUtils streamUtils;

  public void run() {
    try {
      startServer();
    } catch (Throwable t) {
      log.error(t.toString(), t);
    }
  }

  private void startServer() throws Exception {
    LocalDevice.getLocalDevice().setDiscoverable(DiscoveryAgent.GIAC);

    StreamConnectionNotifier serverConnection = (StreamConnectionNotifier) Connector.open(
      "btspp://localhost:" + uuid + ";name=StackExchangeBluetoothServer");

    while (true) {
      log.info("Waiting for connection");
      StreamConnection streamConn = serverConnection.acceptAndOpen();

      log.info("Request received");

      InputStream is = streamConn.openInputStream();
      String content = streamUtils.readLinesAsStr(is);
      is.close();

      List<String> resp;
      if (content == null || content.equals("")) {
        log.info("Empty keywords received from the client.");
        resp = Lists.newArrayList("Please provide keywords to search for");
      } else {
        resp = queryStackExchange(content);
      }

      OutputStream os = streamConn.openOutputStream();
      IOUtils.writeLines(resp, null, os);
      os.close();

      streamConn.close();
    }
  }

  @SuppressWarnings("unchecked")
  private List<String> queryStackExchange(String keywords) throws Exception {
    // Uncomment if you want to test without actually making the calls to StackExchange
//    if(true) {
//      return Lists.newArrayList("title1", "title2", "title3");
//    }
    
    URI uri = new URIBuilder()
      .setScheme("https")
      .setHost("api.stackexchange.com")
      .setPath("/2.1/search")
      .setParameter("order", "desc")
      .setParameter("intitle", keywords)
      .setParameter("site", "stackoverflow")
      .setParameter("pagesize", "5")
      .build();

    HttpResponse httpResp = Request.Get(uri)
      .connectTimeout(5000)
      .socketTimeout(5000)
      .execute().returnResponse();

    List<String> ret = new ArrayList<String>();
    if (httpResp.getStatusLine().getStatusCode() == 200) {
      HttpEntity httpEnt = httpResp.getEntity();

      InputStream is = httpEnt.getContent();
      Header contentEncoding = httpResp.getFirstHeader("Content-Encoding");
      if (contentEncoding != null && "gzip".equals(contentEncoding.getValue())) {
        is = new GZIPInputStream(is);
      }
      String response = IOUtils.toString(is);
      is.close();

      ObjectMapper om = new ObjectMapper();
      Map<String, Object> map = om.readValue(response, Map.class);
      List<Map<String, Object>> items = (List<Map<String, Object>>) map.get("items");

      for (Map<String, Object> item : items) {
        ret.add((String) item.get("title"));
      }
    }

    log.info("Result: {}", Joiner.on(", ").join(ret));
    return ret;
  }

  public static void main(String[] args) {
    System.setProperty("bluecove.stack", "emulator");
    StackExchangeBluetoothRfcommServer sebs = SpringUtils.getDefaultContext()
      .getBean(StackExchangeBluetoothRfcommServer.class);
    sebs.run();
  }
}

In startServer method, it listens for the connection. When one is established, it reads the keywords from the client. If nothing was read, it will reply with a help message. If keywords were read, it will query the StackExchange. The query is separated into queryStackExchange method and uses Apache HttpClient fluid interface – seems pretty nice to me. If the response was received, it will un-GZIP it if needed and then use Jakson JSON parser to actually get the titles from the response.