0%

根据文件头判断文件类型

前言

很多地方要判断文件类型
大多数时候 用后缀判断了
后缀判断。。有点自欺欺人
干脆趁着有时间 写个根据文件前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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
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() {
}

}
  • 测试用例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)
这种判断文件类型会比判断后缀靠谱