万年历 购物 网址 日历 小说 | 三峰软件 天天财富 小游戏 视频推荐 小游戏
TxT小说阅读器
↓小说语音阅读,小说下载↓
一键清除系统垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放,产品展示↓
首页  日历2024  日历2025  日历2026  日历知识  | 每日头条  视频推荐  数码知识 两性话题 情感天地 心理咨询 旅游天地 | 明星娱乐 电视剧  职场天地  体育  娱乐 
日历软件  煮酒论史  历史 中国历史 世界历史 春秋战国 三国 唐朝 宋朝 明朝 清朝 哲学 厚黑学 心理学 | 文库大全  文库分类 
电影票房 娱乐圈 娱乐 弱智 火研 中华城市 仙家 六爻 佛门 风水 钓鱼 双色球 戒色 航空母舰 网球 乒乓球 足球 nba 象棋 体操
    
  知识库 -> 数码 -> U 盘用什么文件系统好? -> 正文阅读

[数码]U 盘用什么文件系统好?

[收藏本文] 【下载本文】
U 盘用什么文件系统好?FAT32? NTFS? exFAT?到底哪个好?求靠谱回答。
安利一个超级冷门文件格式:UDF。




作为同时在使用 Windows / OS X / Linux 三个系统的人,对文件系统这个事也头疼了很久,FAT32 / exFAT / NTFS的优缺点其他回答都说过了。要传输大文件肯定不会选FAT32,NTFS 在 Mac 又没有原生支持,一旦出现需要修复就必须找 Windows 系统修复,要不然无法挂载。exFAT 也用过一段时间,但是有掉全盘的隐患,而且不支持XP(这倒不是什么大问题)
再来说说 UDF, 这个格式最早是作为光盘的文件格式的,DVD / CD 什么的应该都是这个格式,所以对这个格式的兼容性也是各个操作系统几乎都会具备的,UDF 最早是个只读文件系统,在后来的版本更新中加入了对写入操作的支持,这也使得 UDF 可以作为闪存设备格式使用。
相比其他几个文件系统,UDF 其实没什么特别大的优势(所以会如此冷门),也是无日志的文件格式。总体来说兼容性和安全性应该跟 exFAT 差不多,在 Mac 上也不需要像 NTFS 那样再安装工具来重新挂载到读写模式。跟 exFAT 最大的区别大概就是一旦出错不会直接丢全盘数据。
关于 UDF 格式的系统兼容性在维基百科的 Universal Disk Format 词条可以找到, 我用的是2.01版本:


当然,UDF也会有一些微妙的坑, 由于实现细节上的一些差别,在默认情况下使用Windows系统格式化出来的UDF分区,在Mac OS只能被只读挂载。反之用Mac OS格式化的UDF分区,到了Windows上也会变成只读状态。 Github上有一个项目解决了这个问题,但是这个工具只能运行在Mac OS或Linux下:
format-udf?github.com/JElchison/format-udf
如果只有 Windows 系统但是想玩一下 UDF,也可以直接在Windows执行

format /FS:UDF /Q volume:

可以加 /R 参数来选择 UDF 版本,如 /R:2.01 , 可用的选项有 1.02、1.50、2.00、2.01、2.50。
抛开场景直接谈好坏是小孩子才做的事。这三个文件系统有非常多的不同点,但是对于普通的U盘普通用户来说,和他们最相关的应该是1)支持的文件系统大小; 2)支持的最大文件大小; 3)是否带有日志(对用户隐藏的特性); 当然用户可能还有自己特别的需求,比如文件数量、目录深度等,因为我觉得不是最常见需求,所以就不一一讨论了。我们主要针对上面三点进行讨论:
首先,支持的文件系统大小这一点其实可以不用考虑。因为即使是这三个里最小的Fat32文件系统也在最小512b blocksize的情况下能支持2TB的文件系统。

# truncate -s 2000g testfile
# mkfs.fat testfile 
mkfs.fat 4.1 (2017-01-24)
# mount -oloop testfile /mnt/tmp
# df -h /mnt/tmp
文件系统        容量  已用  可用 已用% 挂载点
/dev/loop0      2.0T   32K  2.0T    1% /mnt/tmp

而目前U盘还没有普及到1TB,常用的目前(2019年)还是32/64/128GB。所以这三个文件系统支持的文件系统大小我们就先不考虑了。我们来分别谈一下后面两个,以及其它一些东西。
NTFS:
从技术水平上讲这三个里肯定是NTFS最先进,如果你只是单纯的在装有windows系统的计算机上使用的话,用NTFS不失为一个好的选择。因为NTFS技术水平高,目前也是微软的主流稳定文件系统,有充足的技术支持和维护更新团队。
NTFS相比另外两个文件系统不仅支持大文件(远大于4G),而且它还是一个日志文件系统。对于即插设备来说可能经常要面临直接“掉电”(直接拔出)的风险,掉电对于非日志文件系统(如Fat文件系统)来说是风险很高的操作,会容易将文件系统置于“不一致”(corrupted)的状态,导致下次无法直接挂载使用。对于非日志文件系统,出现文件系统不一致状态后只能借助fsck类型的工具来进行修复,一般用户估计也不知道怎么进行这样的操作。即使知道怎么做,如果能修复好则能继续使用,如果修复不了则无法使用,也就是出现无法识别要求你重新格式化的错误。
NTFS作为一个日志文件系统,掉电后出现了不一致的状态后,在下一次使用文件系统时系统会通过log replay机制将文件系统自动恢复到一致状态。但是有人可能说了,那我用NTFS时也能碰到无法识别要求重新格式化的错误。首先排除硬件出现坏块的情况,如果确实是软件原因导致NTFS无法通过本身的log replay机制恢复到一致性状态,那么就要借助NTFS的fsck工具来进行手动修复。如果fsck工具也修不好,那这就是NTFS的bug,是软件就会有bug,NTFS也免不了。如果是正版用户,就可以联系微软,问他们是不是已知的bug,让他们想办法修复。毕竟你花了钱了,微软要承担因为自身产品bug而导致用户数据丢失的责任(我们也是对我们的客户承担这样的责任的)。如果是盗版,那就自己想办法吧。
关于NTFS的更多细节参数,可参考:
https://en.wikipedia.org/wiki/NTFS?en.wikipedia.org/wiki/NTFS
Fat32:
Fat32是一个很古老的文件系统,我们上面说NTFS是目前这三个里最先进的,那为什么有先进的还需要这些老旧的呢?因为先进的东西实现起来太复杂,对于很多小型设备或非微软系统的设备,往往不支持或不完全支持NTFS文件系统。Fat32作为小巧轻便的文件系统,其实现简单,内部实现清晰,往往被众多设备支持。比如我的PS4 slim就支持Fat32文件系统,但是不支持NTFS文件系统。
所以这就是Fat32存在的最大意义,适用性非常广,连很多非常小的嵌入式系统上都有Fat32的支持。你手持Fat32文件系统的U盘,基本上不用太担心插哪不能用的问题。
但是Fat32有一个最大的缺点,就是支持的最大单个文件只有2G,在使能LFS(Large-file support)特性的情况下也只能支持最大4G的文件。

# cd /path/to/fat32/
# fallocate -l 5g largefile
fallocate: fallocate 失败: File too large

这对于当今用户需求来说是非常不够的。现在向U盘里拷贝大于4G的文件(如压缩文件、高清视频文件等)的情况比比皆是,属于很多人的基本需求。所以这也成了用户考虑是否选择Fat32作为U盘文件系统的第一考虑点。
另外就是Fat32不支持日志功能。像我们上面说NTFS的时候提到了。没有日志功能的文件系统要面临更高的掉电后文件系统出现不一致的风险。出现不一致后只能用配套的fsck工具来尝试恢复到一致状态。如果fsck帮不了你,那像上面一样要么报bug,要么自己想办法。
更多Fat32的参数可参考:
https://en.wikipedia.org/wiki/File_Allocation_Table#FAT32?en.wikipedia.org/wiki/File_Allocation_Table#FAT32
exFat:
exFat可以认为是对Fat32的改善。它最有用的改善就是它消除了2或4G文件大小的限制,exFat支持的最大单个文件远超当前U盘的大小,所以用户不用担心上面Fat32不能存储大文件的问题。我的PS4就支持exFat,我因为最早使用Fat32时不能存储大文件的问题,所以将U盘的文件系统改成了exFat,现在可以在计算机和PS4上正常拷贝数据。
而且根据现今SD卡行业的标准,exfat文件系统已经成为32G以上容量的SD卡的标准文件系统被推荐使用,这一标准的推广也将增加exfat的使用范围:
Capacity (SD/SDHC/SDXC/SDUC) | SD Association?www.sdcard.org/developers/sd-standard-overview/capacity-sd-sdhc-sdxc-sduc/




exFat其实是一个很好的选择,毕竟大文件的支持还是很有用。但是要说有什么理由选Fat32不选exFat,那就是exFat的支持没有Fat32广。我们说了,越新的东西适用性往往越低,所以用户在考虑是否选用exFat替代Fat32前,要考虑一下自己的U盘的应用场景,这些场景是否都支持exFat。
同样,exFat也不是日志文件系统,存在和上述Fat32相同的问题。关于更多exFat的参数可参考:
https://en.wikipedia.org/wiki/ExFAT?en.wikipedia.org/wiki/ExFAT
结语:
当然除了NTFS、ExFat和Fat32以外,这个世界上还有数不清的文件系统类型以及各种变种。选择文件系统时要考虑自己的应用场景,我个人觉得如果你的场景选择NTFS完全没有问题,那你就选择NTFS,其次选择exFat,再其次选择Fat32。一切依照个人的需求而定,没有单独论用谁不用谁的说法。
最后,进一步延伸的一个关于日志文件系统和非日志文件系统话题的回答,感兴趣的可继续阅读下文:
为什么有人说exfat会丢数据呢?506 赞同 · 54 评论回答
说实话,目前真没有一个能满足各种相对常见的场合的,只能比烂,看你相对能接受哪一个的缺点:
FAT32:不能存放大于4GB的文件;大于32GB的U盘,在Windows下只能用第三方工具格式化。没有日志,不停用就直接拔出可能导致文件系统结构损坏从而丢失文件。
NTFS:Mac、Linux不兼容;不能直接用来做EFI电脑的启动盘——起码需要用第三方工具来分一个EFI分区来引导EFI的电脑(Windows内置的分区工具只能给U盘分一个区)。
exFAT:旧一点的操作系统都不支持;另外和FAT32一样没有日志;和NTFS一样不能用来启动EFI电脑。
其他Windows不支持的文件系统:这还用说么?Windows不支持就是最大的缺点。
补充一下:评论区有朋友说Linux/macOS可以装NTFS-3G读写NTFS分区,Windows可以装驱动读写BtrFS/EXT*分区。问题在于既然是U盘,你能保证需要用这个U盘的设备你都有安装软件的权限么?公司的服务器、锁bootloader不能root的手机、别人的电脑,都不是你想装驱动软件就能装的。
至于为什么FAT32/exFAT没有日志会丢文件,请参考这个回答:
木头龙:为什么有人说exfat会丢数据呢?141 赞同 · 24 评论回答


看你用什么系统了。
只用Linux的话用F2FS或ext4,只用MacOS的话用exFAT,只用Windows的话用NTFS。
注意Android也是Linux,F2FS跟ext4也是是多数安卓手机上文件系统的配置。
如果是需要多系统同时使用的话:
MacOS+Windows用 exFAT
MacOS+Linux用exFAT
MacOS+Linux+Windows用exFAT
因为MacOS只支持exFAT
Linux+Windows的话,用NTFS,ext4都可以。
Windows可以安装ext2文件系统的驱动然后直接识别ext2/ext3/ext4的盘,Linux也可以安装驱动直接识别NTFS的盘,不建议用exFAT是因为它无论对Linux还是对Windows而言都不是最优的,如果你没有苹果设备,一般来说没有理由使用exFAT。


如图可见MacOS(10.13.6)当中根本就没有NTFS的选项。
借用高赞回答的一句话“抛开场景直接谈好坏是小孩子才做的事。”
如果你的使用环境只有Windows,那么请尽管使用NTFS。
如果有Linux或Mac,请使用ExFAT。
最理想的U盘文件系统是F2FS,但windows系统是不适配的。
NTFS文件系统最稳定最适合windows系统,但日志的擦写要耗费闪存寿命,如果使用MLC以上的闪存类型,可以选择此文件系统。
FAT32文件系统较为古老可以兼容更旧的系统和设备但文件不能超过4GB,而且运行过程出意外文件系统很容易会坏掉。
exFat是FAT32的升级版,不兼容更旧的系统和设备。他可以放超过4GB的文件,运行过程出意外同FAT32。
很简单
首先 买一个大点的U盘
然后 分三个区
最后 分别格式化为 fat32 exfat NTFS(当然你开心的话可以搞更多分区)
恭喜你再也不用纠结了
——————————————————————
其实我本人一般只会分两个区
一个小点的FAT32分区和一个大点的exFAT分区
fat32里面就用来放pe系统启动文件 还会保留一点是为了便于连接Android系统。而我平时常用的iPhone是可以识别exFAT的
其实如果你手里只有苹果手机平时也不装系统之类的话 exFAT已经足以cover掉所以场景了,至于什么丢文件之类的,概率应该不大吧,我反正没遇到过。
类似问题以前研究过,不过是针对移动硬盘。简单的说考虑这几个点:
一、最大的文件。fat32单个文件不能超过4GB。exfat和ntfs的单个文件极限应该超过目前U盘大小。如果要复制大文件如高清电影要注意。
二、兼容性。通常U盘是用来交换文件的,要看对方是否支持。比如古老的winxp、机顶盒、mp3插卡音箱可能不支持ntfs.
三、性能。如果上面限制不存在,可以任意选择,不妨都试一下,格式化后往里面复制一些文件,对比哪个性能好就用哪一个。我的U盘测过ntfs性能最好。
不要用NTFS这类文件系统,这些文件系统会凭空产生碎片,导致性能很差,但并没有产生任何优势。
只要硬拔u盘就有可能丢文件,只跟写入文件的操作是否被中断有关,与日志没有半毛钱关系。
只要不是装系统用(必须用FAT32),一般就用exFAT。
我看有睿智说f2fs,但不要用,因为f2fs是专门给flash用的,非常底层,正常人一辈子都用不到。
我是Mac OS+Windows,用的exFAT,用了一年多还行。
但是一定要正常插拔U盘,否则容易丢失文件,我的是64GB,这次是在Win上拷贝了10GB大大小小文件之后再次使用的时候提示“需要格式化”,把我都整蒙圈了,换到Mac OS上也是无法识别(无法“恢复”),心想着有重要资料,直接格式化太可惜,常食用DG恢复了400多M资料,今天也是无奈,文件既然回不来,干脆格式化吧,反正也回不来了,然后在Mac OS上“抹掉”,显示卸载,最后居然失败,可是尝试打开U盘,资料居然全部回来了,但确实有3个文件是损坏的。
我猜测啊,应该是非法拔掉U盘时候导致的文件损坏,和exFat格式没啥关系,那么总结两点:
1、一定要正常插拔U盘;
2、U盘作用就是临时传输文件,它和硬盘存储数据用途不是一个性质。
exFAT最好,支持4g以上的大文件。
FAT32:很老的系统(例如XP和Windows 2000)都能兼容FAT32,因此FAT32适用于有和老系统交换文件的场合,缺点是不支持单个4GB以上的文件。
NTFS:普遍被Windows支持,在macOS里只读(写入需要安装第三方软件),在Linux里需要安装FUSE来读写,NTFS支持磁盘配额、文件系统加密以及数据压缩,在固态硬盘上支持TRIM指令
exFAT:Windows XP需要更新后才能支持,XP以后的Windows默认支持,macOS默认支持,可以存放单个大于4GB的文件
无日制文件系统最好,因为u盘是随意插拔的(这是usb接口设计原则)。
然后就是兼容性。
你所列的应该是exfat最佳,你没列出的也有不少。
兼容性优先,毕竟U盘要在不同设备上查看,XP,主板,影碟机,几乎没有不支持FAT32的。
大于4G的文件,只能用工具做切割了再存放。
另外,如果你的U盘只是拷个文档,拷贝安装包,使用不频繁,还是建议FAT32,走到哪都能用
有说法NTFS对优盘会有损耗。
并且NTFS格式在Mac上不支持,或者说不能原生支持,需要第三方应用。
而我恰好需要在windows和Mac环境下转换。
所以最后还是弄了exfat模式。
因为fat32的话在Mac下也能用,但是不支持大文件,而exfat解决了这个问题。
除非你用优盘的环境还有古老的不打补丁的xp,不然打了补丁的xp也是可以用exfat的。
所以其实还是exfat似乎好一些。
至于丢文件问题,哪个文件系统都会丢,多做备份才是王道。
我有次丢的文件正好在我备份的空档时间里,然后那个文件就这么神秘失踪了。
我当时真的欲哭无泪。
送礼物
还没有人送礼物,鼓励一下作者吧
F2FS
JFFS2
凡是英文全寫有"Flash"這個字的文件系統都可以
您好
U盘,看情况的,因为现在U盘价格并不贵
macos————exfat
windows———ntfs
linux————ext4
当然,ext4在win里面现在有驱动支持
没哪个更好,什么能用用什么,U盘是工具
windows维护比较多,单一U盘用ntfs
操作linux和windows比较多————ntfs,因为现在linux对ntfs支持ok,基本上U盘插上去,马上认出来
macos那只能用exfat,因为木有ntfs选择
仅供参考
送礼物
还没有人送礼物,鼓励一下作者吧
U盘大多采用flash作为存储介质,所以推荐使用f2fs作为U盘的文件系统。
NTFS、FAT32、exFAT一个比较概念性的东西,建议如果是移动硬盘就算则NTFS,如果是U盘等采用exFAT闪存为介质的存储设备,exFAT或者兼容性的FAT32比较好,下面小编就来给大家分析NTFS、FAT32、exFAT区别。如下图:


三种分区格式的区别
FAT32是在FAT16基础上发展而来,随着Windows 95 OSR2一起发布,可以被大多数操作系统支持,FAT32更有效地利用了硬盘空间,并且最大分区的上限已经达到了32GB,适合一般家庭使用。
NTFS可以支持的分区(如果采用动态磁盘则称为卷)大小可以达到2TB。而Win 2000中的FAT32支持分区的大小最大为32GB。NTFS可以比FAT32更有效地管理磁盘空间,最大限度地避免了磁盘空间的浪费。


NTFS格式
exFAT是Microsoft在Windows Embeded 5.0以上中引入的一种适合于闪存的文件系统,为了解决FAT32等不支持4G及其更大的文件而推出。对于闪存,NTFS文件系统不适合使用,exFAT更为适用。
从以上可以看出三大分区都在自己的优点和缺点,关键在于我们所需要用到的地方是哪里。然后再根据自己的需求选择适合自己的文件系统。
U盘最好用exfat,ntfs只支持Windows,在一些设备上用不了,而且exfat本来就是专为U盘而生的。
安全优先选NTFS
兼容优先选FAT
综合优先选exFAT
FAT32:
Windows平台的传统文件格式,Windows 95第二版首次引入,取代FAT16(支持文件最大容量2GB),兼容性很好,但缺点是对文件大小有限制,不支持超过4GB的文件。
所以,对于很多大型游戏、镜像文件、压缩包、视频,它是没有办法的。
另外,FAT32格式硬盘分区的最大容量为2TB,虽然U盘做不到,但是现在1xTB硬盘都有了,FAT32已经落后于时代,能不用就别用。
现在格式化U盘的时候,FAT32仍然是默认操作,Windows 10也是如此,更多是出于兼容性的保守考虑。
如果你是要将U盘制作为电脑启动盘的话,那么建议选择FAT32格式!
最后,如果遇到电脑数据误删等情况,欢迎留言给
@嗨格式数据恢复大师
免费获取专业数据恢复软件:
数据恢复官网:
其他数据恢复参考教程:
如果帮到了你,还请多多关注,多多点赞转发支持,在恢复过程中遇到任何问题,欢迎留言给我
@嗨格式数据恢复大师
首先我们需要确认U盘的定位
1、正常(绝大多数)用户的U盘是用来临时存储,临时交换文件的
也就意味着对于绝大多数用户来讲绝对不是永久存储的介质
所以,可以推荐exFat格式,完美兼容Windows/Linux/Mac
2、小众用户U盘上用来安装系统的,此时文件交换属性偏弱,基本上Fat32居多
3、特殊用途的u盘,比如在上面安装系统,此时偏离了文件交换的作用,根据需求来。。。
回顾
在FAT32文件系统分析(六)知道了在数据区主要组成部分为:3字节跳转指令、保留区、FAT区、引导代码、启动标记。
其中,保留区中包含了整个数据区中的基本信息,为此创建了两个结构体_bpb_t 与_fat32_hdr_t。

/**
 * FAT文件系统的BPB结构
 */
typedef struct _bpb_t {
    u8_t BS_jmpBoot[3];                 // 跳转指令
    u8_t BS_OEMName[8];                 // OEM名称
    u16_t BPB_BytsPerSec;               // 每扇区字节数
    u8_t BPB_SecPerClus;                // 每簇扇区数
    u16_t BPB_RsvdSecCnt;               // 保留区扇区数
    u8_t BPB_NumFATs;                   // FAT表项数
    u16_t BPB_RootEntCnt;               // 根目录项目数
    u16_t BPB_TotSec16;                 // 总的扇区数
    u8_t BPB_Media;                     // 媒体类型
    u16_t BPB_FATSz16;                  // FAT表项大小
    u16_t BPB_SecPerTrk;                // 每磁道扇区数
    u16_t BPB_NumHeads;                 // 磁头数
    u32_t BPB_HiddSec;                  // 隐藏扇区数
    u32_t BPB_TotSec32;                 // 总的扇区数
} bpb_t;

/**
 * BPB中的FAT32结构
 */
typedef struct _fat32_hdr_t {
    u32_t BPB_FATSz32;                  // FAT表的字节大小
    u16_t BPB_ExtFlags;                 // 扩展标记
    u16_t BPB_FSVer;                    // 版本号
    u32_t BPB_RootClus;                 // 根目录的簇号
    u16_t BPB_FsInfo;                   // fsInfo的扇区号
    u16_t BPB_BkBootSec;                // 备份扇区
    u8_t BPB_Reserved[12];
    u8_t BS_DrvNum;                     // 设备号
    u8_t BS_Reserved1;
    u8_t BS_BootSig;                    // 扩展标记
    u32_t BS_VolID;                     // 卷序列号
    u8_t BS_VolLab[11];                 // 卷标名称
    u8_t BS_FileSysType[8];             // 文件类型名称
} fat32_hdr_t;


这两个结构体包含的内容十分的详细,不过有许多信息在实际过程的时候使用的频率很低,且如果在每次调用disk_read_sector读取这一片扇区的时候,那必须要用一个缓冲区来存储这两个结构体,这样不仅会浪费内存而且效率不高。因此,有必要定义一个新的结构体来存储这_bpb_t 与fat32_hdr_t中的关键信息。

/**
 * xfat结构
 */
typedef struct _xfat_t {
    u32_t fat_start_sector;             // FAT表起始扇区
    u32_t fat_tbl_nr;                   // FAT表数量
    u32_t fat_tbl_sectors;              // 每个FAT表的扇区数
    u32_t total_sectors;                // 总扇区数

    xdisk_part_t * disk_part;           // 对应的分区信息
} xfat_t;
xfat_err_t xfat_open(xfat_t * xfat, xdisk_part_t * xdisk_part);


#include "xfat.h"
#include "xdisk.h"

extern u8_t temp_buffer[512];      // todo: 缓存优化

/**
 * 从dbr中解析出fat相关配置参数
 * @param dbr 读取的设备dbr
 * @return
 */
static xfat_err_t parse_fat_header (xfat_t * xfat, dbr_t * dbr) {
    xdisk_part_t * xdisk_part = xfat->disk_part;

    // 解析DBR参数,解析每个FAT表的扇区数,就是FAT表的字节大小
    xfat->fat_tbl_sectors = dbr->fat32.BPB_FATSz32;

    // 开启FAT镜像,只刷新一个FAT表
    if (dbr->fat32.BPB_ExtFlags & (1 << 7)) {
        u32_t table = dbr->fat32.BPB_ExtFlags & 0xF;
        //FAT表起始扇区 = 保留区扇区数+具体磁盘分区起始地址+FAT表的镜像数目*FAT表的字节大小。
        xfat->fat_start_sector = dbr->bpb.BPB_RsvdSecCnt + xdisk_part->start_sector + table * xfat->fat_tbl_sectors;
        xfat->fat_tbl_nr = 1;
    }
    // 禁用磁盘分区
    else 
    {
        //FAT表的起始扇区 = 保留扇区数+具体磁盘分区的起始地址
        xfat->fat_start_sector = dbr->bpb.BPB_RsvdSecCnt + xdisk_part->start_sector;
        //FAT表项数
        xfat->fat_tbl_nr = dbr->bpb.BPB_NumFATs;
    }

    xfat->total_sectors = dbr->bpb.BPB_TotSec32;

    return FS_ERR_OK;
}

/**
 * 初始化FAT项
 * @param xfat xfat结构
 * @param disk_part 分区结构
 * @return
 */
xfat_err_t xfat_open(xfat_t * xfat, xdisk_part_t * xdisk_part) {
    dbr_t * dbr = (dbr_t *)temp_buffer;
    xdisk_t * xdisk = xdisk_part->disk;
    xfat_err_t err;

    xfat->disk_part = xdisk_part;

    // 读取磁盘中第一个分区中的第一个扇区,就是获得第一个分区的DBR信息
    err = xdisk_read_sector(xdisk, (u8_t *) dbr, xdisk_part->start_sector, 1);
    if (err < 0) {
        return err;
    }

    // 解析dbr参数中的fat相关信息
    err = parse_fat_header(xfat, dbr);
    if (err < 0) {
        return err;
    }

    return FS_ERR_OK;
}

解析


FAT镜像:FAT镜像是一种文件系统镜像,它使用了FAT(File Allocation Table)文件系统,通常用于在计算机系统之间传输文件或备份数据。
数据区解析
上面我们解析了保留区、FAT区的内容,接下来就需要解析数据区中有什么内容了。


数据区的内容:存储目录和文件内容,其中FAT的数据是通过簇来实现的,注意的是数据开始的簇号为2.


每个簇包含一个扇区和多个扇区,在格式化磁盘的会要求选择分配单元的大小,这里的分配单元指的就是簇的大小,这里的大小都是2的N次幂。


在给文件分配簇的时候,即使该文件占用的空间少于1个簇的时候,都会分配一个簇。就是不足一簇分配整簇。
读取数据区中的一个文件


无论是在逻辑上还是在物理地址上,文件和目录都是按照”树形“结构组织。如上图所示,这其实就是windows中的文件包含关系。如果我们要找到mp3文件的话,首先知道它的上一层目录是啥,从上图可以知道mp3文件的上一层目录是根目录下的一个子目录。为此,我们需要了解如何读取根目录。




根目录本质上也是一个文件,可它的起始地址在哪里?在FAT32六的分析中,我们可知保留区中包含了数据区内容的信息。其实,根目录的起始地址可以在dbr结构体中存储了根目录的起始簇号。
读取根目录




根目录的起始簇号的内容都是目录项。啥是目录项?目录项里面记载着文件或者目录的信息,它里面包含了:文件名,扩展名,属性,保留区,创建时间,最后访问时间,写时间,数据起始簇号,文件字节大小。这里的属性其实在windows右键打开文件属性显示的内容几乎是一样的。这里文件显示的最大的大小是4B,转换为2的32次幂就是4G的大小,说明最大只能存储4G的文件。
在FAT32中文件与子目录信息都是用目录项来表示.如上图,如果根目录下包含的是一个文件,那么它对应的目录项中的数据起始簇号代表的就是文件数据的起始地址。
如果根目录下包含的内容也是一个目录(子目录),那么根目录下的目录项里的数据起始簇号,代表的就是子目录下的目录项(也就是子目录的信息)
根目录信息提取的代码实现
步骤:
定位根目录位置添加目录项定义解析并打印根目录的第一簇
在_xfat_t 结构体中添加簇与根目录的相关信息

/**
 * xfat结构
 */
typedef struct _xfat_t {
    u32_t fat_start_sector;             // FAT表起始扇区
    u32_t fat_tbl_nr;                   // FAT表数量
    u32_t fat_tbl_sectors;              // 每个FAT表的扇区数
    u32_t sec_per_cluster;              // 每簇占用的扇区数
    u32_t root_cluster;                 // 根目录的簇号
    u32_t cluster_byte_size;            // 每簇字节大小  每个簇字节大小= 簇占用的扇区数*每个扇区的字节大小
    u32_t total_sectors;                // 总扇区数

    u8_t * fat_buffer;             // FAT表项缓冲
    xdisk_part_t * disk_part;           // 对应的分区信息
} xfat_t;


获得根目录的fat相关信息

/**
 * 从dbr中解析出fat相关配置参数
 * @param dbr 读取的设备dbr
 * @return
 */
static xfat_err_t parse_fat_header (xfat_t * xfat, dbr_t * dbr) {
    xdisk_part_t * xdisk_part = xfat->disk_part;

    // 解析DBR参数,解析出有用的参数
    xfat->root_cluster = dbr->fat32.BPB_RootClus;   //根目录的起始簇号
    xfat->fat_tbl_sectors = dbr->fat32.BPB_FATSz32;

    // 如果禁止FAT镜像,只刷新一个FAT表
    // disk_part->start_block为该分区的绝对物理扇区号,所以不需要再加上Hidden_sector
    if (dbr->fat32.BPB_ExtFlags & (1 << 7)) {
        u32_t table = dbr->fat32.BPB_ExtFlags & 0xF;
        xfat->fat_start_sector = dbr->bpb.BPB_RsvdSecCnt + xdisk_part->start_sector + table * xfat->fat_tbl_sectors;
        xfat->fat_tbl_nr = 1;
    } else {
        xfat->fat_start_sector = dbr->bpb.BPB_RsvdSecCnt + xdisk_part->start_sector;
        xfat->fat_tbl_nr = dbr->bpb.BPB_NumFATs;
    }

    
    xfat->sec_per_cluster = dbr->bpb.BPB_SecPerClus; //每簇占用的扇区数
    xfat->total_sectors = dbr->bpb.BPB_TotSec32;     // 总的扇区数
    xfat->cluster_byte_size = xfat->sec_per_cluster * dbr->bpb.BPB_BytsPerSec; //每个簇占用的字节大小

    return FS_ERR_OK;
}

/**
 * 初始化FAT项
 * @param xfat xfat结构
 * @param disk_part 分区结构
 * @return
 */
xfat_err_t xfat_open(xfat_t * xfat, xdisk_part_t * xdisk_part) {
    dbr_t * dbr = (dbr_t *)temp_buffer;
    xdisk_t * xdisk = xdisk_part->disk;
    xfat_err_t err;

    xfat->disk_part = xdisk_part;

    // 读取dbr参数区
    err = xdisk_read_sector(xdisk, (u8_t *) dbr, xdisk_part->start_sector, 1);
    if (err < 0) {
        return err;
    }

    // 解析dbr参数中的fat相关信息
    err = parse_fat_header(xfat, dbr);
    if (err < 0) {
        return err;
    }

    // 先一次性全部读取FAT表: todo: 优化
    xfat->fat_buffer = (u8_t *)malloc(xfat->fat_tbl_sectors * xdisk->sector_size);
    err = xdisk_read_sector(xdisk, (u8_t *)xfat->fat_buffer, xfat->fat_start_sector, xfat->fat_tbl_sectors);
    if (err < 0) {
        return err;
    }

    return FS_ERR_OK;
}


上面主要关注的代码:
xfat->root_cluster = dbr->fat32.BPB_RootClus; //根目录的起始簇号
xfat->sec_per_cluster = dbr->bpb.BPB_SecPerClus; //每簇占用的扇区数
xfat->cluster_byte_size = xfat->sec_per_cluster * dbr->bpb.BPB_BytsPerSec; //每个簇占用的字节大小
读取根目录对应的簇
如何获得某一个簇的起始地址?


如上图,实际上根目录就存在于簇2中。当然我们设置API不能说仅仅是读取簇2,而且其它簇的地址的起始地址都能读到,为此这里我们假设根目录存在簇4当中。首先要知道簇4的起始地址,那么要知道簇2的起始在哪里。簇2的地址是在保留区获取,前面我为了方便将簇2的起始地址保留到了xfat结构体下的fat_start_sector。
所以簇4的起始地址=((簇4-簇2)*每个簇包含的扇区大小)+簇2的起始地址。 【这里的(簇4-簇2)代表簇号相互减】

#define xfat_get_disk(xfat)  ((xfat)->disk_part->disk) // 获取disk结构

/**
 * 获取指定簇号的第一个扇区编号
 * @param xfat xfat结构
 * @param cluster_no  簇号
 * @return 扇区号
 */
u32_t cluster_fist_sector(xfat_t *xfat, u32_t cluster_no)
{
    u32_t data_start_sector = xfat->fat_start_sector + xfat->fat_tbl_sectors * xfat->fat_tbl_nr;
    return data_start_sector + (cluster_no - 2) * xfat->sec_per_cluster;    // 前两个簇号保留
}

/**
 * 读取一个簇的内容到指定缓冲区
 * @param xfat xfat结构
 * @param buffer 数据存储的缓冲区
 * @param cluster 读取的起始簇号
 * @param count 读取的簇数量
 * @return
 */
xfat_err_t read_cluster(xfat_t *xfat, u8_t *buffer, u32_t cluster, u32_t count) {
  xfat_err_t err = 0;
  u32_t i = 0;
  u8_t * curr_buffer = buffer;
  u32_t curr_sector = cluster_fist_sector(xfat, cluster);

  for (i = 0; i < count; i++) 
  {
      err = xdisk_read_sector(xfat_get_disk(xfat), curr_buffer, curr_sector, xfat->sec_per_cluster);
      if (err < 0) 
      {
          return err;
      }

      curr_buffer += xfat->cluster_byte_size; //读取的簇大小
      curr_sector += xfat->sec_per_cluster; //读取的扇区数
  }

  return FS_ERR_OK;
}


err = xdisk_read_sector(xfat_get_disk(xfat), curr_buffer, curr_sector, xfat->sec_per_cluster);
假如根目录存在簇4中,可通过xdisk_read_sector这个API中,【curr_sector = cluster_fist_sector(xfat, cluster)】是要读取的簇4的起始地址,
xfat->sec_per_cluste代表要读取的扇区数
测试
前面提到目录项的内容包括:文件名字,文件创建时间,文件大小等。为此,我们需要定义目录项的结构体,以及目录项的一些宏定义。

#define CLUSTER_INVALID                 0x0FFFFFFF          // 无效的簇号

#define DIRITEM_NAME_FREE               0xE5                // 目录项空闲名标记
#define DIRITEM_NAME_END                0x00                // 目录项结束名标记

#define DIRITEM_ATTR_READ_ONLY          0x01                // 目录项属性:只读
#define DIRITEM_ATTR_HIDDEN             0x02                // 目录项属性:隐藏
#define DIRITEM_ATTR_SYSTEM             0x04                // 目录项属性:系统类型
#define DIRITEM_ATTR_VOLUME_ID          0x08                // 目录项属性:卷id
#define DIRITEM_ATTR_DIRECTORY          0x10                // 目录项属性:目录
#define DIRITEM_ATTR_ARCHIVE            0x20                // 目录项属性:归档
#define DIRITEM_ATTR_LONG_NAME          0x0F                // 目录项属性:长文件名

/**
 * FAT目录项的日期类型
 */
typedef struct _diritem_date_t {
    u16_t day : 5;                  // 日
    u16_t month : 4;                // 月
    u16_t year_from_1980 : 7;       // 年
} diritem_date_t;

/**
 * FAT目录项的时间类型
 */
typedef struct _diritem_time_t {
    u16_t second_2 : 5;             // 2秒
    u16_t minute : 6;               // 分
    u16_t hour : 5;                 // 时
} diritem_time_t;

/**
 * FAT目录项
 */
typedef struct _diritem_t {
    u8_t DIR_Name[8];                   // 文件名
    u8_t DIR_ExtName[3];                // 扩展名
    u8_t DIR_Attr;                      // 属性
    u8_t DIR_NTRes;
    u8_t DIR_CrtTimeTeenth;             // 创建时间的毫秒
    diritem_time_t DIR_CrtTime;         // 创建时间
    diritem_date_t DIR_CrtDate;         // 创建日期
    diritem_date_t DIR_LastAccDate;     // 最后访问日期
    u16_t DIR_FstClusHI;                // 簇号高16位
    diritem_time_t DIR_WrtTime;         // 修改时间
    diritem_date_t DIR_WrtDate;         // 修改时期
    u16_t DIR_FstClusL0;                // 簇号低16位
    u32_t DIR_FileSize;                 // 文件字节大小
} diritem_t;

主函数

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "xdisk.h"
#include "xfat.h"

extern xdisk_driver_t vdisk_driver;

const char * disk_path_test = "disk_test.img";
const char * disk_path = "disk.img";

static u32_t write_buffer[160*1024];
static u32_t read_buffer[160*1024];

xdisk_t disk;
xdisk_part_t disk_part;
xfat_t xfat;

// io测试,测试通过要注意关掉
int disk_io_test (void) 
{
  int err;
  xdisk_t disk_test;

  memset(read_buffer, 0, sizeof(read_buffer));

  err = xdisk_open(&disk_test, "vidsk_test", &vdisk_driver, (void *)disk_path_test);
  if (err) 
  {
      printf("open disk failed!\n");
      return -1;
  }

  err = xdisk_write_sector(&disk_test, (u8_t *)write_buffer, 0, 2);
  if (err) 
  {
      printf("disk write failed!\n");
      return -1;
  }

  err = xdisk_read_sector(&disk_test, (u8_t *)read_buffer, 0, 2);
  if (err) 
  {
      printf("disk read failed!\n");
      return -1;
  }

  err = memcmp((u8_t *)read_buffer, (u8_t *)write_buffer, disk_test.sector_size * 2);
  if (err != 0) 
  {
      printf("data no equal!\n");
      return -1;
  }

  err = xdisk_close(&disk_test);
  if (err) 
  {
      printf("disk close failed!\n");
      return -1;
  }

  printf("disk io test ok!\n");
  return 0;
}

int disk_part_test (void)
{
  u32_t count, i;
  xfat_err_t err = FS_ERR_OK;

  printf("partition read test...\n");

  err = xdisk_get_part_count(&disk, &count);
  if (err < 0) 
  {
      printf("partion count detect failed!\n");
      return err;
  }
  printf("partition count:%d\n", count);

  for (i = 0; i < count; i++) 
  {
    xdisk_part_t part;
    int err;

    err = xdisk_get_part(&disk, &part, i);
    if (err == -1) 
    {
      printf("read partion in failed:%d\n", i);
      return -1;
    }

        printf("no %d: start: %d, count: %d, capacity:%.0f M\n",
               i, part.start_sector, part.total_sector,
               part.total_sector * disk.sector_size / 1024 / 1024.0);
  }
  return 0;
}

void show_dir_info (diritem_t * diritem) {
    char file_name[12];
    u8_t attr = diritem->DIR_Attr;

    // name
    memset(file_name, 0, sizeof(file_name));
    memcpy(file_name, diritem->DIR_Name, 11);
    if (file_name[0] == 0x05) {
        file_name[0] = 0xE5;
    }
    printf("\n name: %s, ", file_name);

    // attr
    printf("\n\t");
    if (attr & DIRITEM_ATTR_READ_ONLY) {
        printf("readonly, ");
    }

    if (attr & DIRITEM_ATTR_HIDDEN) {
        printf("hidden, ");
    }

    if (attr & DIRITEM_ATTR_SYSTEM) {
        printf("system, ");
    }

    if (attr & DIRITEM_ATTR_DIRECTORY) {
        printf("directory, ");
    }

    if (attr & DIRITEM_ATTR_ARCHIVE) {
        printf("achinve, ");
    }

    // create time
    printf("\n\tcreate:%d-%d-%d, ", diritem->DIR_CrtDate.year_from_1980 + 1980,
            diritem->DIR_CrtDate.month, diritem->DIR_CrtDate.day);
    printf("\n\time:%d-%d-%d, ", diritem->DIR_CrtTime.hour, diritem->DIR_CrtTime.minute,
           diritem->DIR_CrtTime.second_2 * 2 + diritem->DIR_CrtTimeTeenth / 100);

    // last write time
    printf("\n\tlast write:%d-%d-%d, ", diritem->DIR_WrtDate.year_from_1980 + 1980,
           diritem->DIR_WrtDate.month, diritem->DIR_WrtDate.day);
    printf("\n\ttime:%d-%d-%d, ", diritem->DIR_WrtTime.hour,
           diritem->DIR_WrtTime.minute, diritem->DIR_WrtTime.second_2 * 2);

    // last acc time
    printf("\n\tlast acc:%d-%d-%d, ", diritem->DIR_LastAccDate.year_from_1980 + 1980,
           diritem->DIR_LastAccDate.month, diritem->DIR_LastAccDate.day);

    // size
    printf("\n\tsize %d kB, ", diritem->DIR_FileSize / 1024);
    printf("\n\tcluster %d, ", (diritem->DIR_FstClusHI << 16) | diritem->DIR_FstClusL0);

    printf("\n");
}

int fat_dir_test(void) {
    int err;
    u32_t curr_cluster;
    u8_t * culster_buffer;
    int index = 0;
    diritem_t * dir_item;
    u32_t j;

    printf("root dir read test...\n");

    culster_buffer = (u8_t *)malloc(xfat.cluster_byte_size);

    // 解析根目录所在的簇
    curr_cluster = xfat.root_cluster;
    while (is_cluster_valid(curr_cluster)) {
        err = read_cluster(&xfat, culster_buffer, curr_cluster, 1);
        if (err) {
            printf("read cluster %d failed\n", curr_cluster);
            return -1;
        }

        dir_item = (diritem_t *)culster_buffer;
        for (j = 0; j < xfat.cluster_byte_size / sizeof(diritem_t); j++) {
            u8_t  * name = (u8_t *)(dir_item[j].DIR_Name);
            if (name[0] == DIRITEM_NAME_FREE) {
                continue;
            } else if (name[0] == DIRITEM_NAME_END) {
                break;
            }

            index++;
            printf("no: %d, ", index);
            show_dir_info(&dir_item[j]);
        }

        err = get_next_cluster(&xfat, curr_cluster, &curr_cluster);
        if (err) {
            printf("get next cluster failed, current cluster %d\n", curr_cluster);
            return -1;
        }
    }

    return 0;
}

int main (void) 
{
  xfat_err_t err;
  int i;

  for (i = 0; i < sizeof(write_buffer) / sizeof(u32_t); i++)   {
      write_buffer[i] = i;
  }

  err = xdisk_open(&disk, "vidsk", &vdisk_driver, (void *)disk_path);
  if (err) 
  {
      printf("open disk failed!\n");
      return -1;
  }

  err = disk_part_test();
  if (err) return err;

  err = xdisk_get_part(&disk, &disk_part, 1);
  if (err < 0) 
  {
      printf("read partition info failed!\n");
      return -1;
  }

  err = xfat_open(&xfat, &disk_part);
  if (err < 0) 
  {
      return err;
  }

  err = fat_dir_test();
  if (err) return err;

  err = xdisk_close(&disk);
  if (err) 
  {
      printf("disk close failed!\n");
      return -1;
  }

  printf("Test End!\n");
  return 0;
}


主要关注的函数如下

/**
 * 检查指定簇是否可用,非占用或坏簇
 * @param cluster 待检查的簇
 * @return
 */
int is_cluster_valid(u32_t cluster) {
    cluster &= 0x0FFFFFFF;
    return (cluster < 0x0FFFFFF0) && (cluster >= 0x2);     // 值是否正确
}
void show_dir_info (diritem_t * diritem) {
    char file_name[12];
    u8_t attr = diritem->DIR_Attr;//获取文件的属性

    // 获取文件名或者子目录的名字
    memset(file_name, 0, sizeof(file_name));
    memcpy(file_name, diritem->DIR_Name, 11);
    //FAT32 文档里面规定规范
    if (file_name[0] == 0x05) {
        file_name[0] = 0xE5;
    }
    printf("\n name: %s, ", file_name);

    // 文件是否是只读属性
    printf("\n\t");
    if (attr & DIRITEM_ATTR_READ_ONLY) {
        printf("readonly, ");
    }
    //文件是否是隐藏属性
    if (attr & DIRITEM_ATTR_HIDDEN) {
        printf("hidden, ");
    }
    //文件是否是系统文件属性
    if (attr & DIRITEM_ATTR_SYSTEM) {
        printf("system, ");
    }
      //文件是否为目录
    if (attr & DIRITEM_ATTR_DIRECTORY) {
        printf("directory, ");
    }
      //文件是否是归档属性
    if (attr & DIRITEM_ATTR_ARCHIVE) {
        printf("achinve, ");
    }

    // 创建 time
    printf("\n\tcreate:%d-%d-%d, ", diritem->DIR_CrtDate.year_from_1980 + 1980,
            diritem->DIR_CrtDate.month, diritem->DIR_CrtDate.day);
    printf("\n\time:%d-%d-%d, ", diritem->DIR_CrtTime.hour, diritem->DIR_CrtTime.minute,
           diritem->DIR_CrtTime.second_2 * 2 + diritem->DIR_CrtTimeTeenth / 100);

    // 上一次读取时间
    printf("\n\tlast write:%d-%d-%d, ", diritem->DIR_WrtDate.year_from_1980 + 1980,
           diritem->DIR_WrtDate.month, diritem->DIR_WrtDate.day);
    printf("\n\ttime:%d-%d-%d, ", diritem->DIR_WrtTime.hour,
           diritem->DIR_WrtTime.minute, diritem->DIR_WrtTime.second_2 * 2);

    // 上一次访问时间
    printf("\n\tlast acc:%d-%d-%d, ", diritem->DIR_LastAccDate.year_from_1980 + 1980,
           diritem->DIR_LastAccDate.month, diritem->DIR_LastAccDate.day);

    // 文件大小
    printf("\n\tsize %d kB, ", diritem->DIR_FileSize / 1024);
    printf("\n\tcluster %d, ", (diritem->DIR_FstClusHI << 16) | diritem->DIR_FstClusL0);

    printf("\n");
}
/***********************************************************/
int fat_dir_test(void)
{
    int err;
    u32_t curr_cluster;
    u8_t * culster_buffer;
    int index = 0;
    diritem_t * dir_item;
    u32_t j;

    printf("root dir read test...\n");
    //根据保留区中记录的每个簇的字节大小在堆中分配一个数据用于后面的簇读取的信息保存。
    culster_buffer = (u8_t *)malloc(xfat.cluster_byte_size);

    // 解析根目录所在的簇,其实就是簇2,这里就是读取簇号
    curr_cluster = xfat.root_cluster;
    // 检查要读取的簇号是否可用
    while (is_cluster_valid(curr_cluster))
    {
        //读取簇2,将簇号2的一个簇大小的内容
        //就是读取簇2的目录项内容存在culster_buffer
        err = read_cluster(&xfat, culster_buffer, curr_cluster, 1);
        if (err) 
        {
            printf("read cluster %d failed\n", curr_cluster);
            return -1;
        }
        //将簇2读取的内容转换为目录项结构体
        dir_item = (diritem_t *)culster_buffer;
        //如何一个一个地读取每一个目录项?
        //也很简单就是将整簇大小除以目录项的大小
        for (j = 0; j < xfat.cluster_byte_size / sizeof(diritem_t); j++) 
        {
            //读取目录项下的文件或者子目录的名字
            u8_t  * name = (u8_t *)(dir_item[j].DIR_Name);
            if (name[0] == DIRITEM_NAME_FREE)
            {
                continue;
            } else if (name[0] == DIRITEM_NAME_END) 
            {
                break;
            }
            //统计目录项的数量
            index++;
            printf("no: %d, ", index);
            show_dir_info(&dir_item[j]);
        }

        err = get_next_cluster(&xfat, curr_cluster, &curr_cluster);
        if (err) 
        {
            printf("get next cluster failed, current cluster %d\n", curr_cluster);
            return -1;
        }
    }

    return 0;
}


测试


工程代码链接中地址:
github:https://github.com/WOLEN6914183/FAT32.git
百度网盘->码:fat3 https://pan.baidu.com/s/1B8IP61aozYquoDFcKE_J6Q
喜欢就微信扫描下面二维码关注我吧!
首选FAT32,次选exFAT,实在没办法了才NTFS。为何如此?因为NTFS是日志型的文件系统,它的工作特性极易导致U盘的物理损坏。
顺便说一句,现在许多U盘启动的PE三分区方案都是把可见分区分成NTFS,这是很大的错误。
[收藏本文] 【下载本文】
   数码 最新文章
中国的 App 怎么这么恶心啊,还是说安卓恶心
华为的对手真的从来不是小米吗?
U 盘用什么文件系统好?
小米周销量降至最低,你怎么看?
如何评价海马体证件照?
为什么这次小米14的国产化率那么高,在网上
年货节想买个手机给父母当年货,哪些手机适
如何看待OpenAI紧急发布的最新版本o3-mini开
2025 年蛇年央视春晚节目单公布,都有哪些精
中国手机巨头们为什么不用鸿蒙?
上一篇文章      下一篇文章      查看所有文章
加:2025-04-14 09:48:43  更:2025-04-15 10:18:43 
 
娱乐生活: 电影票房 娱乐圈 娱乐 弱智 火研 中华城市 印度 仙家 六爻 佛门 风水 古钱币交流专用 钓鱼 双色球 航空母舰 网球 乒乓球 中国女排 足球 nba 中超 跑步 象棋 体操 戒色 上海男科 80后
足球: 曼城 利物浦队 托特纳姆热刺 皇家马德里 尤文图斯 罗马 拉齐奥 米兰 里昂 巴黎圣日尔曼 曼联
  网站联系: qq:121756557 email:121756557@qq.com  知识库