根据文件头判断文件类型
前言
很多地方要判断文件类型
大多数时候 用后缀判断了
后缀判断。。有点自欺欺人
干脆趁着有时间 写个根据文件前4个字节判断文件的实际格式
参考文章:
https://baike.baidu.com/item/%E6%96%87%E4%BB%B6%E5%A4%B4/2695144?fr=aladdin
http://doc.chacuo.net/filehead 常见的文件类型和headerHex对应表
示例
- util
package com.ming.core.utils;
import com.google.common.collect.Sets;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Set;
/**
* 文件类型工具类
* 根据后缀判断文件类型并不是很准确
* 需要根据文件的头四位来获取文件真实类型
*
*
* <p>
* <p>
* https://baike.baidu.com/item/%E6%96%87%E4%BB%B6%E5%A4%B4/2695144?fr=aladdin
* <p>
* <p>
* 新增文件类型操作方式:
* 1:修改main函数里面的文件地址 并且执行获取headerHex
* 2:FileType枚举新增枚举
* <p>
* 常见文件参考headerHex: http://doc.chacuo.net/filehead
*
* @author ming
* @date 2022-01-04 18:37:33
*/
public class FileTypeUtils {
@Getter
@AllArgsConstructor
public enum FileType {
JPG(Sets.newHashSet("FFD8FFE0", "FFD8FFE000104A4649460001", "FFD8FFEE"), Sets.newHashSet(".jpg", "jpeg"), "JPEG原始或JFIF或Exif文件格式"),
PNG(Sets.newHashSet("89504E47"), Sets.newHashSet(".png"), "png格式图片,以可移植网络图形格式编码的图像"),
JAVA_FILE(Sets.newHashSet("7061636B"), Sets.newHashSet(".java"), "java源码文件"),
JAVA_CLASS_FILE(Sets.newHashSet("CAFEBABE"), Sets.newHashSet(".class"), "java字节码文件"),
;
private Set<String> headerHexSet;
private Set<String> suffixSet;
private String desc;
}
/**
* 头部字节数组长度
*/
private static final int HEADER_BYTE_ARRAY_LENGTH = 4;
/**
* 获取常见文件的文件头
*
* @author ming
* @date 2022-04-18 10:29:41
*/
public static void main(String[] args) throws IOException {
String path = "";
System.out.println(getFileHeaderHex(Files.newInputStream(Path.of(path))));
}
/**
* 根据文件路径获取文件格式信息
*
* @param filePath 文件路径
* @return String 文件类型
* @author ming
* @date 2022-04-18 11:54:41
*/
public static FileType getFileType(String filePath) throws IOException {
return getFileType(Files.newInputStream(Path.of(filePath)));
}
/**
* 根据文件InputStream获取文件格式信息
*
* @param fileInputStream 文件输入流
* @return String 文件类型
* @author ming
* @date 2022-04-18 11:54:41
*/
public static FileType getFileType(InputStream fileInputStream) throws IOException {
return getFileTypeByFileHeaderHex(getFileHeaderHex(fileInputStream));
}
/**
* 根据文件内容byte数组获取文件格式信息
*
* @param fileBytes 文件内容数组
* @return String 文件类型
* @author ming
* @date 2022-04-18 11:54:41
*/
public static FileType getFileType(byte[] fileBytes) {
return getFileTypeByFileHeaderHex(getFileHeaderHex(fileBytes));
}
/**
* 根据文件headerHex 返回文件类型
*
* @param fileHeaderHex 文件头部hex
* @return String 文件类型
* @author ming
* @date 2022-04-18 11:55:56
*/
private static FileType getFileTypeByFileHeaderHex(String fileHeaderHex) {
assert StringUtils.isNotEmpty(fileHeaderHex);
return Arrays.stream(FileType.values())
.filter(f -> f.headerHexSet.contains(fileHeaderHex))
.findAny().orElseThrow(() -> new RuntimeException("未识别该文件类型!请检查是否录入该文件标识或者为txt类型文件"));
}
/**
* byte数组转换成16进制字符串
*
* @param src 字节数组
* @return String 大写的hex码
*/
private static String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder();
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
// 以十六进制(基数 16)无符号整数形式返回一个整数参数的字符串表示形式,并转换为大写
String hv = Integer.toHexString(src[i] & 0xFF).toUpperCase();
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
/**
* 根据文件数组 获取文件头部Hex码
*
* @param fileBytes 文件内容数组
* @return String fileHeaderHex
* @author ming
* @date 2022-04-18 11:44:50
*/
private static String getFileHeaderHex(byte[] fileBytes) {
if (fileBytes.length <= HEADER_BYTE_ARRAY_LENGTH) {
throw new RuntimeException("字节数组长度不能小于等于" + HEADER_BYTE_ARRAY_LENGTH);
}
byte[] headerBytes = new byte[HEADER_BYTE_ARRAY_LENGTH];
//获取前四个字节
System.arraycopy(fileBytes, 0, headerBytes, 0, HEADER_BYTE_ARRAY_LENGTH);
return bytesToHexString(headerBytes);
}
/**
* 根据文件流获取文件头部hex码
* <p>
* 会执行InputStream#close()
*
* @param inputStream 输入流
* @return String fileHeaderHex
* @throws IOException
* @author ming
* @date 2022-04-18 11:48:08
*/
private static String getFileHeaderHex(InputStream inputStream) throws IOException {
try {
byte[] b = new byte[HEADER_BYTE_ARRAY_LENGTH];
inputStream.read(b, 0, b.length);
return bytesToHexString(b);
} finally {
inputStream.close();
}
}
private FileTypeUtils() {
}
}
- 测试用例
package com.ming.core.utils;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.*;
class FileTypeUtilsTest {
@Test
void getFileType() throws IOException {
FileTypeUtils.FileType fileType = FileTypeUtils.getFileType("C:\\Users\\ming\\Desktop\\workspaces\\workbench\\admin\\target\\classes\\com\\ming\\Start.class");
System.out.println(fileType.getDesc());
}
}
总结
一般的文件的头部都是固定的类型 例如 java字节码文件的 咖啡宝贝(CAFEBABE)
这种判断文件类型会比判断后缀靠谱