JS: Используется ли словарь быстрее, чем цикл по массиву?

Я создаю некоторую программу на Nodejs, которая должна будет отслеживать в памяти большого количества пользователей. Также у меня будет функция, которая фильтрует пользователя по id. Код будет выглядеть примерно так:

const users = [
    {
        id: 1,
        name: 'John',
        friends: [3, 6, 8]
    },
    {
        id: 2,
        name: 'Mark',
        friends: [567, 23]
    }
]

function getUserById(userId) {
    const user = users.filter(user => user.id === userId);
    return user[0];
}

Вопрос в том, работает ли эта версия в целом быстрее (каждый ключ - это идентификатор пользователя):

const users = {
    1: {
        id: 1,
        name: 'John',
        friends: [3, 6, 8]
    },
    2: {
        id: 2,
        name: 'Mark',
        friends: [567, 23]
    }
}

function getUserById(userId) {
   return users[userId];
}

Моя интуиция подсказывает, что словарь быстрее. Какие факты?

Предположим, что 100 тысяч пользователей

i.brod 28.11.2018 19:31

ваши 100 тысяч пользователей будут признательны, если вы воспользуетесь базой данных. Если вам действительно нужно хранить их в памяти, возможно, вы захотите взглянуть на redis

Max 28.11.2018 19:41

Это состояние будет использоваться для отслеживания пользователей, подключенных через SocketIO, поэтому используется память.

i.brod 28.11.2018 19:43
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
4
3
2 207
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Время поиска ключа в объектах не гарантируется. Это также может быть O (n), но большинство движков оптимизируют его до O (1), если вы динамически просматриваете ключ несколько раз. Фильтрация массива - O (n), но .find() в среднем в два раза быстрее:

  return users.find(user => user.id === userId);

Теперь единственная структура данных, которую ищет гарантии O (1), - это Map:

 const userMap = new Map(users.map(u => [u.id, u]));
 console.info(userMap.get("test"));

Однако если вы планируете делать это в очень большом масштабе (100 КБ - это большой размер), я бы предпочел переместить эту задачу в базу данных, поскольку она в значительной степени оптимизирована для этих задач. MongoDB будет легко внедрить, Redis будет очень быстрым, есть много других.

Что касается базы данных: это будет не просто список пользователей сайта, а список пользователей, подключенных через SocketIO. Вот почему мне нужно иметь дорожку в памяти.

i.brod 28.11.2018 19:41

@ sheff2k1, тогда я бы сохранил дополнительное свойство напрямую в объекте сокета.

Jonas Wilms 28.11.2018 19:43

Да, я планирую разместить свойство userData для каждого сокета, но мне все равно нужно будет отслеживать все подключенные сокеты, чтобы иногда выполнять какие-то специфичные для сокетов задачи.

i.brod 28.11.2018 19:44

@ sheff2k1 перемещение пользователей в базу данных также позволяет запускать несколько веб-узлов Nodejs параллельно, что будет опережать один поток в памяти сервера nodejs.

Jonas Wilms 28.11.2018 19:47

Да, это хороший момент. Обратите внимание, что у меня уже есть пользователи в базе данных SQL. Этот объект состояния - всего лишь способ создать некоторую корреляцию между сокетом и вошедшим в систему пользователем (система входа в систему полностью отделена от SocketIO).

i.brod 28.11.2018 19:51

Я написал небольшой скрипт, который можно скопировать и вставить в консоль, и он показывает актуальные данные для этого вопроса и на практике меняет ответ Джонаса Вильмса.

function random_int_from_range(x, y) {
return (x + Math.floor(Math.random() * (y - x + 1)));
}

function generate_name(length_min, length_max) {
  var letters = 'abcdefghijklmnopqrstuvwxyz';
  var name_array = [];

  for (var i = 0; i <= random_int_from_range(length_min, length_max); i ++) {
      name_array.push(letters.charAt(Math.floor(Math.random() * letters.length +1)));
  }

  return name_array.join('')
}

function generate_friends_array(length_min, length_max, num_users) {
  friends_array = [];
  for (var i = 0; i < random_int_from_range(length_min, length_max); i++) {
    friends_array.push(random_int_from_range(0, num_users - 1))
  }

  return friends_array
}

function generate_users_dict(num_users) {
  var users = {};
  for (var i = 0; i < num_users; i++) {
    users[i] = {
        'id': i,
        'name': generate_name(4,6),
        'friends': generate_friends_array(0, 20, num_users)
    }
  }

  return users
}

function generate_users_list_from_dict(users_dict) {
  var users_list = [];

  for (var key in  users_dict) {
    users_list.push(users_dict[key]);
  }

  return users_list;
}

function get_diff_in_seconds_from_two_milisecond_values(early_value, late_value) {
  return (late_value - early_value) / 1000
}

function get_user_by_id_from_dict(users_dict, user_id) {
  return users_dict[user_id]
}

function get_user_by_id_from_list(users_list, user_id) {
  const users = users_list.filter(user => user.id === user_id);
  return users[0]
}

function get_time_for_retrieval_of_item_from_object(object, object_length) {
  var function_names = ['get_user_by_id_from_dict', 'get_user_by_id_from_list'];
  var random_id = random_int_from_range(0, object_length - 1);
  var function_name = '';

  if (Array.isArray(object)) {
    function_name = function_names[1];
  }
  else {
    function_name = function_names[0];
  }

  var time_before_retrieval = new Date().getTime();
  window[function_name](object, random_id);
  var time_after_retrieval = new Date().getTime();

  return get_diff_in_seconds_from_two_milisecond_values(time_before_retrieval, 
  time_after_retrieval);
}

function test_retrieval_times(number_of_users, tests_num, object_type) {
  var users_dict = generate_users_dict(number_of_users);
  var users_list = generate_users_list_from_dict(users_dict);
  var times_array = [];
  var object = '';

  if (object_type == 'dict') {
    object = users_dict;
  }
  else {
    object = users_list;
  }

  for (var i = 0; i < tests_num; i++) {
    times_array.push(get_time_for_retrieval_of_item_from_object(object, 
    number_of_users));
  }

  return times_array;
}

function get_average_retrieval_time(object_type, number_of_users, 
                                    numbers_of_retrievals) {
  var retrieval_times = test_retrieval_times(number_of_users, numbers_of_retrievals, 
                                             object_type);
  var sum = 0;

  for (var i = 0; i < retrieval_times.length; i++) {
    sum += retrieval_times[i];
  }

  console.info('average retrieval time for ' +  object_type + ': ' + sum / 
              numbers_of_retrievals);
}

var number_of_users = parseInt(prompt("Please enter object size", "1000000"));
var number_of_retrievals = parseInt(prompt("Please enter number of retrievals", 
                                    "100"));

get_average_retrieval_time('dict', number_of_users, number_of_retrievals);
get_average_retrieval_time('list', number_of_users, number_of_retrievals);

Результаты тестов выводятся на консоль.

Другие вопросы по теме