美文网首页iOS 面试题iOS开发攻城狮的集散地iOS 进阶
18·iOS 面试题·在不知道二进制文件格式的情况下如何区分文件

18·iOS 面试题·在不知道二进制文件格式的情况下如何区分文件

作者: pengxuyuan | 来源:发表于2018-10-19 14:55 被阅读22次

前言

对于判断一个文件的格式,不可以简单通过文件的后缀名来确定文件类型,因为后缀名是可以自由修改的。在平时开发中,需要判断不同图片的文件格式,就不能简单的判断图片文件后缀名或者是图片链接的后缀。

更有的时候,我们只有二进制数据。对此,我们可以通过分析二进制来判断该二进制代表的文件格式。

二进制文件结构

常见的二进制都是由文件头和分区组成,文件头描述了文件的整体信息,常见的字段有魔数、版本号、检验码、文件大小等等。

文件头魔数,一般作为文件格式的标识,我们可以通过分析这个魔数,来确定该二进制的文件格式。对于常见的魔数可以参阅:FILE SIGNATURES TABLE

SDWebImage 框架判断图片格式

SDWebImage 框架为 NSData 实现了个分类来判断二进制文件图片格式,主要是通过取出 Data 中的第一个字节来进行魔数判断,具体代码如下:

+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
    if (!data) {
        return SDImageFormatUndefined;
    }
    
    // File signatures table: http://www.garykessler.net/library/file_sigs.html
    uint8_t c;
    [data getBytes:&c length:1];
    switch (c) {
        case 0xFF:
            return SDImageFormatJPEG;
        case 0x89:
            return SDImageFormatPNG;
        case 0x47:
            return SDImageFormatGIF;
        case 0x49:
        case 0x4D:
            return SDImageFormatTIFF;
        case 0x52: {
            if (data.length >= 12) {
                //RIFF....WEBP
                NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
                if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                    return SDImageFormatWebP;
                }
            }
            break;
        }
        case 0x00: {
            if (data.length >= 12) {
                //....ftypheic ....ftypheix ....ftyphevc ....ftyphevx
                NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding];
                if ([testString isEqualToString:@"ftypheic"]
                    || [testString isEqualToString:@"ftypheix"]
                    || [testString isEqualToString:@"ftyphevc"]
                    || [testString isEqualToString:@"ftyphevx"]) {
                    return SDImageFormatHEIC;
                }
            }
            break;
        }
    }
    return SDImageFormatUndefined;
}

YYImage 框架判断图片格式

YYImage 框架提供一个 YYImageDetectType 函数来判断一个二进制图片格式,同理,也是取出 Data 前几个字节来进行魔数判断,具体代码如下:

YYImageType YYImageDetectType(CFDataRef data) {
    if (!data) return YYImageTypeUnknown;
    uint64_t length = CFDataGetLength(data);
    if (length < 16) return YYImageTypeUnknown;
    
    const char *bytes = (char *)CFDataGetBytePtr(data);
    
    uint32_t magic4 = *((uint32_t *)bytes);
    switch (magic4) {
        case YY_FOUR_CC(0x4D, 0x4D, 0x00, 0x2A): { // big endian TIFF
            return YYImageTypeTIFF;
        } break;
            
        case YY_FOUR_CC(0x49, 0x49, 0x2A, 0x00): { // little endian TIFF
            return YYImageTypeTIFF;
        } break;
            
        case YY_FOUR_CC(0x00, 0x00, 0x01, 0x00): { // ICO
            return YYImageTypeICO;
        } break;
            
        case YY_FOUR_CC(0x00, 0x00, 0x02, 0x00): { // CUR
            return YYImageTypeICO;
        } break;
            
        case YY_FOUR_CC('i', 'c', 'n', 's'): { // ICNS
            return YYImageTypeICNS;
        } break;
            
        case YY_FOUR_CC('G', 'I', 'F', '8'): { // GIF
            return YYImageTypeGIF;
        } break;
            
        case YY_FOUR_CC(0x89, 'P', 'N', 'G'): {  // PNG
            uint32_t tmp = *((uint32_t *)(bytes + 4));
            if (tmp == YY_FOUR_CC('\r', '\n', 0x1A, '\n')) {
                return YYImageTypePNG;
            }
        } break;
            
        case YY_FOUR_CC('R', 'I', 'F', 'F'): { // WebP
            uint32_t tmp = *((uint32_t *)(bytes + 8));
            if (tmp == YY_FOUR_CC('W', 'E', 'B', 'P')) {
                return YYImageTypeWebP;
            }
        } break;
            /*
             case YY_FOUR_CC('B', 'P', 'G', 0xFB): { // BPG
             return YYImageTypeBPG;
             } break;
             */
    }
    
    uint16_t magic2 = *((uint16_t *)bytes);
    switch (magic2) {
        case YY_TWO_CC('B', 'A'):
        case YY_TWO_CC('B', 'M'):
        case YY_TWO_CC('I', 'C'):
        case YY_TWO_CC('P', 'I'):
        case YY_TWO_CC('C', 'I'):
        case YY_TWO_CC('C', 'P'): { // BMP
            return YYImageTypeBMP;
        }
        case YY_TWO_CC(0xFF, 0x4F): { // JPEG2000
            return YYImageTypeJPEG2000;
        }
    }
    
    // memcmp()用来比较s1 和s2 所指的内存区间前n 个字符。
    // JPG             FF D8 FF
    if (memcmp(bytes,"\377\330\377",3) == 0) return YYImageTypeJPEG;
    
    // JP2
    if (memcmp(bytes + 4, "\152\120\040\040\015", 5) == 0) return YYImageTypeJPEG2000;
    
    return YYImageTypeUnknown;
}

总结

我们可以通过二进制文件头魔数来确定文件格式。对于判断图片文件格式,YYImage 框架和 SDWebImage 框架都是采用了这种方法,这两个框架实现区别在于 SDWebImage 框架利用 Foundation 方法来操作,YYImage 框架利用 Core-Foundation 函数来实现。经过测试 YYImage 性能稍优于 SDWebImage,但也只是在体量很大的时候有差别,在平时开发中,我们不需要考虑这里区别。

参考文献

二进制文件格式设计

YYImage

SDWebImage

FILE SIGNATURES TABLE

相关文章

网友评论

    本文标题:18·iOS 面试题·在不知道二进制文件格式的情况下如何区分文件

    本文链接:https://www.haomeiwen.com/subject/jogyzftx.html