С какими контейнерами работаешь чаще всего
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
С какими контейнерами работаешь чаще всего
В backend-разработке на C++, особенно при работе с высоконагруженными системами, я использую следующие контейнеры в зависимости от задачи:
1. std::vector — самый часто используемый контейнер
Когда использую: Почти всегда, когда нужна динамическая коллекция данных.
// Буферизация данных из сети
std::vector<char> network_buffer;
buffer.reserve(65536); // Приделяю память заранее
// Batch-обработка запросов
std::vector<Request> batch;
batch.push_back(request1);
batch.push_back(request2);
// Работа с таблицами из БД
std::vector<UserRecord> users = db.query("SELECT * FROM users");
// Временные данные для сортировки
std::vector<Score> scores = get_leaderboard();
std::sort(scores.begin(), scores.end());
Преимущества:
- Кэш-дружественность (continuous memory)
- O(1) random access
- Простые операции с памятью
- Предсказуемая производительность
2. std::unordered_map — для быстрого поиска
Когда использую: Кэши, индексы, маппирование ID на данные, счётчики.
// Кэш пользователей по ID (исключает повторные запросы в БД)
std::unordered_map<uint64_t, User> user_cache;
auto it = user_cache.find(user_id);
if (it != user_cache.end()) {
return it->second; // O(1) в среднем
}
// Счётчик частоты слов
std::unordered_map<std::string, int> word_frequency;
for (const auto& word : text) {
word_frequency[word]++;
}
// Маппирование сессий
std::unordered_map<std::string, SessionData> sessions;
sessions[session_token] = {user_id, timestamp, ip};
// Индекс для быстрого поиска
std::unordered_map<int, std::vector<int>> inverted_index;
Преимущества:
- O(1) в среднем случае поиск
- O(1) вставка/удаление
- Не требует упорядоченности
Недостатки:
- Худший случай O(n) при hash collisions
- Не сохраняет порядок
- Больше памяти чем std::map
3. std::map — для упорядоченного доступа
Когда использую: Когда нужен порядок, диапазонные запросы, сортировка.
// Кэш с временем жизни (нужна упорядоченность по времени)
std::map<int64_t, CacheEntry> timestamp_index;
auto old = timestamp_index.begin();
if (old->first < now - CACHE_TTL) {
timestamp_index.erase(old); // Удаляем самый старый
}
// Диапазонные запросы (все записи за период)
auto start = timestamp_map.lower_bound(start_time);
auto end = timestamp_map.upper_bound(end_time);
for (auto it = start; it != end; ++it) {
process(it->second);
}
// Отсортированный индекс по цене
std::map<double, std::vector<Product>> price_index;
Преимущества:
- O(log n) поиск, вставка, удаление
- Упорядоченность гарантирована
- Удобные диапазонные операции (lower_bound, upper_bound)
4. std::deque — для очередей и буферов с две концов
Когда использую: Очереди обработки, циклические буферы, скользящие окна.
// Очередь задач (добавляем в конец, берём с начала)
std::deque<Task> task_queue;
task_queue.push_back(new_task); // O(1)
Task current = task_queue.front();
task_queue.pop_front(); // O(1)
// Скользящее окно (moving average)
std::deque<double> window;
for (double value : data_stream) {
window.push_back(value);
if (window.size() > WINDOW_SIZE) {
window.pop_front();
}
double avg = calculate_average(window);
}
// Буфер с операциями с двух сторон
std::deque<Event> event_buffer;
event_buffer.push_back(new_event); // Добавить в конец
event_buffer.push_front(urgent_event); // Добавить в начало
Преимущества:
- O(1) операции с двумя концами
- Кэш-дружественнее чем list
- Хороша для очередей
5. std::set/std::multiset — для уникальности и упорядоченности
Когда использую: Уникальные ID, множество операций (объединение, пересечение), рейтинги.
// Множество активных сессий (для быстрого поиска)
std::set<SessionId> active_sessions;
if (active_sessions.count(session_id)) {
// сессия активна
}
// Лидерборд (multiset с кастомным компаратором)
std::multiset<ScoreEntry> leaderboard;
leaderboard.insert({player_id, score});
// Множество заблокированных IP (для проверок)
std::set<std::string> blacklist;
if (!blacklist.count(client_ip)) {
allow_connection();
}
6. std::list — редко, только когда нужны частые вставки/удаления в середине
Когда использую: Очень редко, в основном для структур данных которые требуютO(1) вставок в произвольном месте.
// Двусвязный список для LRU-кэша
std::list<CacheItem> cache_list;
std::unordered_map<Key, std::list<CacheItem>::iterator> index;
void access(Key key) {
auto it = index[key];
cache_list.erase(it); // Удалить откуда угодно O(1)
cache_list.push_front(*it); // Добавить в начало O(1)
index[key] = cache_list.begin();
}
Рекомендуемые комбинации для разных задач
Кэширование с вытеснением (LRU):
std::unordered_map<Key, std::list<Value>::iterator> index;
std::list<Value> recency_list;
Высоконагруженный бэкенд (обработка запросов):
std::deque<Request> request_queue; // Входящие запросы
std::unordered_map<uint64_t, User> users; // Кэш пользователей
std::map<int64_t, MetricEntry> metrics; // Метрики по времени
std::vector<Response> batch_responses; // Батч ответов
Аналитика и обработка данных:
std::vector<Record> raw_data; // Сырые данные
std::unordered_map<std::string, int> freq; // Подсчёты
std::map<double, std::vector<Item>> index; // Индексы
std::set<Id> processed_ids; // Отслеживание обработанных
Практические советы по выбору
| Задача | Контейнер | Причина |
|---|---|---|
| Буфер данных | vector | Быстро, кэш-friendly |
| Быстрый поиск по ключу | unordered_map | O(1) avg |
| Диапазонные запросы | map | sorted, O(log n) |
| Очередь (FIFO) | deque | O(1) обе стороны |
| Уникальные значения | set | auto-dedupe, O(log n) |
| Частые вставки в середину | list | редко нужно! |
| Лидерборд | multiset | auto-sort, дубликаты |
Общее правило: Используй vector по умолчанию, переходи на другие контейнеры только когда vector становится узким местом или семантика требует другого.