1. 简介

FatFS是一个适用于小型嵌入式系统的通用的FAT/exFAT文件系统模块。FatFS是使用ANSI C(C89)进行编写的,并且分出了磁盘I/O层,因此它是独立于平台的。不仅仅可以用于各种嵌入式平台,同样台用于Linux、android、MacOS甚至windows平台。
主要代码文件:

  • ff.h和ff.c是文件系统相关的代码。
  • diskio.h和diskio.c是磁盘I/O操作接口代码。
  • ffconf.h是文件系统版本配置代码,通过配置来开启关闭功能,可以根据需要提供更小的代码空间占用。

2. 用法

本文的内容及示例均是基于ff1.4版本进行的,并且在Windows平台上,基于虚拟磁盘和真实U盘进行测试和验证的。

2.1. 配置信息

以下设置为示例使用设置,为了更方便测试,没有启用Unicode及多分区功能。

  • #define FF_CODE_PAGE 936 // 如果想支持中文使用此配置
  • #define FF_LFN_UNICODE 0 // 配置是否使用unicode,0表示使用ANSI
  • #define FF_MULTI_PARTITION 0 // 是否启用多分区,默认不启用

2.2. 磁盘操作接口

// 此函数主要完成磁盘设备的打开(虚拟磁盘文件的创建打开)
int assign_drives();
// 此函数返回磁盘是否写保护状态
DSTATUS disk_status (BYTE pdrv);
// 此函数的作用是完成获取磁盘的容量、扇区大小(虚拟磁盘直接写死即可)
DSTATUS disk_initialize (BYTE pdrv);
// 此函数主要负责读取磁盘指定位置指定大小的内容
DRESULT disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count);
// 此函数主要负责往磁盘指定位置写入指定大小的内容
DRESULT disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count);
// 此函数主要根据cmd来返回指定内容,如扇区大小、容量、对齐扇区大小
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);

2.2.1. 真实磁盘操作接口实现


/*-----------------------------------------------------------------------*/
/* Initialize Disk Drive                                                 */
/*-----------------------------------------------------------------------*/DSTATUS disk_initialize (BYTE pdrv       /* Physical drive nmuber */
)
{DSTATUS sta;if (WaitForSingleObject(hMutex, 5000) != WAIT_OBJECT_0){return STA_NOINIT;}get_status(pdrv);sta = Stat[pdrv].status;ReleaseMutex(hMutex);return sta;
}/*-----------------------------------------------------------------------*/
/* Get Disk Status                                                       */
/*-----------------------------------------------------------------------*/DSTATUS disk_status (BYTE pdrv       /* Physical drive nmuber (0) */)
{DSTATUS sta;sta = Stat[pdrv].status;return sta;
}/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/DRESULT disk_read (BYTE pdrv,            /* Physical drive nmuber (0) */BYTE *buff,          /* Pointer to the data buffer to store read data */LBA_t sector,        /* Start sector number (LBA) */UINT count           /* Number of sectors to read */
)
{DWORD nc = 0, rnc;DSTATUS res;UINT uPartIdx = 0;UINT uPartCnt = (count+127) / 128;UINT uBuffSct = 128;if (Stat[pdrv].status & STA_NOINIT || WaitForSingleObject(hMutex, 3000) != WAIT_OBJECT_0) {return RES_NOTRDY;}nc = (DWORD)count * Stat[pdrv].sz_sector;memcpy(Buffer, buff, nc);for (; uPartIdx < uPartCnt; uPartIdx++){// 如果是最后1份,且是不完整的if (uPartIdx == (count / 128)){uBuffSct = count % 128;}if (!LogReadFlash(Stat[pdrv].h_drive, sector, uBuffSct, Buffer, 512)){res = RES_ERROR;}else{memcpy(buff + uPartIdx*128*512, Buffer, nc);res = RES_OK;}sector += uBuffSct;}ReleaseMutex(hMutex);return res;
}/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/DRESULT disk_write (BYTE pdrv,           /* Physical drive nmuber (0) */const BYTE *buff,    /* Pointer to the data to be written */LBA_t sector,        /* Start sector number (LBA) */UINT count           /* Number of sectors to write */
)
{DWORD nc = 0, rnc;LARGE_INTEGER ofs;DRESULT res;if (Stat[pdrv].status & STA_NOINIT || WaitForSingleObject(hMutex, 3000) != WAIT_OBJECT_0){return RES_NOTRDY;}res = RES_OK;if (Stat[pdrv].status & STA_PROTECT){res = RES_WRPRT;} else {nc = (DWORD)count * Stat[pdrv].sz_sector;if (nc > BUFSIZE) res = RES_PARERR;}/* Physical drives */if (pdrv >= MIN_READ_ONLY && pdrv <= MAX_READ_ONLY)res = RES_WRPRT;if (res == RES_OK){UINT uPartIdx = 0;UINT uPartCnt = (count+127) / 128;UINT uBuffSct = 128;memcpy(Buffer, buff, nc);for (; uPartIdx < uPartCnt; uPartIdx++){// 如果是最后1份,且是不完整的if (uPartIdx == (count / 128)){uBuffSct = count % 128;}if (!LogWriteFlash(Stat[pdrv].h_drive, sector, uBuffSct, Buffer, 512)){res = RES_ERROR;}sector += uBuffSct;}}ReleaseMutex(hMutex);return res;
}/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/DRESULT disk_ioctl (BYTE pdrv,       /* Physical drive nmuber (0) */BYTE ctrl,       /* Control code */void *buff        /* Buffer to send/receive data */
)
{DRESULT res;if ((Stat[pdrv].status & STA_NOINIT)){return RES_NOTRDY;}res = RES_PARERR;switch (ctrl){case CTRL_SYNC:           /* Nothing to do */res = RES_OK;break;case GET_SECTOR_COUNT:   /* Get number of sectors on the drive */*(LBA_t*)buff = Stat[pdrv].n_sectors;res = RES_OK;break;case GET_SECTOR_SIZE: /* Get size of sector for generic read/write */*(WORD*)buff = Stat[pdrv].sz_sector;res = RES_OK;break;case GET_BLOCK_SIZE:    /* Get internal block size in unit of sector */*(DWORD*)buff = SZ_BLOCK;res = RES_OK;break;case 200:              /* Load disk image file to the RAM disk (drive 0) */{HANDLE h;DWORD br;if (pdrv == 0) {h = CreateFileW(buff, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0);if (h != INVALID_HANDLE_VALUE){if (ReadFile(h, RamDisk, SZ_RAMDISK * 1024 * 1024, &br, 0)) {res = RES_OK;}CloseHandle(h);}}}break;}return res;
}

2.2.2. 虚拟磁盘操作接口实现

int get_status (BYTE pdrv
)
{volatile STAT *stat = &Stat[pdrv];/* Get drive size */stat->status = STA_NOINIT;stat->sz_sector = 512;stat->n_sectors = 15761504;if (stat->sz_sector < FF_MIN_SS || stat->sz_sector > FF_MAX_SS) return 0;/* Get write protect status */stat->status = 0;if (pdrv >= MIN_READ_ONLY && pdrv <= MAX_READ_ONLY) stat->status = STA_PROTECT;return 1;
}/*--------------------------------------------------------------------------Public Functions---------------------------------------------------------------------------*/
#define FILENAME "e:\\Virtual.img"/*-----------------------------------------------------------------------*/
/* Initialize Windows disk accesss layer                                 */
/*-----------------------------------------------------------------------*/int assign_drives (void)
{BYTE pdrv = 0;HANDLE h;DWORD dwRet = 0;OSVERSIONINFO vinfo = { sizeof (OSVERSIONINFO) };hMutex = CreateMutex(0, 0, 0);if (hMutex == INVALID_HANDLE_VALUE) return 0;Buffer = VirtualAlloc(0, BUFSIZE, MEM_COMMIT, PAGE_READWRITE);if (!Buffer) return 0;RamDisk = VirtualAlloc(0, SZ_RAMDISK * 0x100000, MEM_COMMIT, PAGE_READWRITE);if (!RamDisk)return 0;Stat[0].h_drive = fopen(FILENAME, "w+");if (!Stat[0].h_drive) {printf(__FILE__":%d\n", __LINE__);printf(FILENAME" open err!\n");return RES_NOTRDY;}wprintf(L"PD#%u <== %s", 0, "virtual disk");if (get_status(0)){wprintf(L" (%uMB, %u bytes * %I64u sectors)\n", (UINT)((LONGLONG)Stat[pdrv].sz_sector * Stat[pdrv].n_sectors / 1024 / 1024), Stat[pdrv].sz_sector, (QWORD)Stat[pdrv].n_sectors);} else{wprintf(L" (Not Ready)\n");}Drives = 0;return 0;
}/*-----------------------------------------------------------------------*/
/* Initialize Disk Drive                                                 */
/*-----------------------------------------------------------------------*/DSTATUS disk_initialize (BYTE pdrv       /* Physical drive nmuber */
)
{DSTATUS sta;if (WaitForSingleObject(hMutex, 5000) != WAIT_OBJECT_0){return STA_NOINIT;}get_status(pdrv);sta = Stat[pdrv].status;ReleaseMutex(hMutex);return sta;
}/*-----------------------------------------------------------------------*/
/* Get Disk Status                                                       */
/*-----------------------------------------------------------------------*/DSTATUS disk_status (BYTE pdrv       /* Physical drive nmuber (0) */)
{DSTATUS sta;sta = Stat[pdrv].status;return sta;
}/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/DRESULT disk_read (BYTE pdrv,            /* Physical drive nmuber (0) */BYTE *buff,          /* Pointer to the data buffer to store read data */LBA_t sector,        /* Start sector number (LBA) */UINT count           /* Number of sectors to read */
)
{DWORD nc, rnc;LARGE_INTEGER ofs;DSTATUS res = 0;DWORD ulSeek = 0;unsigned long nRead = 0;int nSeekRes = 0;ulSeek = sector * 512;nSeekRes = fseek(Stat[pdrv].h_drive, ulSeek, SEEK_SET);nRead = fread(buff, 512, count, Stat[pdrv].h_drive);if (nSeekRes || nRead == 0) {printf(__FILE__":%d\n", __LINE__);printf("read disk err!\n");return RES_ERROR;}return res;
}/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/DRESULT disk_write (BYTE pdrv,           /* Physical drive nmuber (0) */const BYTE *buff,    /* Pointer to the data to be written */LBA_t sector,        /* Start sector number (LBA) */UINT count           /* Number of sectors to write */
)
{DWORD nc = 0, rnc;LARGE_INTEGER ofs;DRESULT res;DWORD ulSeek = 0;unsigned long nRead = 0;int nSeekRes = 0;ulSeek = sector * 512;res = RES_OK;if (Stat[pdrv].status & STA_PROTECT){res = RES_WRPRT;} else {nc = (DWORD)count * Stat[pdrv].sz_sector;if (nc > BUFSIZE) res = RES_PARERR;}nSeekRes = fseek(Stat[pdrv].h_drive, ulSeek, SEEK_SET);nRead = fwrite(buff, 512, count, Stat[pdrv].h_drive);if (nSeekRes || nRead == 0){printf(__FILE__":%d\n", __LINE__);printf("read disk err!\n");return RES_ERROR;}return res;
}/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/DRESULT disk_ioctl (BYTE pdrv,       /* Physical drive nmuber (0) */BYTE ctrl,       /* Control code */void *buff        /* Buffer to send/receive data */
)
{DRESULT res;res = RES_PARERR;switch (ctrl){case CTRL_SYNC:            /* Nothing to do */res = RES_OK;break;case GET_SECTOR_COUNT:   /* Get number of sectors on the drive */*(LBA_t*)buff = Stat[pdrv].n_sectors;res = RES_OK;break;case GET_SECTOR_SIZE: /* Get size of sector for generic read/write */*(WORD*)buff = Stat[pdrv].sz_sector;res = RES_OK;break;case GET_BLOCK_SIZE:    /* Get internal block size in unit of sector */*(DWORD*)buff = SZ_BLOCK;res = RES_OK;break;case 200:              /* Load disk image file to the RAM disk (drive 0) */{HANDLE h;DWORD br;if (pdrv == 0) {h = CreateFileW(buff, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0);if (h != INVALID_HANDLE_VALUE){if (ReadFile(h, RamDisk, SZ_RAMDISK * 1024 * 1024, &br, 0)) {res = RES_OK;}CloseHandle(h);}}}break;}return res;
}

2.3. FatFS常用接口

FatFS应用接口,主要分四大类:

2.3.1. 文件操作接口

  • f_open -创建或打开文件,可设置不同读写属性。
  • f_close - 关闭打开的文件。- f_read - 读取文件。
  • f_write - 写文件。
  • f_lseek - 移动当前文件操作指针到指定位置。
  • f_sync - 将缓存立即写入设备,需要配置CTRL_SYNC控制码。
  • - f_forward - 带回调函数的读取文件内容接口。
  • f_tell - 获取当前文件指针。
  • f_eof - 测试是否到文件末尾。
  • f_size - 获取文件大小。
  • f_error - 测试是否出现错误。

2.3.2. 目录操作接口

  • f_opendir - 打开指定目录。
  • f_closedir - 关闭指定目录。
  • f_readdir - 读取目录的内容,可以判断是否为目录以及相关属性等。
  • f_findfirst - 开始遍历目录,目录可以使用通配符描述。
  • f_findnext - 读取下一个目标。

2.3.3. 文件和目录管理接口

  • f_stat - 检测文件或子目录是否存在。
  • f_unlink - 删除文件或子目录。
  • f_rename - 重命名或移动文件或子目录。
  • f_chmod - 改变文件或子目录属性。
  • f_utime - 改变文件或子目录时间戳。
  • f_mkdir - 创建子目录。
  • f_chdir - 改变当前目录。
  • f_chdrive - 改变当前驱动器号。
  • f_getcwd - 获取当前驱动器号。

2.3.4. 卷管理和系统配置

  • f_mount - 注册和反注册指定卷的工作区,其作用主要是完成卷(文件系统相关参数)和指定磁盘的对应关系。FatFS默认是使用磁盘号与文件系统相对应,也可以使用自定义卷名与文件系统相关联。另外此函数还会调用磁盘初始化接口来完成磁盘的打开操作。
  • f_mkfs - 对指定的卷(根据磁盘名或卷名)来进行指定类型的格式化操作。
  • f_fdisk - 对指定的物理设备创建多个分区。
  • f_getfree - 获取指定卷的空闲空间。
  • f_getlabel - 获取卷标。
  • f_setlabel - 设备卷票。
  • f_setcp - 设置活动代码页。

3. 示例

示例完全基于ff1.4版本,只修改磁盘I/O接口,完全不修改ff.c代码。

3.1. 格式化

  1. 初始化
    调用assign_drives函数完成初始化,即打开指定设备(物理设备或逻辑设备)。
  2. 挂载
    f_mount用来挂载,其作用主要是完成卷(文件系统相关参数)和指定磁盘的对应关系。
  3. 设置参数
    n_fat默认为1,Windows下通用设置为2。align对齐默认是256。m_root默认为0。au_size默认根据容量计算。
  4. 格式化
    FatFs有一个全局变量FatFs[FF_VOLUMES],FF_VOLUMES默认为8。所以FatFS默认只能挂载8个卷。如果想同时支持更多盘,FF_VOLUMES宏需要相应的修改。
  5. 代码
 // 3就是设备号,在FatFS中也被称为逻辑驱动器号,默认不填写则为0TCHAR filePath[] = "3:\\hello.txt";MKFS_PARM opt = {0};FATFS fsEx = {0};FRESULT fRet = FR_OK;assign_drives('j', 3);   f_mount(&fsEx, "3:", 0);opt.fmt = FM_EXFAT;opt.n_fat = 2;fRet = f_mkfs("3:", &opt, workBuff, sizeof(workBuff));

3.2. 读写文件

  1. 初始化
    调用assign_drives函数完成初始化,即打开指定设备(物理设备或逻辑设备)。
  2. 挂载
    f_mount用来挂载,其作用主要是完成卷(文件系统相关参数)和指定磁盘的对应关系。
  3. 打开文件
    打开指定文件,返回一个文件属性列表变量用来写读关闭文件。
  4. 写文件
    和Windows写文件差不多,详细示例
  5. 读文件
    和Windows读文件操作类似,详细示例
  6. 关闭文件
    关闭文件的一个重要作用是保证将缓存写入设备(直接设备或虚拟设备)
  7. 示例
FIL fil = {0};FRESULT res = FR_OK;UINT bw = 0;BYTE buff[1024] = {0};res = f_open(&fil, pFilePath, FA_CREATE_NEW | FA_WRITE);if (res) {printf("f_open hello.txt err\n");return res;}f_write(&fil, "Hello, World!\n", 15, &bw);if (bw != 15) {printf("f_write hello.txt err\n");f_close(&fil);return 1;}f_close(&fil);res = f_open(&fil, pFilePath, FA_READ | FA_OPEN_EXISTING);if (res){printf("f_open hello.txt err\n");return res;}memset(buff, 0, sizeof(buff));res = f_read(&fil, buff, 15, &bw);if (res) {printf("f_read err\n");f_close(&fil);return res;}printf("buff: %s", buff);

3.3. 读写目录

  1. 初始化
    调用assign_drives函数完成初始化,即打开指定设备(物理设备或逻辑设备)。
  2. 挂载
    f_mount用来挂载,其作用主要是完成卷(文件系统相关参数)和指定磁盘的对应关系。
  3. 创建目录
    f_mkdir的参数和Windows类似,只需要目录名,默认当前目录。
  4. 打开目录
    f_opendir打开指定目录,返回一个目录标识供读目录使用。
  5. 读取目录
    读取目录信息,包括是否目录、各种目录属性。
  6. 关闭目录
  7. 创建打开示例
UINT bw = 0;FIL fil = {0};TCHAR buff[1024] = {0};DIR dir = {0};FRESULT res = FR_OK;res = f_mkdir("3:\\ABC");if (res) {printf("f_open hello.txt err\n");return res;}res = f_opendir(&dir, "3:\\ABC");if (res) {printf("f_open hello.txt err\n");return res;}res = f_mkdir("3:\\ABC\\A");if (res) {printf("f_open hello.txt err\n");return res;}res = f_opendir(&dir, "3:\\ABC\\A");if (res) {printf("f_open hello.txt err\n");return res;}res = f_mkdir("3:\\ABC\\B");if (res) {printf("f_open hello.txt err\n");return res;}res = f_opendir(&dir, "3:\\ABC\\B");if (res) {printf("f_open hello.txt err\n");return res;}res = f_mkdir("3:\\ABC\\B\\C");if (res) {printf("f_open hello.txt err\n");return res;}res = f_opendir(&dir, "3:\\ABC\\B\\C");if (res) {printf("f_open hello.txt err\n");return res;}f_closedir(&dir);res = f_open(&fil, "3:/ABC/B/a.txt", FA_CREATE_NEW | FA_WRITE);if (res) {printf("f_open hello.txt err\n");return res;}f_write(&fil, "Mike say:Hello, World!\n", 24, &bw);if (bw != 24) {printf("f_write hello.txt err\n");f_close(&fil);return 1;}f_close(&fil);res = f_open(&fil, "3:/ABC/B/a.txt", FA_READ | FA_OPEN_EXISTING);if (res){printf("f_open hello.txt err\n");return res;}memset(buff, 0, sizeof(buff));res = f_read(&fil, buff, 24, &bw);if (res) {printf("f_read err\n");f_close(&fil);return res;}printf("buff: %s", buff);f_close(&fil);
  1. 遍历目录示例
FRESULT scan_files (CHAR* path       /* Pointer to the path name working buffer */
)
{DIR dir;FRESULT res;int i;if ((res = f_opendir(&dir, path)) == FR_OK) {i = strlen(path);while (((res = f_readdir(&dir, &Finfo)) == FR_OK) && Finfo.fname[0]){if (Finfo.fattrib & AM_DIR) {AccDirs++;*(path+i) = L'/';strcpy(path+i+1, Finfo.fname);printf("%s\n", path);res = scan_files(path);*(path+i) = L'\0';if (res != FR_OK) break;} else{AccFiles++;AccSize += Finfo.fsize;printf("%s\n", Finfo.fname);}}f_closedir(&dir);}return res;
}

3.4. 多分区

多分区需要启用宏FF_MULTI_PARTITION。

int TestMultiParition()
{#if FF_MULTI_PARTITIONFRESULT fRet = FR_OK;MKFS_PARM opt = {0};LBA_t plist[] = {3000*2048, 100};  /* Divide the drive into two partitions *//* {0x10000000, 100}; 256M sectors for 1st partition and left all for 2nd partition *//* {20, 20, 20, 0}; 20% for 3 partitions each and remaing space is left not allocated */f_fdisk(0, plist, workBuff);                    /* Divide physical drive 0 */opt.fmt = FM_FAT32;opt.n_fat = 2;fRet = f_mkfs("0:", &opt, workBuff, sizeof(workBuff)); /* Create FAT volume on the logical drive 0 */fRet = f_mkfs("1:", &opt, workBuff, sizeof(workBuff)); /* Create FAT volume on the logical drive 1 */
#endifreturn 0;
}

4. 查看虚拟盘信息

我们可以通过虚拟盘将文件系统及相关信息写入到Windows的文件中,用文件来模拟FatFS的移植。创建format.img文件,将diskio的相关接口均实现到此文件中。并且通过DiskGunius工具提供的打开虚拟磁盘文件,可以查看相关信息。DiskGunius支持FAT32和exFAT文件系统。

5. 注意

  • 多盘需要考虑修改FatFs数组的大小。
  • 读写文件时,需要考虑内存使用情况。

6. 代码

Windows下基于文件仿真FatFS
基于Windows下的真实U盘的FatFS

FatFS使用介绍及示例相关推荐

  1. tf.cast()函数介绍和示例

    tf.cast()函数介绍和示例 tf.cast(x, dtype, name=None) 释义:数据类型转换 x,输入张量 dtype,转换数据类型 name,名称 示例: import tenso ...

  2. numpy.random.rand(),numpy.random.randn(),numpy.random.normal()函数介绍和示例

    numpy.random.rand(),numpy.random.randn(),numpy.random.normal()函数介绍和示例 1. numpy.random.rand() 均匀分布 范围 ...

  3. mysql创建表分区详细介绍及示例

    mysql创建表分区详细介绍及示例 1. 基本概念 1.1 什么是表分区? 1.2 表分区与分表的区别 1.3 表分区有什么好处? 1.4 分区表的限制因素 2. 如何判断当前MySQL是否支持分区? ...

  4. MISRA-C 2012修改指南介绍及示例

    导言 随着对汽车电子嵌入式软件的重视程度不断提高,为减少软件的缺陷,汽车工业软件可靠性协会MISRA(Motor Industry Software Reliability Association)提 ...

  5. 6、Druid的Roll up详细介绍及示例

    Apache Druid 系列文章 1.Druid(Imply-3.0.4)介绍及部署(centos6.10).验证 2.Druid的入门示例(使用三种不同的方式摄入数据和提交任务) 3.Druid的 ...

  6. Java 观察者模式介绍及示例

    Java 观察者模式介绍及示例 一.观察者模式简介 1.1概念 观察者模式(Observer Pattern) : 观察者模式又名 发布/订阅模式,属于行为模式,定义了对象中一对多的依赖关系,让多个观 ...

  7. python中的内置函数getattr()介绍及示例

    python中的内置函数getattr()介绍及示例 其实getattr()这个方法最主要的作用是实现反射机制.也就是说可以通过字符串获取方法实例.这样,你就可以把一个类可能要调用的方法放在配置文件里 ...

  8. 集合视图UICollectionView 介绍及其示例程序

    UICollectionView是一种新的数据展示方式,简单来说可以把它理解成多列的UITableView.如果你用过iBooks的话,可 能你还对书架布局有一定印象,一个虚拟书架上放着你下载和购买的 ...

  9. java的annotation_Java Annotation认知(包括框架图、详细介绍、示例说明)

    摘要 Java Annotation是JDK5.0引入的一种注释机制. 网上很多关于Java Annotation的文章,看得人眼花缭乱.Java Annotation本来很简单的,结果说的人没说清楚 ...

最新文章

  1. 联想笔记本Ideapad(flex 2)进入BIOS设置U盘启动的详细步骤
  2. AttributeError: module ‘tensorflow‘ has no attribute ‘Session‘错误解决
  3. 免sudo使用docker
  4. ECSHOP发送邮件提示need rcpt command的解决方法
  5. Vue框架之条件与循环的使用
  6. python整数分节输出_pyfactor
  7. python实现根据文件名找出(拷贝/剪切)同名文件的不同类型文件
  8. rfc 查看工具_使用技术RFC作为管理工具的6课
  9. 明晚8点直播丨 Oracle RMAN 单实例异机迁移恢复(版本:11gR2)
  10. Android实用笔记——使用ViewFlipper实现屏幕切换动画
  11. 大数据-实时推荐系统最主流推荐系统itemCF和userCF视频教程下载
  12. Bailian3756 多边形内角和【数学计算】
  13. Android 文件存放路径
  14. 浅谈软件研发管理体系建设
  15. PC电脑桌面监控:Xbox Game Bar
  16. Linux虚拟机网络配置
  17. UVA 12235 Help Bubu 状态压缩DP
  18. miui 10 android 9,MIUI10开发版8.12.13发布 基于Android 9.0 小米Max3已更新
  19. Leetcode575:分糖果
  20. 【JAVA大厂面试必问】大厂面试八股文整理, 中厂小厂也爱问的八股文!

热门文章

  1. C++ STL之 queue和deque用法详解
  2. ad频谱分析 matlab_使用Matlab对采样数据进行频谱分析
  3. identity认证
  4. xcx欢迎使用CSDN-markdown编辑器
  5. 商品常见指标 - 商品毛利
  6. [Plant Simulation]工人开车搬运及装卸货物(Transporter的复合使用及pe函数)
  7. ScrollView高度测量原理
  8. 搭建unbound转发服务器分流国内外及内网域名
  9. Apache部署静态网站
  10. 基于Qt的飞机小游戏实现