暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

动画演示HASH JOIN过程

原创 二两烧麦 2025-10-27
241
Hash Join算法动画演示

数据库Hash Join算法演示

本动画展示了Hash Join算法的两个阶段:构建阶段(使用小表构建哈希表)和探查阶段(使用大表探测哈希表)

<div class="container">
    <div class="tables-container">
        <div class="table-section">
            <div class="table-title">小表 (Build Side)</div>
        </div>
        <div class="table-section">
            <div class="table-title">大表 (Probe Side)</div>
        </div>
    </div>
    
    <canvas id="hashJoinCanvas" width="1000" height="600"></canvas>
    
    <div class="controls">
        <button id="startBtn">开始演示</button>
        <button id="buildBtn" disabled>构建阶段</button>
        <button id="probeBtn" disabled>探查阶段</button>
        <button id="resetBtn">重置</button>
    </div>
    
    <div id="status" class="status"></div>
    
    <div class="legend">
        <div class="legend-item">
            <div class="color-box" style="background-color: #3498db;"></div>
            <span>小表 (构建表)</span>
        </div>
        <div class="legend-item">
            <div class="color-box" style="background-color: #e74c3c;"></div>
            <span>大表 (探查表)</span>
        </div>
        <div class="legend-item">
            <div class="color-box" style="background-color: #2ecc71;"></div>
            <span>哈希表</span>
        </div>
        <div class="legend-item">
            <div class="color-box" style="background-color: #f39c12;"></div>
            <span>匹配结果</span>
        </div>
        <div class="legend-item">
            <div class="color-box" style="background-color: #9b59b6;"></div>
            <span>当前处理元素</span>
        </div>
    </div>
</div>

<script>
    // 获取Canvas和上下文
    const canvas = document.getElementById('hashJoinCanvas');
    const ctx = canvas.getContext('2d');
    
    // 获取按钮和状态元素
    const startBtn = document.getElementById('startBtn');
    const buildBtn = document.getElementById('buildBtn');
    const probeBtn = document.getElementById('probeBtn');
    const resetBtn = document.getElementById('resetBtn');
    const statusEl = document.getElementById('status');
    
    // 定义表数据
    const smallTable = [
        { id: 101, name: 'Alice', hash: 1 },
        { id: 102, name: 'Bob', hash: 2 },
        { id: 103, name: 'Charlie', hash: 3 },
        { id: 104, name: 'David', hash: 0 }
    ];
    
    const largeTable = [
        { id: 101, value: 'Value A', hash: 1 },
        { id: 105, value: 'Value B', hash: 1 },
        { id: 102, value: 'Value C', hash: 2 },
        { id: 106, value: 'Value D', hash: 2 },
        { id: 103, value: 'Value E', hash: 3 },
        { id: 104, value: 'Value F', hash: 0 }
    ];
    
    // 动画状态
    let animationState = {
        phase: 'idle', // 'idle', 'build', 'probe'
        buildIndex: 0,
        probeIndex: 0,
        hashTable: [[], [], [], []], // 4个哈希桶
        matches: [],
        currentHash: null,
        animationId: null
    };
    
    // 颜色定义
    const colors = {
        smallTable: '#3498db',
        largeTable: '#e74c3c',
        hashTable: '#2ecc71',
        match: '#f39c12',
        current: '#9b59b6',
        text: '#2c3e50',
        border: '#bdc3c7'
    };
    
    // 绘制初始状态
    function drawInitialState() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        
        // 绘制标题
        ctx.fillStyle = colors.text;
        ctx.font = 'bold 20px Arial';
        ctx.fillText('Hash Join 算法演示', canvas.width/2 - 100, 30);
        
        // 绘制小表
        drawTable(smallTable, 50, 80, colors.smallTable, '小表 (Build Side)');
        
        // 绘制大表
        drawTable(largeTable, 550, 80, colors.largeTable, '大表 (Probe Side)');
        
        // 绘制哈希表结构
        drawHashTable(300, 350);
        
        // 绘制结果区域
        drawResultArea(50, 500);
        
        // 更新状态文本
        statusEl.textContent = '点击"开始演示"查看Hash Join算法过程';
    }
    
    // 绘制表格
    function drawTable(table, x, y, color, title) {
        // 绘制标题
        ctx.fillStyle = colors.text;
        ctx.font = 'bold 16px Arial';
        ctx.fillText(title, x, y - 10);
        
        // 绘制表头
        ctx.fillStyle = color;
        ctx.fillRect(x, y, 400, 30);
        ctx.fillStyle = 'white';
        ctx.font = '14px Arial';
        ctx.fillText('ID', x + 10, y + 20);
        ctx.fillText(table === smallTable ? 'Name' : 'Value', x + 100, y + 20);
        ctx.fillText('Hash', x + 300, y + 20);
        
        // 绘制表格行
        for (let i = 0; i < table.length; i++) {
            const rowY = y + 30 + i * 30;
            
            // 高亮当前处理的行
            if ((animationState.phase === 'build' && i === animationState.buildIndex && table === smallTable) ||
                (animationState.phase === 'probe' && i === animationState.probeIndex && table === largeTable)) {
                ctx.fillStyle = colors.current;
            } else {
                ctx.fillStyle = i % 2 === 0 ? '#f8f9fa' : '#e9ecef';
            }
            
            ctx.fillRect(x, rowY, 400, 30);
            ctx.strokeStyle = colors.border;
            ctx.strokeRect(x, rowY, 400, 30);
            
            ctx.fillStyle = colors.text;
            ctx.font = '14px Arial';
            ctx.fillText(table[i].id.toString(), x + 10, rowY + 20);
            ctx.fillText(table === smallTable ? table[i].name : table[i].value, x + 100, rowY + 20);
            ctx.fillText(table[i].hash.toString(), x + 300, rowY + 20);
        }
    }
    
    // 绘制哈希表
    function drawHashTable(x, y) {
        // 绘制标题
        ctx.fillStyle = colors.text;
        ctx.font = 'bold 16px Arial';
        ctx.fillText('哈希表', x, y - 10);
        
        // 绘制哈希桶
        for (let i = 0; i < 4; i++) {
            const bucketX = x + i * 150;
            
            // 绘制桶标题
            ctx.fillStyle = colors.hashTable;
            ctx.fillRect(bucketX, y, 120, 30);
            ctx.fillStyle = 'white';
            ctx.font = '14px Arial';
            ctx.fillText(`桶 ${i}`, bucketX + 50, y + 20);
            
            // 绘制桶内容
            const bucket = animationState.hashTable[i];
            for (let j = 0; j < bucket.length; j++) {
                const itemY = y + 30 + j * 30;
                ctx.fillStyle = j % 2 === 0 ? '#f8f9fa' : '#e9ecef';
                ctx.fillRect(bucketX, itemY, 120, 30);
                ctx.strokeStyle = colors.border;
                ctx.strokeRect(bucketX, itemY, 120, 30);
                
                ctx.fillStyle = colors.text;
                ctx.font = '14px Arial';
                ctx.fillText(`ID: ${bucket[j].id}`, bucketX + 10, itemY + 20);
            }
            
            // 如果是当前哈希桶,高亮显示
            if (animationState.currentHash === i) {
                ctx.strokeStyle = colors.current;
                ctx.lineWidth = 3;
                ctx.strokeRect(bucketX - 2, y - 2, 124, 32 + bucket.length * 30);
                ctx.lineWidth = 1;
            }
        }
    }
    
    // 绘制结果区域
    function drawResultArea(x, y) {
        // 绘制标题
        ctx.fillStyle = colors.text;
        ctx.font = 'bold 16px Arial';
        ctx.fillText('连接结果', x, y - 10);
        
        // 绘制表头
        ctx.fillStyle = colors.match;
        ctx.fillRect(x, y, 900, 30);
        ctx.fillStyle = 'white';
        ctx.font = '14px Arial';
        ctx.fillText('小表ID', x + 10, y + 20);
        ctx.fillText('小表Name', x + 100, y + 20);
        ctx.fillText('大表ID', x + 300, y + 20);
        ctx.fillText('大表Value', x + 400, y + 20);
        ctx.fillText('匹配哈希值', x + 700, y + 20);
        
        // 绘制匹配结果
        for (let i = 0; i < animationState.matches.length; i++) {
            const match = animationState.matches[i];
            const rowY = y + 30 + i * 30;
            
            ctx.fillStyle = i % 2 === 0 ? '#f8f9fa' : '#e9ecef';
            ctx.fillRect(x, rowY, 900, 30);
            ctx.strokeStyle = colors.border;
            ctx.strokeRect(x, rowY, 900, 30);
            
            ctx.fillStyle = colors.text;
            ctx.font = '14px Arial';
            ctx.fillText(match.small.id.toString(), x + 10, rowY + 20);
            ctx.fillText(match.small.name, x + 100, rowY + 20);
            ctx.fillText(match.large.id.toString(), x + 300, rowY + 20);
            ctx.fillText(match.large.value, x + 400, rowY + 20);
            ctx.fillText(match.hash.toString(), x + 700, rowY + 20);
        }
    }
    
    // 构建阶段动画
    function animateBuildPhase() {
        if (animationState.buildIndex >= smallTable.length) {
            // 构建阶段完成
            animationState.phase = 'idle';
            buildBtn.disabled = false;
            probeBtn.disabled = false;
            statusEl.textContent = '构建阶段完成!哈希表已建立。点击"探查阶段"继续。';
            return;
        }
        
        // 更新状态
        animationState.phase = 'build';
        const currentItem = smallTable[animationState.buildIndex];
        animationState.currentHash = currentItem.hash;
        
        // 将当前项添加到哈希表
        animationState.hashTable[currentItem.hash].push(currentItem);
        
        // 绘制当前状态
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        drawTable(smallTable, 50, 80, colors.smallTable, '小表 (Build Side)');
        drawTable(largeTable, 550, 80, colors.largeTable, '大表 (Probe Side)');
        drawHashTable(300, 350);
        drawResultArea(50, 500);
        
        // 更新状态文本
        statusEl.textContent = `构建阶段: 处理小表记录 ID=${currentItem.id}, 哈希值=${currentItem.hash}`;
        
        animationState.buildIndex++;
        
        // 继续动画
        animationState.animationId = setTimeout(animateBuildPhase, 1500);
    }
    
    // 探查阶段动画
    function animateProbePhase() {
        if (animationState.probeIndex >= largeTable.length) {
            // 探查阶段完成
            animationState.phase = 'idle';
            buildBtn.disabled = false;
            probeBtn.disabled = false;
            statusEl.textContent = 'Hash Join 完成!所有记录已处理。';
            return;
        }
        
        // 更新状态
        animationState.phase = 'probe';
        const currentItem = largeTable[animationState.probeIndex];
        animationState.currentHash = currentItem.hash;
        
        // 在哈希表中查找匹配项
        const hashBucket = animationState.hashTable[currentItem.hash];
        let foundMatch = false;
        
        for (const smallItem of hashBucket) {
            if (smallItem.id === currentItem.id) {
                // 找到匹配
                animationState.matches.push({
                    small: smallItem,
                    large: currentItem,
                    hash: currentItem.hash
                });
                foundMatch = true;
                break;
            }
        }
        
        // 绘制当前状态
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        drawTable(smallTable, 50, 80, colors.smallTable, '小表 (Build Side)');
        drawTable(largeTable, 550, 80, colors.largeTable, '大表 (Probe Side)');
        drawHashTable(300, 350);
        drawResultArea(50, 500);
        
        // 更新状态文本
        if (foundMatch) {
            statusEl.textContent = `探查阶段: 处理大表记录 ID=${currentItem.id}, 哈希值=${currentItem.hash} - 找到匹配!`;
        } else {
            statusEl.textContent = `探查阶段: 处理大表记录 ID=${currentItem.id}, 哈希值=${currentItem.hash} - 未找到匹配`;
        }
        
        animationState.probeIndex++;
        
        // 继续动画
        animationState.animationId = setTimeout(animateProbePhase, 1500);
    }
    
    // 重置动画
    function resetAnimation() {
        // 清除任何正在进行的动画
        if (animationState.animationId) {
            clearTimeout(animationState.animationId);
        }
        
        // 重置状态
        animationState = {
            phase: 'idle',
            buildIndex: 0,
            probeIndex: 0,
            hashTable: [[], [], [], []],
            matches: [],
            currentHash: null,
            animationId: null
        };
        
        // 启用/禁用按钮
        startBtn.disabled = false;
        buildBtn.disabled = true;
        probeBtn.disabled = true;
        
        // 重绘初始状态
        drawInitialState();
    }
    
    // 事件监听
    startBtn.addEventListener('click', function() {
        startBtn.disabled = true;
        buildBtn.disabled = false;
        statusEl.textContent = '准备开始构建阶段...';
    });
    
    buildBtn.addEventListener('click', function() {
        buildBtn.disabled = true;
        probeBtn.disabled = true;
        animateBuildPhase();
    });
    
    probeBtn.addEventListener('click', function() {
        buildBtn.disabled = true;
        probeBtn.disabled = true;
        animateProbePhase();
    });
    
    resetBtn.addEventListener('click', resetAnimation);
    
    // 初始化
    drawInitialState();
</script>
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论