"索引就像图书馆的目录,映射就像电话簿,让查找变得超级简单!" 📚📞
🎯 什么是索引与映射?
想象一下,你是一个超级忙碌的图书管理员 📚。每天都有成千上万的读者来借书,如果每次都要在巨大的书库里一本一本地找,那你的腿早就跑断了!
索引就像是图书馆的目录系统,告诉你哪本书在哪个书架的第几层!
映射就像是电话簿,告诉你某个人的电话号码!
🏃♂️ 核心思想:用空间换时间,用预建索引换快速查找
没有索引:查找数据 → 遍历所有数据 → 找到结果 (耗时:1000ms)
有索引: 查找数据 → 直接定位 → 找到结果 (耗时:1ms)
性能提升:1000倍! 🎉
🎨 四种索引类型详解
1. 哈希表优化 - 像字典一样"秒"查 🗂️
生活比喻: 就像字典,知道拼音就能直接翻到那一页!
// 优化前的HashMap使用
public class BasicHashMapExample {
private Map<String, User> userMap = new HashMap<>();
public User getUser(String userId) {
return userMap.get(userId); // 简单查找
}
}
// 优化后的HashMap使用
public class OptimizedHashMapExample {
private Map<String, User> userMap;
private Map<String, User> hotUserMap; // 热点用户缓存
public OptimizedHashMapExample() {
// 预分配容量,避免扩容
this.userMap = new HashMap<>(10000);
this.hotUserMap = new HashMap<>(1000);
}
public User getUser(String userId) {
// 先查热点缓存
User user = hotUserMap.get(userId);
if (user != null) {
return user;
}
// 再查主缓存
user = userMap.get(userId);
if (user != null) {
// 热点用户加入热点缓存
if (isHotUser(user)) {
hotUserMap.put(userId, user);
}
}
return user;
}
private boolean isHotUser(User user) {
// 判断是否为热点用户
return user.getLoginCount() > 100;
}
}
哈希表优化技巧:
// 1. 预分配容量
Map<String, Object> map = new HashMap<>(expectedSize * 4 / 3 + 1);
// 2. 使用合适的负载因子
Map<String, Object> map = new HashMap<>(16, 0.75f);
// 3. 使用ConcurrentHashMap提高并发性能
Map<String, Object> concurrentMap = new ConcurrentHashMap<>();
// 4. 使用LinkedHashMap保持插入顺序
Map<String, Object> linkedMap = new LinkedHashMap<>();
2. 树形索引结构 - 像家族树一样分层 🏗️
生活比喻: 就像公司的组织架构图,从CEO到部门经理到普通员工,层级分明!
// 红黑树索引实现
public class RedBlackTreeIndex<K extends Comparable<K>, V> {
private Node root;
private static final boolean RED = true;
private static final boolean BLACK = false;
private class Node {
K key;
V value;
Node left, right;
boolean color;
Node(K key, V value, boolean color) {
this.key = key;
this.value = value;
this.color = color;
}
}
public void put(K key, V value) {
root = put(root, key, value);
root.color = BLACK;
}
private Node put(Node h, K key, V value) {
if (h == null) return new Node(key, value, RED);
int cmp = key.compareTo(h.key);
if (cmp < 0) h.left = put(h.left, key, value);
else if (cmp > 0) h.right = put(h.right, key, value);
else h.value = value;
// 红黑树平衡操作
if (isRed(h.right) && !isRed(h.left)) h = rotateLeft(h);
if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);
if (isRed(h.left) && isRed(h.right)) flipColors(h);
return h;
}
public V get(K key) {
Node x = root;
while (x != null) {
int cmp = key.compareTo(x.key);
if (cmp < 0) x = x.left;
else if (cmp > 0) x = x.right;
else return x.value;
}
return null;
}
private boolean isRed(Node x) {
if (x == null) return false;
return x.color == RED;
}
private Node rotateLeft(Node h) {
Node x = h.right;
h.right = x.left;
x.left = h;
x.color = h.color;
h.color = RED;
return x;
}
private Node rotateRight(Node h) {
Node x = h.left;
h.left = x.right;
x.right = h;
x.color = h.color;
h.color = RED;
return x;
}
private void flipColors(Node h) {
h.color = RED;
h.left.color = BLACK;
h.right.color = BLACK;
}
}
B+树索引实现:
public class BPlusTreeIndex<K extends Comparable<K>, V> {
private static final int MAX_KEYS = 4;
private Node root;
private abstract class Node {
abstract V get(K key);
abstract void put(K key, V value);
abstract void remove(K key);
}
private class LeafNode extends Node {
List<Entry<K, V>> entries = new ArrayList<>();
@Override
V get(K key) {
for (Entry<K, V> entry : entries) {
if (key.compareTo(entry.key) == 0) {
return entry.value;
}
}
return null;
}
@Override
void put(K key, V value) {
// 插入逻辑
int index = Collections.binarySearch(entries, new Entry<>(key, null));
if (index >= 0) {
entries.get(index).value = value;
} else {
entries.add(-index - 1, new Entry<>(key, value));
}
}
@Override
void remove(K key) {
entries.removeIf(entry -> key.compareTo(entry.key) == 0);
}
}
private class InternalNode extends Node {
List<K> keys = new ArrayList<>();
List<Node> children = new ArrayList<>();
@Override
V get(K key) {
int index = Collections.binarySearch(keys, key);
if (index >= 0) {
return children.get(index + 1).get(key);
} else {
int childIndex = -index - 1;
return children.get(childIndex).get(key);
}
}
@Override
void put(K key, V value) {
// 插入逻辑
}
@Override
void remove(K key) {
// 删除逻辑
}
}
private static class Entry<K, V> implements Comparable<Entry<K, V>> {
K key;
V value;
Entry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public int compareTo(Entry<K, V> other) {
return key.compareTo(other.key);
}
}
}
3. 位图索引 - 用0和1记录一切 📊
生活比喻: 就像考勤表,用✓和✗记录每个人每天的出勤情况!
public class BitmapIndex {
private Map<String, BitSet> indexes = new HashMap<>();
private List<String> values = new ArrayList<>();
public void addValue(String value) {
if (!indexes.containsKey(value)) {
indexes.put(value, new BitSet());
values.add(value);
}
}
public void setBit(String value, int position) {
BitSet bitSet = indexes.get(value);
if (bitSet != null) {
bitSet.set(position);
}
}
public boolean getBit(String value, int position) {
BitSet bitSet = indexes.get(value);
return bitSet != null && bitSet.get(position);
}
// 查找包含特定值的所有位置
public List<Integer> findPositions(String value) {
BitSet bitSet = indexes.get(value);
if (bitSet == null) return new ArrayList<>();
List<Integer> positions = new ArrayList<>();
for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
positions.add(i);
}
return positions;
}
// 位图交集操作
public BitSet intersect(String value1, String value2) {
BitSet bitSet1 = indexes.get(value1);
BitSet bitSet2 = indexes.get(value2);
if (bitSet1 == null || bitSet2 == null) {
return new BitSet();
}
BitSet result = (BitSet) bitSet1.clone();
result.and(bitSet2);
return result;
}
// 位图并集操作
public BitSet union(String value1, String value2) {
BitSet bitSet1 = indexes.get(value1);
BitSet bitSet2 = indexes.get(value2);
if (bitSet1 == null) return bitSet2 != null ? (BitSet) bitSet2.clone() : new BitSet();
if (bitSet2 == null) return (BitSet) bitSet1.clone();
BitSet result = (BitSet) bitSet1.clone();
result.or(bitSet2);
return result;
}
}
// 使用示例
public class BitmapIndexExample {
public static void main(String[] args) {
BitmapIndex index = new BitmapIndex();
// 添加值
index.addValue("北京");
index.addValue("上海");
index.addValue("广州");
// 设置位
index.setBit("北京", 0);
index.setBit("上海", 1);
index.setBit("北京", 2);
index.setBit("广州", 3);
// 查找
List<Integer> beijingPositions = index.findPositions("北京");
System.out.println("北京的位置: " + beijingPositions); // [0, 2]
// 交集
BitSet intersection = index.intersect("北京", "上海");
System.out.println("北京和上海的交集: " + intersection); // 空集
}
}
4. 倒排索引 - 从内容找文档 🔍
生活比喻: 就像搜索引擎,输入关键词就能找到包含这个词的所有网页!
public class InvertedIndex {
private Map<String, Set<Integer>> index = new HashMap<>();
private List<String> documents = new ArrayList<>();
public void addDocument(String document) {
int docId = documents.size();
documents.add(document);
// 分词
String[] words = document.toLowerCase().split("\\s+");
// 建立倒排索引
for (String word : words) {
word = word.replaceAll("[^a-zA-Z0-9]", ""); // 去除标点符号
if (!word.isEmpty()) {
index.computeIfAbsent(word, k -> new HashSet<>()).add(docId);
}
}
}
public Set<Integer> search(String query) {
String[] words = query.toLowerCase().split("\\s+");
Set<Integer> result = new HashSet<>();
for (String word : words) {
word = word.replaceAll("[^a-zA-Z0-9]", "");
if (!word.isEmpty()) {
Set<Integer> docIds = index.get(word);
if (docIds != null) {
if (result.isEmpty()) {
result.addAll(docIds);
} else {
result.retainAll(docIds); // 交集
}
} else {
return new HashSet<>(); // 没有找到任何文档
}
}
}
return result;
}
public Set<Integer> searchOr(String query) {
String[] words = query.toLowerCase().split("\\s+");
Set<Integer> result = new HashSet<>();
for (String word : words) {
word = word.replaceAll("[^a-zA-Z0-9]", "");
if (!word.isEmpty()) {
Set<Integer> docIds = index.get(word);
if (docIds != null) {
result.addAll(docIds); // 并集
}
}
}
return result;
}
public void printIndex() {
for (Map.Entry<String, Set<Integer>> entry : index.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
// 使用示例
public class InvertedIndexExample {
public static void main(String[] args) {
InvertedIndex index = new InvertedIndex();
// 添加文档
index.addDocument("Java is a programming language");
index.addDocument("Python is also a programming language");
index.addDocument("Java and Python are both popular");
// 搜索
Set<Integer> javaDocs = index.search("Java");
System.out.println("包含'Java'的文档: " + javaDocs);
Set<Integer> programmingDocs = index.search("programming");
System.out.println("包含'programming'的文档: " + programmingDocs);
Set<Integer> javaOrPythonDocs = index.searchOr("Java Python");
System.out.println("包含'Java'或'Python'的文档: " + javaOrPythonDocs);
// 打印索引
index.printIndex();
}
}
🎯 索引优化的实际应用
1. 数据库索引优化 🗄️
// 复合索引优化
@Entity
@Table(name = "users")
@Index(name = "idx_user_name_age", columnList = "name, age")
@Index(name = "idx_user_email", columnList = "email")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@Column(name = "age")
private Integer age;
@Column(name = "email")
private String email;
// getters and setters
}
// 查询优化
@Repository
public class UserRepository {
// 使用索引的查询
@Query("SELECT u FROM User u WHERE u.name = :name AND u.age = :age")
List<User> findByNameAndAge(@Param("name") String name, @Param("age") Integer age);
// 避免全表扫描的查询
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);
}
2. 搜索引擎优化 🔍
@Service
public class SearchEngineService {
private final InvertedIndex index;
private final Map<String, Double> termWeights = new HashMap<>();
public SearchEngineService() {
this.index = new InvertedIndex();
}
public void indexDocument(String docId, String content) {
// 计算词频
Map<String, Integer> termFreq = calculateTermFrequency(content);
// 计算权重
for (Map.Entry<String, Integer> entry : termFreq.entrySet()) {
String term = entry.getKey();
int freq = entry.getValue();
double weight = calculateWeight(term, freq);
termWeights.put(docId + ":" + term, weight);
}
index.addDocument(content);
}
public List<SearchResult> search(String query) {
Set<Integer> docIds = index.search(query);
List<SearchResult> results = new ArrayList<>();
for (Integer docId : docIds) {
double score = calculateScore(docId, query);
results.add(new SearchResult(docId, score));
}
// 按分数排序
results.sort((a, b) -> Double.compare(b.getScore(), a.getScore()));
return results;
}
private double calculateScore(Integer docId, String query) {
String[] terms = query.toLowerCase().split("\\s+");
double score = 0.0;
for (String term : terms) {
String key = docId + ":" + term;
Double weight = termWeights.get(key);
if (weight != null) {
score += weight;
}
}
return score;
}
}
3. 缓存索引优化 💾
@Service
public class CacheIndexService {
private final Map<String, Object> cache = new ConcurrentHashMap<>();
private final Map<String, Long> accessTimes = new ConcurrentHashMap<>();
private final Map<String, Integer> accessCounts = new ConcurrentHashMap<>();
public void put(String key, Object value) {
cache.put(key, value);
accessTimes.put(key, System.currentTimeMillis());
accessCounts.put(key, 0);
}
public Object get(String key) {
Object value = cache.get(key);
if (value != null) {
// 更新访问统计
accessTimes.put(key, System.currentTimeMillis());
accessCounts.put(key, accessCounts.getOrDefault(key, 0) + 1);
}
return value;
}
// 基于访问频率的索引
public List<String> getHotKeys() {
return accessCounts.entrySet().stream()
.sorted((a, b) -> Integer.compare(b.getValue(), a.getValue()))
.limit(100)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
// 基于访问时间的索引
public List<String> getRecentKeys() {
return accessTimes.entrySet().stream()
.sorted((a, b) -> Long.compare(b.getValue(), a.getValue()))
.limit(100)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
}
🛡️ 索引维护与优化
1. 索引重建策略 🔄
@Service
public class IndexMaintenanceService {
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
@PostConstruct
public void startMaintenance() {
// 每天凌晨2点重建索引
scheduler.scheduleAtFixedRate(this::rebuildIndex,
getDelayUntilNext2AM(), 24 * 60 * 60 * 1000, TimeUnit.MILLISECONDS);
// 每小时检查索引健康状态
scheduler.scheduleAtFixedRate(this::checkIndexHealth,
0, 60, TimeUnit.MINUTES);
}
private void rebuildIndex() {
try {
log.info("开始重建索引...");
long startTime = System.currentTimeMillis();
// 重建索引逻辑
rebuildAllIndexes();
long endTime = System.currentTimeMillis();
log.info("索引重建完成,耗时: {}ms", endTime - startTime);
} catch (Exception e) {
log.error("索引重建失败", e);
}
}
private void checkIndexHealth() {
// 检查索引健康状态
checkIndexConsistency();
checkIndexPerformance();
}
}
2. 索引性能监控 📊
@Component
public class IndexPerformanceMonitor {
private final MeterRegistry meterRegistry;
private final Timer indexSearchTimer;
private final Counter indexHitCounter;
private final Counter indexMissCounter;
public IndexPerformanceMonitor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.indexSearchTimer = Timer.builder("index.search.duration").register(meterRegistry);
this.indexHitCounter = Counter.builder("index.hits").register(meterRegistry);
this.indexMissCounter = Counter.builder("index.misses").register(meterRegistry);
}
public void recordSearch(Duration duration, boolean hit) {
indexSearchTimer.record(duration);
if (hit) {
indexHitCounter.increment();
} else {
indexMissCounter.increment();
}
}
public double getHitRate() {
double hits = indexHitCounter.count();
double misses = indexMissCounter.count();
return hits / (hits + misses);
}
}
🎉 总结:索引让查找变得"秒"级
索引与映射就像生活中的各种"查找工具":
- 哈希表 = 字典的拼音索引 📖
- 树形索引 = 公司的组织架构图 🏢
- 位图索引 = 考勤表的✓✗记录 📊
- 倒排索引 = 搜索引擎的关键词索引 🔍
通过合理使用索引与映射,我们可以:
- 🚀 大幅提升查找速度
- 💰 减少计算资源消耗
- ⚡ 改善用户体验
- 🎯 提高系统响应能力
记住:索引不是万能的,但它是快速查找的利器! 合理使用索引,让你的Java应用查找数据如探囊取物! ✨
"索引就像魔法,让查找变得瞬间完成!" 🪄⚡