static GList *load(const char *file);
 static gboolean bookmark_contains_all_tags(Bookmark *bm, char **query,
     unsigned int qlen);
-static Bookmark *line_to_bookmark(const char *line);
-static int bookmark_comp(Bookmark *a, Bookmark *b);
+static Bookmark *line_to_bookmark(char *uri, char *data);
 static void free_bookmark(Bookmark *bm);
 
 /**
 
 static GList *load(const char *file)
 {
-    return util_file_to_unique_list(
-        file, (Util_Content_Func)line_to_bookmark, (GCompareFunc)bookmark_comp,
-        (GDestroyNotify)free_bookmark, vb.config.history_max
-    );
+    return util_file_to_unique_list(file, (Util_Content_Func)line_to_bookmark, 0);
 }
 
 /**
     return true;
 }
 
-static Bookmark *line_to_bookmark(const char *line)
+static Bookmark *line_to_bookmark(char *uri, char *data)
 {
-    char **parts;
-    int len;
+    char *p;
     Bookmark *bm;
-    while (g_ascii_isspace(*line)) {
-        line++;
-    }
-    if (!*line) {
-        return NULL;
-    }
 
-    parts = g_strsplit(line, "\t", 3);
-    len   = g_strv_length(parts);
-
-    bm        = g_slice_new(Bookmark);
-    bm->uri   = g_strdup(parts[0]);
-    bm->tags  = NULL;
-    bm->title = NULL;
-    if (len == 3) {
-        bm->title = g_strdup(parts[1]);
-        bm->tags  = g_strdup(parts[2]);
-    } else if (len == 2) {
-        bm->title = g_strdup(parts[1]);
+    /* data part may consist of title or title<tab>tags*/
+    bm      = g_slice_new(Bookmark);
+    bm->uri = uri;
+    if ((p = strchr(data, '\t'))) {
+        *p        = '\0';
+        bm->title = data;
+        bm->tags  = p + 1;
+    } else {
+        bm->title = data;
+        bm->tags  = NULL;
     }
-    g_strfreev(parts);
 
     return bm;
 }
 
-static int bookmark_comp(Bookmark *a, Bookmark *b)
-{
-    return g_strcmp0(a->uri, b->uri);
-}
-
 static void free_bookmark(Bookmark *bm)
 {
     g_free(bm->uri);
-    g_free(bm->title);
-    g_free(bm->tags);
     g_slice_free(Bookmark, bm);
 }
 
 
 extern VbCore vb;
 
+#define HIST_FILE(t) (vb.files[file_map[t]])
 /* map history types to files */
 static const VbFile file_map[HISTORY_LAST] = {
     FILES_COMMAND,
     char *second;
 } History;
 
-static const char *get_file_by_type(HistoryType type);
 static GList *load(const char *file);
 static void write_to_file(GList *list, const char *file);
-static History *line_to_history(const char *line);
 static gboolean history_item_contains_all_tags(History *item, char **query,
     unsigned int qlen);
-static int history_comp(History *a, History *b);
+static History *line_to_history(char *uri, char *title);
 static void free_history(History *item);
 
 
     }
 
     for (HistoryType i = HISTORY_FIRST; i < HISTORY_LAST; i++) {
-        file = get_file_by_type(i);
+        file = HIST_FILE(i);
         list = load(file);
         write_to_file(list, file);
         g_list_free_full(list, (GDestroyNotify)free_history);
         return;
     }
 
-    file = get_file_by_type(type);
+    file = HIST_FILE(type);
     if (additional) {
         util_file_append(file, "%s\t%s\n", value, additional);
     } else {
     GtkTreeIter iter;
     History *item;
 
-    src = load(get_file_by_type(type));
+    src = load(HIST_FILE(type));
     src = g_list_reverse(src);
     if (!input || !*input) {
         /* without any tags return all items */
 
     switch (type) {
         case VB_INPUT_COMMAND:
-            src = load(get_file_by_type(HISTORY_COMMAND));
+            src = load(HIST_FILE(HISTORY_COMMAND));
             break;
 
         case VB_INPUT_SEARCH_FORWARD:
         case VB_INPUT_SEARCH_BACKWARD:
-            src = load(get_file_by_type(HISTORY_SEARCH));
+            src = load(HIST_FILE(HISTORY_SEARCH));
             break;
 
         default:
     }
     g_list_free_full(src, (GDestroyNotify)free_history);
 
-    /* prepend the original query as own item like done in vim to have the
+    /* Prepend the original query as own item like done in vim to have the
      * original input string in input box if we step before the first real
-     * item */
+     * item. */
     result = g_list_prepend(result, g_strdup(query));
 
     return result;
 }
 
-static const char *get_file_by_type(HistoryType type)
-{
-    return vb.files[file_map[type]];
-}
-
 /**
  * Loads history items form file but eleminate duplicates in FIFO order.
  *
- * Returned list must be freed with (GDestroyNotify) free_history.
+ * Returned list must be freed with (GDestroyNotify)free_history.
  */
 static GList *load(const char *file)
 {
-    /* read the history items from file */
     return util_file_to_unique_list(
-        file, (Util_Content_Func)line_to_history, (GCompareFunc)history_comp,
-        (GDestroyNotify)free_history, vb.config.history_max
+        file, (Util_Content_Func)line_to_history, vb.config.history_max
     );
 }
 
     }
 }
 
-static History *line_to_history(const char *line)
-{
-    char **parts;
-    int len;
-
-    while (VB_IS_SPACE(*line)) {
-        line++;
-    }
-    if (!*line) {
-        return NULL;
-    }
-
-    History *item = g_slice_new0(History);
-
-    parts = g_strsplit(line, "\t", 2);
-    len   = g_strv_length(parts);
-    if (len == 2) {
-        item->first  = g_strdup(parts[0]);
-        item->second = g_strdup(parts[1]);
-    } else {
-        item->first  = g_strdup(parts[0]);
-    }
-    g_strfreev(parts);
-
-    return item;
-}
-
 /**
  * Checks if the given array of tags are all found in history item.
  */
     return true;
 }
 
-static int history_comp(History *a, History *b)
+static History *line_to_history(char *uri, char *title)
 {
-    /* compare only the first part */
-    return g_strcmp0(a->first, b->first);
+    History *item = g_slice_new0(History);
+
+    item->first  = uri;
+    item->second = title;
+
+    return item;
 }
 
 static void free_history(History *item)
 {
+    /* The first and second property are created from the same allocated
+     * string so we only need to free the first. */
     g_free(item->first);
-    g_free(item->second);
     g_slice_free(History, item);
 }
 
 }
 
 /**
- * Retrieves a list with unique items from file.
+ * Retrieves a list with unique items from file. The uniqueness is calculated
+ * based on the lines comparing all chars until the next <tab> char or end of
+ * line.
  *
  * @filename:    file to read items from
- * @func:        function to parse a single line to item
- * @unique_func: function to decide if two items are equal
- * @free_func:   function to free already converted item if this isn't unique
+ * @func:        Function to parse a single line to item. This is called by
+ *               two strings of the same allocated memory chunk which isn't
+ *               freed here. This allows to use the strings like they are. But
+ *               in case the memory should be freed, free only that of the
+ *               first string.
  * @max_items:   maximum number of items that are returned, use 0 for
  *               unlimited items
  */
 GList *util_file_to_unique_list(const char *filename, Util_Content_Func func,
-    GCompareFunc unique_func, GDestroyNotify free_func, unsigned int max_items)
+    guint max_items)
 {
-    GList *gl = NULL;
-    /* yes, the whole file is read and wen possible don not need all the
-     * lines, but this is easier to implement compared to reading the file
-     * line wise from end to beginning */
     char *line, **lines;
-    void *value;
-    int len, num_items = 0;
-
-    /* return empty list if max items is 0 */
-    if (!max_items) {
-        return gl;
-    }
+    int i, len;
+    GList *gl = NULL;
+    GHashTable *ht;
 
     lines = util_get_lines(filename);
-    len   = g_strv_length(lines);
-    if (!len) {
-        return gl;
+    if (!lines) {
+        return NULL;
     }
 
-    /* begin with the last line of the file to make unique check easier -
-     * every already existing item in the list is the latest, so we don't need
-     * to remove items from the list which takes some time */
-    for (int i = len - 1; i >= 0; i--) {
+    /* Use the hashtable to check for duplicates in a faster way than by
+     * iterating over the generated list itself. So it's enough to store the
+     * the keys only. */
+    ht = g_hash_table_new(g_str_hash, g_str_equal);
+
+    /* Begin with the last line of the file to make unique check easier -
+     * every already existing item in the table is the latest, so we don't need
+     * to do anything if an item already exists in the hash table. */
+    len = g_strv_length(lines);
+    for (i = len - 1; i >= 0; i--) {
+        char *key, *data;
+        void *item;
+
         line = lines[i];
         g_strstrip(line);
         if (!*line) {
             continue;
         }
 
-        if ((value = func(line))) {
-            /* if the value is already in list, free it and don't put it onto
-             * the list */
-            if (g_list_find_custom(gl, value, unique_func)) {
-                free_func(value);
-            } else {
-                gl = g_list_prepend(gl, value);
-                /* skip the loop if we precessed max_items unique items */
-                if (++num_items >= max_items) {
+        /* if line contains tab char - separate the line at this */
+        if ((data = strchr(line, '\t'))) {
+            *data = '\0';
+            key   = line;
+            data++;
+        } else {
+            key  = line;
+            data = NULL;
+        }
+
+        /* If the key part of file line is not in the hash table, insert it
+         * into the table and also in the list. */
+        if (!g_hash_table_lookup_extended(ht, key, NULL, NULL)) {
+            if ((item = func(key, data))) {
+                g_hash_table_insert(ht, key, NULL);
+                gl = g_list_prepend(gl, item);
+
+                /* Don't put more entries into the list than requested. */
+                if (max_items && g_hash_table_size(ht) >= max_items) {
                     break;
                 }
             }
         }
     }
-    g_strfreev(lines);
+
+    /* Free the memory for the string array but keep the strings untouched. */
+    g_free(lines);
+    g_hash_table_destroy(ht);
 
     return gl;
 }
 
     UTIL_EXP_SPECIAL = 0x04, /* expand % to current URI */
 };
 
-typedef gboolean (*Util_Comp_Func)(const char*, const char*);
-typedef void *(*Util_Content_Func)(const char*);
+typedef void *(*Util_Content_Func)(char*, char*);
 
 char* util_get_config_dir(void);
 char* util_get_cache_dir(void);
 char* util_get_file_contents(const char* filename, gsize* length);
 char** util_get_lines(const char* filename);
 GList *util_file_to_unique_list(const char *filename, Util_Content_Func func,
-    GCompareFunc unique_func, GDestroyNotify free_func, unsigned int max_items);
+    guint max_items);
 gboolean util_file_append(const char *file, const char *format, ...);
 gboolean util_file_prepend(const char *file, const char *format, ...);
 char* util_strcasestr(const char* haystack, const char* needle);