数据结构实战指南从数组链表到树的性能优化技巧
先说说我为啥要重新整理数据结构
最近重构一个老项目的搜索功能,用户输入关键词后要返回相关商品列表,但数据量特别大,每次搜索都卡得不行。看了眼控制台,原来是从后端返回了一个复杂的嵌套对象,前端还要做各种过滤和排序处理。折腾了半天,发现根本问题还是数据结构设计得太乱了。
以前总是想着能跑就行,现在数据量上来了,之前那种随意组织数据的方式真的要命。所以这次好好整理了下常用的数据结构优化技巧,亲测有效。
数组转Map,查找效率直接起飞
这是我在项目中最常用的优化手段。之前有个需求是要根据用户ID快速找到对应信息,在一个包含上千个用户的数组里反复查找,性能差得很。
优化前的代码长这样:
// 低效查找 - O(n) 时间复杂度
function getUserById(userId) {
return users.find(user => user.id === userId);
}
// 每次查找都要遍历整个数组,太慢了
const targetUser = getUserById(targetId);
改成Map之后,查找变成了O(1)时间复杂度:
// 将数组转换为Map
const userMap = new Map();
users.forEach(user => {
userMap.set(user.id, user);
});
// 高效查找 - O(1) 时间复杂度
function getUserById(userId) {
return userMap.get(userId);
}
这里踩了个坑,就是Map的key如果是字符串的话,要注意类型匹配问题。比如用户ID是数字,但是有时候前端传过来变成字符串,这时候就要注意统一类型。
扁平化处理,减少嵌套层级
后端返回的数据经常是一层层的嵌套,比如电商项目里的分类数据:
const nestedCategories = [
{
id: 1,
name: "电子产品",
children: [
{
id: 11,
name: "手机",
children: [
{ id: 111, name: "iPhone" },
{ id: 112, name: "Android" }
]
},
{
id: 12,
name: "电脑",
children: [
{ id: 121, name: "笔记本" },
{ id: 122, name: "台式机" }
]
}
]
}
];
这种嵌套层级很深的数据处理起来很麻烦,所以我习惯用递归转成一维数组:
function flattenTree(categories, parentPath = []) {
let result = [];
categories.forEach(category => {
const currentPath = [...parentPath, category.name];
// 添加当前节点
result.push({
id: category.id,
name: category.name,
path: currentPath.join(' > '),
level: currentPath.length - 1
});
// 递归处理子节点
if (category.children && category.children.length) {
result = result.concat(flattenTree(category.children, currentPath));
}
});
return result;
}
const flatCategories = flattenTree(nestedCategories);
// 输出:
// [
// {id: 1, name: "电子产品", path: "电子产品", level: 0},
// {id: 11, name: "手机", path: "电子产品 > 手机", level: 1},
// {id: 111, name: "iPhone", path: "电子产品 > 手机 > iPhone", level: 2}
// ...
// ]
这样处理后,查找、筛选、渲染都变得很简单,特别是需要显示面包屑导航的时候特别方便。
索引映射,避免重复计算
在处理大量商品数据时,经常需要根据多个维度来筛选,比如价格区间、品牌、类别等。如果每次都重新计算就会很慢。
我的解决方案是建立索引映射:
class ProductIndexer {
constructor(products) {
this.products = products;
this.priceIndex = new Map(); // 价格索引
this.brandIndex = new Map(); // 品牌索引
this.categoryIndex = new Map(); // 分类索引
this.buildIndexes();
}
buildIndexes() {
this.products.forEach((product, index) => {
// 构建价格区间索引
const priceRange = Math.floor(product.price / 100) * 100; // 每100元一个区间
if (!this.priceIndex.has(priceRange)) {
this.priceIndex.set(priceRange, []);
}
this.priceIndex.get(priceRange).push(index);
// 构建品牌索引
if (!this.brandIndex.has(product.brand)) {
this.brandIndex.set(product.brand, []);
}
this.brandIndex.get(product.brand).push(index);
// 构建分类索引
if (!this.categoryIndex.has(product.category)) {
this.categoryIndex.set(product.category, []);
}
this.categoryIndex.get(product.category).push(index);
});
}
filterByPrice(min, max) {
const result = [];
for (let [range, indices] of this.priceIndex) {
if (range >= min && range <= max) {
result.push(...indices);
}
}
return [...new Set(result)].map(i => this.products[i]);
}
filterByBrand(brand) {
const indices = this.brandIndex.get(brand) || [];
return indices.map(i => this.products[i]);
}
multiFilter(options = {}) {
let result = new Set();
if (options.minPrice !== undefined && options.maxPrice !== undefined) {
const priceFiltered = new Set(
this.filterByPrice(options.minPrice, options.maxPrice)
.map(p => p.id)
);
if (result.size === 0) {
result = priceFiltered;
} else {
result = new Set([...result].filter(x => priceFiltered.has(x)));
}
}
if (options.brand) {
const brandFiltered = new Set(
this.filterByBrand(options.brand)
.map(p => p.id)
);
if (result.size === 0) {
result = brandFiltered;
} else {
result = new Set([...result].filter(x => brandFiltered.has(x)));
}
}
return Array.from(result).map(id =>
this.products.find(p => p.id === id)
);
}
}
踩坑提醒:这种预处理方式适合数据不经常变化的场景。如果数据频繁更新,维护索引的成本可能比直接查询还高。而且要特别注意内存占用,大量数据的索引也会占用不少空间。
缓存策略,避免重复请求
数据结构优化不仅仅是内存里的事情,API层面也需要考虑缓存。我在jztheme.com的项目中就遇到过这个问题,同一个用户信息被反复请求。
class DataCache {
constructor(ttl = 300000) { // 默认5分钟过期
this.cache = new Map();
this.ttl = ttl;
}
set(key, value) {
this.cache.set(key, {
data: value,
timestamp: Date.now()
});
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() - item.timestamp > this.ttl) {
this.cache.delete(key); // 过期删除
return null;
}
return item.data;
}
async getData(key, fetchFunction) {
const cached = this.get(key);
if (cached) {
return cached;
}
const data = await fetchFunction();
this.set(key, data);
return data;
}
}
// 使用示例
const cache = new DataCache();
async function getUserInfo(userId) {
return await cache.getData(user_${userId}, async () => {
const response = await fetch(https://jztheme.com/api/users/${userId});
return response.json();
});
}
这里的关键是设置合适的TTL时间,太短了缓存效果不好,太长了数据可能不准确。一般根据业务特点来调整,比如用户基本信息可以设置较长的缓存时间,订单状态这种实时性要求高的就要短一点。
内存监控,别让优化变成负担
说了这么多优化技巧,但别忘了监控内存使用情况。数据结构优化后虽然提高了查询效率,但也可能增加了内存占用。
我用过这种方式监控内存:
function monitorMemory() {
if (performance.memory) {
console.log('内存使用情况:', {
used: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) + 'MB',
total: Math.round(performance.memory.totalJSHeapSize / 1024 / 1024) + 'MB',
limit: Math.round(performance.memory.jsHeapSizeLimit / 1024 / 1024) + 'MB'
});
}
}
// 定期检查
setInterval(monitorMemory, 30000);
如果发现内存持续增长,要考虑清理不必要的缓存,或者限制缓存大小。
小结
数据结构优化确实能显著提升性能,但这只是冰山一角。选择合适的数据结构要考虑具体场景,比如数据量大小、读写频率、更新频率等等。
以上是我踩坑后的总结,希望对你有帮助。这个技巧的拓展用法还有很多,后续会继续分享这类博客。

暂无评论