为什么下载小电影时,进度经常会卡在99%?

进度条99%的魔咒源于技术原理和产品经理的设计。程序员无法精确预估进度,而产品经理有时会设置虚假进度条以改善用户体验。此外,资源块校验机制可能导致下载在最后阶段卡住,如迅雷的P2P协议在99.9%时进行文件校验。这种现象虽令人困扰,但也减少了错误和等待的不确定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

点击“开发者技术前线”,选择“星标????”让一部分开发者看到未来

来源:综合自网络

下载最怕什么,那绝对是进度条:99%。

这是一个充满魔力的数字,曾让我狂躁、焦虑,甚至激动得想砸键盘锤电脑扔手机。

比如下载学习资料或看动作大片,苦苦等待2小时,好不容易下到99%,以为2秒后就能享受大片的美妙,步入极乐世界。

结果半小时过去了,进度条死死卡在99%,任你千兆光纤,专线宽带,愣是一丝不动,稳如泰山。

再去检查路由器,狂按重启键,发现网络一切正常,网页秒开,唯独进度条上的99%永恒不变。

即使你重启电脑,重新打开下载软件,重新开始那99%的下载任务,它依旧还是99%,不增不减。

你不禁开始疑惑:为什么进度条总要卡在99%?为什么最后1%永远加载不动?

今天,要为大家破解这一千古谜题,揭开背后不可告人的真相。

 技术原理导致 

关于进度条99%的问题,得从它的诞生说起。

1896年,波兰经济学家Karol Adamiecki制作了一种名叫时间表的图,提出了早期的进度条概念,但是当时没有具体的应用。

等到1979年,这哥们Mitchell Model在他的博士论文中提出了进度条。

论文里他表示:进度条能在复杂的计算机环境中监视系统行为。

说白了就是:进度条能直观展现电脑在做什么,做到哪种程度。

正因为进度条能用最简单的图案和数字,表达电脑复杂的计算过程的特性,于是渐渐在各大操作系统流行起来,成为了电脑的经典标志之一。

但问题来了,人不是电脑更不是神,再牛X的程序员也无法预测电脑什么时候完成工作。

所以程序员开发出来的进度条,根本不能精准地反映出电脑情况,所谓的50%、80%、90%,仅是大概的数字,预测而已。

可以说你看到的进度条,和实际的进度是两个东西。

对于一些可定量的项目,进度条基本可以和实际相符,但不同的硬件资源和后台程序都会相互占据资源,计算机很难恒定分配运行,当你影片下载到 99% 时又打开了大型游戏,或者哪个小任务卡住了,就到了艰难的「1% 时刻」。

其实这种 1% 随时都在发生,但我们只对最后的 1% 印象深刻。

它有时候前面很快,后面很慢。

就像 U 盘复制文件,系统会根据文件数量和传输速度算好大概时间,但并不是每个百分比都执行相同的工作,因为每个文件大小都不一样,而最后 1% 可能因为还要验证文件、全盘扫描、整理数据等等,所以耗时也最久。

它也可能一直不快不慢,因为它整条都是假的。

虽然卡在 99% 的等待并不让人愉快,但也不得不承认,没有 0% 到 99%,我们的情绪会更焦躁,因为不知道尽头在哪里。

这就是进度条的厉害之处 —— 让你心甘情愿地等待。

 产品经理的恶意 

1985 年,卡内基梅隆大学人机交互研究所教授 Brad Myers 还是一位研究生,当时他就在论文里提出了这个观点。

只要看到进度条,人们就会感觉好点,它能让人放松,让人在等待时间去干点别的 —— 去花 5 分钟发个传真,或者干些在 1985 年的办公室会干的事。

虽然进度条由程序员开发,但真正设计进度条的人,是产品经理,包括功能、样式、图案等。

很多产品经理在设计进度条时,会特意要求程序员制作一个“虚假进度条”。

可能你会问,产品经理为什么无缘无故搞个假东西骗人呢?

给你们举个栗子,看完就懂了。

假设现在有2个相同下载速度的进度条,A和B,它们的下载完成时间都是100秒。

A是经过产品经理特殊调教的虚假进度条,它很套路,用了20秒下载到99%,最后1%花了80秒完成。

B是老实进度条,没被调教,10秒加载到10%,100秒100%,一分不差。

此时因为A前十秒加载到99%,而同样时间B却仅有10%,在强烈的对比下,大部分人会认为A比B更快,A比B更好用。

在优胜劣汰的规则下,用户肯定更多会选择A这种方式的软件,而产品经理想要留住用户,采用这种虚假进度条那是必须的。

现在明白了吧,有时候不是进度条不准,而是产品经理在搞事。

 下载完成后的块校验 

根据我多年的经验,导致这种情况发生的原因主要还是因为资源块校验的机制。

迅雷下载采用P2P协议加速,P2P的优点在于有多个数据来源。

每个下载过该文件的人,相当于一台服务器,当别人下载时自动在后台上传数据,提供速度。

说白了就是下的人越多,你所下载的资源能被拼凑时间越短。

但缺点同样也有,因为数据来源多,质量参差不齐外加上传不稳定,容易导致文件乱码出错。

因此迅雷定下了一个规则:在下载到99.9%的时候,会对文件进行块检验,如果某个块出现问题,无法重新下载,则会一直卡在当前进度不动。

下面这个图很好的说明了问题

兄弟你的形状怎么跟我们不一样啊?

如果哪天卡在99.9%不动,别傻楞去充白金会员,大声告诉你:钛金会员都没用!



— 完 —点这里????关注我,记得标星呀~
前线推出学习交流一定要备注:研究/工作方向+地点+学校/公司+昵称(如JAVA+上海扫码加小编微信,进群和大佬们零距离

END

后台回复“电子书” “资料” 领取一份干货,数百面试手册等你开发者技术前线 ,汇集技术前线快讯和关注行业趋势,大厂干货,是开发者经历和成长的优秀指南。历史推荐为什么 HTTPS 是安全的?
面试官问我:用了HTTPS就安全了吗?用了HTTPS会被抓包吗?我竟然回答不上来...
费解!为什么那么多人用“ji32k7au4a83”作密码?

好文点个在看吧!

#include "esp_system.h" #include "Game_Main.h" #include "driver/gpio.h" #include "esptim.h" #include "esp_random.h" #include "esp_mesh.h" #include "../Fun/videoplay.h" #include "../Fun/draw.h" #include "../Fun/RGBPanel.h" #include "../Fun/LanGame.h" #include "../Fun/GameDevice.h" #include "../Fun/JC24BDevice.h" #include "../Fun/video_control.h" #include "../Fun/LanWifiMesh.h" #include "../Fun/Button.h" #include "../Fun/countdown_timer.h" #include "../config.h" uint8_t GMAIN_GameState; static uint8_t GMAIN_TestColorIndex; static TIM_timing_t GMAIN_TimeObj1; static uint8_t GMAIN_BootClickCount = 0; static TIM_timing_t GMAIN_BootTimeObj; static uint8_t GMAIN_testTime = 3; CountdownTimer game_timer; // 全局倒计器 static TickType_t last_update = 0; // 全局显示更新间戳 // 显示区域定义 #define DISPLAY_WIDTH 256 // 屏幕宽度 #define DIGIT_WIDTH 45 // 单数字宽度 #define POINT_WIDTH 22 // 小数点宽度 // 总宽度计算:3整数位 + 小数点 + 2小数位 #define TOTAL_WIDTH (3*DIGIT_WIDTH + POINT_WIDTH + 2*DIGIT_WIDTH) #define START_X ((DISPLAY_WIDTH - TOTAL_WIDTH) / 2) // 居中显示 // 格式化数字为固定位数字符串(自动补零) void format_fixed_digits(int value, int digits, char* output) { for (int i = digits - 1; i >= 0; i--) { output[i] = (value % 10) + '0'; // 取最后一位 value /= 10; } output[digits] = '\0'; // 字符串终止符 } // void GMAIN_ChangeState(uint8_t state) // { // // 状态切换前处理当前状态 // switch (GMAIN_GameState) { // case GAME_M_TIME: // // 如果当前是倒计状态,暂停计器 // if (timer_get_state(&game_timer) == TIMER_RUNNING) { // timer_pause(&game_timer); // } // break; // } // GMAIN_GameState = state; // 更新状态 // switch (GMAIN_GameState) // { // case 0: // video_control_play(VIDEO_LOGO, VIDEO_PLAY_LOOP, 0); // break; // case 1: // video_control_play(VIDEO_WIN, VIDEO_PLAY_LOOP, esp_mesh_is_root() ? 10 : 12); // break; // case 2: // video_control_play(VIDEO_WIN_ASSIST, VIDEO_PLAY_LOOP, esp_mesh_is_root() ? 10 : 15); // break; // case 3: // video_control_play(VIDEO_WWJ_STATE_COIN_IN, VIDEO_PLAY_SINGLE, 0); // break; // case 4: // video_control_play(VIDEO_WWJ_STATE_COIN_OK, VIDEO_PLAY_LOOP, 0); // break; // case 5: // video_control_play(VIDEO_WWJ_STATE_GAMMING, VIDEO_PLAY_LOOP, 0); // break; // case GAME_M_WWJ_STATE_STOP: // video_control_play(VIDEO_WWJ_STATE_STOP, VIDEO_PLAY_SINGLE, 0); // break; // case GAME_M_TIME: // // 初始化倒计器(如果未初始化) // if (timer_get_state(&game_timer) == TIMER_STOPPED) { // timer_init(&game_timer, 10.0f); // 10秒倒计 // } // // 启动倒计 // timer_start(&game_timer); // // 初始化显示相关变量 // static TickType_t last_update = 0; // last_update = xTaskGetTickCount(); // 重置更新间 // break; // case GAME_M_SELF_TEST: // video_stop(); // GMAIN_TestColorIndex = 0; // TIM_TIMING_SET_1MS(GMAIN_TimeObj1, 0); // break; // } // } // void GMAIN_Run(void) // { // switch (GMAIN_GameState) // { // case GAME_M_IDLE: // break; // case GAME_M_WIN: // break; // case GAME_M_WIN_ASSIST: // break; // case GAME_M_WWJ_STATE_COIN_IN: // case GAME_M_WWJ_STATE_COIN_OK: // break; // case GAME_M_WWJ_STATE_GAMMING: // break; // case GAME_M_WWJ_STATE_STOP: // break; // case GAME_M_TIME: // 倒计状态处理 // // 获取剩余间 // int integer_seconds, centiseconds; // timer_get_remaining_split(&game_timer, &integer_seconds, &centiseconds); // TickType_t now = xTaskGetTickCount(); // if (now - last_update >= pdMS_TO_TICKS(10)) { // RGBPanel_FillScreen(0x0000); // RGBPanel_ShowValue(10, 0, integer_seconds, // RGBPanel_IMAGE_WWJ_STATE_RED_NUM0, // RGBPanel_NO_IMAGE); // RGBPanel_drawImageById(40, 0, RGBPanel_IMAGE_WWJ_STATE_RED_Point); // RGBPanel_ShowValue(50, 0, centiseconds, // RGBPanel_IMAGE_WWJ_STATE_RED_NUM0, // RGBPanel_NO_IMAGE); // RGBPanel_Update(); // last_update = now; // } // // 检查是否超 // if (timer_is_expired(&game_timer)) { // // 倒计结束处理 // GMAIN_ChangeState(GAME_M_WIN); // 切换到胜利状态 // } // break; // case GAME_M_SELF_TEST: // if (TIM_TIMING_RUN(GMAIN_TimeObj1)) // { // if (GMAIN_TestColorIndex >= 8) // { // JC24BDevice_SendLogoPlay(); // LanWifiMesh_SendLogoPlay(); // GMAIN_ChangeState(GAME_M_IDLE); // return; // } // uint8_t r = 0; // uint8_t g = 0; // uint8_t b = 0; // if ((GMAIN_TestColorIndex & 0x01) > 0) // { // r = 0xFF; // } // if ((GMAIN_TestColorIndex & 0x02) > 0) // { // g = 0xFF; // } // if ((GMAIN_TestColorIndex & 0x04) > 0) // { // b = 0xFF; // } // RGBPanel_FillScreenRGB888(r, g, b); // RGBPanel_Update(); // GMAIN_TestColorIndex++; // TIM_TIMING_SET_100MS(GMAIN_TimeObj1, GMAIN_testTime); // } // break; // } // if (IO_KeyPressed(KEY_BUTTON_TEST)) // { // GMAIN_ChangeState(GAME_M_WIN); // JC24BDevice_BroadcastWin(); // LanWifiMesh_BroadcastWin(); // } // if (TIM_TIMING_RUN(GMAIN_BootTimeObj)) // { // TIM_TIMING_SET_1S(GMAIN_BootTimeObj, 10000); // GMAIN_BootClickCount = 0; // } // if (IO_KeyPressed(KEY_BUTTON_BOOT) && g_config.meshType == CONFIG_MESH_TYPE_ROOT) // { // GMAIN_BootClickCount++; // if (GMAIN_BootClickCount >= 5) // { // config_set_mesh_type(CONFIG_MESH_TYPE_CHILD); // esp_restart(); // } // TIM_TIMING_SET_1S(GMAIN_BootTimeObj, 2); // } // } extern int g_wifi_rssi; static void GMAIN_ShowRootFlag(void); void GMAIN_FrameCall(uint32_t frameIndex) { switch (g_config.matrix_chain) { case 2: { switch (GMAIN_GameState) { case GAME_M_WWJ_STATE_COIN_IN: RGBPanel_ShowValue(54, 18, g_GameDeviceData.coinInCount, RGBPanel_IMAGE_WWJ_STATE_WHITE_NUM0, RGBPanel_NO_IMAGE); RGBPanel_ShowValue(92, 18, g_GameDeviceData.coinPlayCount, RGBPanel_IMAGE_WWJ_STATE_YELLOW_NUM0, RGBPanel_NO_IMAGE); break; } } break; case 3: { switch (GMAIN_GameState) { case GAME_M_WWJ_STATE_COIN_IN: RGBPanel_ShowValue(102, 18, g_GameDeviceData.coinInCount, RGBPanel_IMAGE_WWJ_STATE_WHITE_NUM0, RGBPanel_NO_IMAGE); RGBPanel_ShowValue(144, 18, g_GameDeviceData.coinPlayCount, RGBPanel_IMAGE_WWJ_STATE_YELLOW_NUM0, RGBPanel_NO_IMAGE); break; } } break; case 4: { switch (GMAIN_GameState) { case GAME_M_WWJ_STATE_COIN_IN: case GAME_M_WWJ_STATE_COIN_OK: RGBPanel_ShowValue(168, 18, g_GameDeviceData.coinInCount, RGBPanel_IMAGE_WWJ_STATE_WHITE_NUM0, RGBPanel_NO_IMAGE); RGBPanel_ShowValue(208, 18, g_GameDeviceData.coinPlayCount, RGBPanel_IMAGE_WWJ_STATE_YELLOW_NUM0, RGBPanel_NO_IMAGE); break; } } break; default: break; } if (esp_mesh_is_root()) { GMAIN_ShowRootFlag(); } else if (g_wifi_rssi <= -60 || g_wifi_rssi == 0) { RGBPanel_ShowWifiRssi(g_wifi_rssi); } } // 6*6 static const uint8_t GMAIN_mesh_root_flag[] = { 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, }; #define COLOR_YELLOW 0xFFE0 #define GRADIENT_STEPS (128) // 渐变的步数 static uint16_t gradientProgress = 0; typedef struct { uint8_t colorR; uint8_t colorG; uint8_t colorB; } color_t; static color_t startColor = { 31, 31, 31}; static color_t endColor = { 31, 0, 0}; static uint8_t colorIndex = 0; const uint8_t colorIndexBuff[7] = { 0x7, 0x05, 0x01, 0x03, 0x02, 0x06, 0x04}; uint16_t getGradientColor(void) { // 计算每个通道的渐变步长 uint16_t r = startColor.colorR + (endColor.colorR - startColor.colorR) * gradientProgress / (GRADIENT_STEPS - 1); uint16_t g = startColor.colorG + (endColor.colorG - startColor.colorG) * gradientProgress / (GRADIENT_STEPS - 1); uint16_t b = startColor.colorB + (endColor.colorB - startColor.colorB) * gradientProgress / (GRADIENT_STEPS - 1); // 增加渐变进度 if (++gradientProgress == (GRADIENT_STEPS - 1)) { gradientProgress = 0; startColor = endColor; colorIndex = (colorIndex + 1) % 7; endColor.colorR = ((colorIndexBuff[colorIndex] & 0x04) > 0 ? 31 : 0); endColor.colorG = ((colorIndexBuff[colorIndex] & 0x02) > 0 ? 31 : 0); endColor.colorB = ((colorIndexBuff[colorIndex] & 0x01) > 0 ? 31 : 0); } uint16_t color = (r << 11) | (g << 5) | b; // 返回RGB565格式的颜色值 return color; } static void GMAIN_ShowRootFlag(void) { uint16_t color = getGradientColor(); int16_t startX = 58; startX += (g_config.matrix_chain - 1) * 64; for (uint8_t y = 0; y < 6; y++) { for (uint8_t x = 0; x < 6; x++) { RGBPanel_drawPixel(startX + x, y, (GMAIN_mesh_root_flag[x + y * 6] ? color : 0)); } } } void GMAIN_VideoEndCall(void) { switch (GMAIN_GameState) { case 0: break; case 1: case 2: case 3: case 4: case 5: case GAME_M_SELF_TEST: break; } } void GMAIN_Init(void) { timer_init(&game_timer, 0); // 初始化为0秒 GMAIN_count_down(GAME_M_SELF_TEST); GMAIN_BootClickCount = 0; TIM_TIMING_SET_1S(GMAIN_BootTimeObj, 10000); } void GMAIN_count_down(uint8_t state) { // 状态切换前处理当前状态 switch (GMAIN_GameState) { case 2: // 如果当前是倒计状态,暂停计器 if (timer_get_state(&game_timer) == TIMER_RUNNING) { timer_stop(&game_timer); } break; } GMAIN_GameState = state; // 更新状态 switch (GMAIN_GameState) { case 0: video_control_play(VIDEO_LOGO, VIDEO_PLAY_LOOP, 0); break; case 1: video_control_play(VIDEO_COUNTDOWN_READY, VIDEO_PLAY_LOOP, 0); break; case 2: video_stop(); // 初始化倒计器(如果未初始化) if (timer_get_state(&game_timer) == TIMER_STOPPED) { timer_init(&game_timer, g_GameDeviceData.settingtime_value); } // 启动倒计 timer_start(&game_timer); // 初始化显示相关变量 static TickType_t last_update = 0; last_update = xTaskGetTickCount(); // 重置更新间 break; case 3: timer_pause(&game_timer); break; case 4: video_control_play(VIDEO_WIN , VIDEO_PLAY_LOOP, 0); break; case 5: video_control_play(VIDEO_LOSE , VIDEO_PLAY_LOOP, 0); break; case GAME_M_SELF_TEST: video_stop(); GMAIN_TestColorIndex = 0; TIM_TIMING_SET_1MS(GMAIN_TimeObj1, 0); break; } } void GMAIN_count_down_Run(void) { switch (GMAIN_GameState) { case 0: break; case 1: break; case 2: // 正计状态处理 // 获取已耗 int integer_seconds, centiseconds; timer_get_elapsed_split(&game_timer, &integer_seconds, &centiseconds); TickType_t now = xTaskGetTickCount(); if (now - last_update >= pdMS_TO_TICKS(10)) { RGBPanel_FillScreen(0x0000); char int_str[4]; char frac_str[3]; format_fixed_digits(integer_seconds, 3, int_str); format_fixed_digits(centiseconds, 2, frac_str); int current_x = START_X; int current_y = 5; for (int i = 0; i < 3; i++) { uint8_t digit = int_str[i] - '0'; RGBPanel_drawImageById(current_x, current_y, RGBPanel_IMAGE_WWJ_STATE_RED_NUM0 + digit); current_x += DIGIT_WIDTH; } RGBPanel_drawImageById(current_x, current_y+13, RGBPanel_IMAGE_WWJ_STATE_RED_Point); current_x += POINT_WIDTH; for (int i = 0; i < 2; i++) { uint8_t digit = frac_str[i] - '0'; RGBPanel_drawImageById(current_x, current_y, RGBPanel_IMAGE_WWJ_STATE_RED_NUM0 + digit); current_x += DIGIT_WIDTH; } } last_update = now; // 超判断 if (timer_is_expired(&game_timer)) { vTaskDelay(pdMS_TO_TICKS(20)); // 等待20ms确保显示完成 GMAIN_count_down(3); break; } RGBPanel_Update(); break; case 3: break; case 4: break; case 5: break; case GAME_M_SELF_TEST: if (TIM_TIMING_RUN(GMAIN_TimeObj1)) { if (GMAIN_TestColorIndex >= 8) { JC24BDevice_SendLogoPlay(); LanWifiMesh_SendLogoPlay(); GMAIN_count_down(GAME_M_IDLE); return; } uint8_t r = 0; uint8_t g = 0; uint8_t b = 0; if ((GMAIN_TestColorIndex & 0x01) > 0) { r = 0xFF; } if ((GMAIN_TestColorIndex & 0x02) > 0) { g = 0xFF; } if ((GMAIN_TestColorIndex & 0x04) > 0) { b = 0xFF; } RGBPanel_FillScreenRGB888(r, g, b); RGBPanel_Update(); GMAIN_TestColorIndex++; TIM_TIMING_SET_100MS(GMAIN_TimeObj1, GMAIN_testTime); } break; } // if (IO_KeyPressed(KEY_BUTTON_TEST)) // { // GMAIN_count_down(GAME_M_WIN); // JC24BDevice_BroadcastWin(); // LanWifiMesh_BroadcastWin(); // } if (TIM_TIMING_RUN(GMAIN_BootTimeObj)) { TIM_TIMING_SET_1S(GMAIN_BootTimeObj, 10000); GMAIN_BootClickCount = 0; } if (IO_KeyPressed(KEY_BUTTON_BOOT) && g_config.meshType == CONFIG_MESH_TYPE_ROOT) { GMAIN_BootClickCount++; if (GMAIN_BootClickCount >= 5) { config_set_mesh_type(CONFIG_MESH_TYPE_CHILD); esp_restart(); } TIM_TIMING_SET_1S(GMAIN_BootTimeObj, 2); } } 还是停留在9.98,我觉得是显示问题,帮我看看
最新发布
07-10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值