常規方案
使用 FileReader 以utf-8格式讀取文件,根據文件內容是否包含亂碼字符 � ,來判斷文件是否為utf-8。
如果存在 �,即文件編碼非utf-8,反之為utf-8。
代碼如下:
const isUtf8 = async (file: File) => { return await new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsText(file); reader.onloadend = (e: any): void => { const content = e.target.result; const encodingRight = content.indexOf("") === -1; if (encodingRight) { resolve(encodingRight); } else { reject(new Error("編碼格式錯誤,請上傳 UTF-8 格式文件")); } }; reader.onerror = () => { reject(new Error("文件內容讀取失敗,請檢查文件是否損壞")); }; }); };
該方法問題在於,如果文件非常大,比如幾個G,瀏覽器讀到的內容直接放在內存中,fileReader實例會直接觸發onerror,拋出錯誤,有時瀏覽器會直接崩潰。
https://www.98891.com/article-68-1.html
大文件方案
對於大文件,可以對文件內容進行抽樣,對文件進行切片,這里使用 100 片。對切出的每片文件再切取前面 1kb 大小的片段,以 string 方式讀取。如果 1024B 可能正好切在某個漢字編碼的中間,導致以 string 方式讀取時出錯,即首尾可能出現 � ,被認為是非utf-8片段。這時可以取 1kb 對應字符串的前半段,再去判斷 � 是否存在。
上述常數可以根據需求進行調整。
代碼如下:
const getSamples = (file: File) => { const filesize = file.size; const parts: Blob[] = []; if (filesize < 50 * 1024 * 1024) { parts.push(file); } else { let total = 100; const sampleSize = 1024 * 1024; const chunkSize = Math.floor(filesize / total); let start = 0; let end = sampleSize; while (total > 1) { parts.push(file.slice(start, end)); start += chunkSize; end += chunkSize; total--; } } return parts; }; const isUtf8 = (filePart: Blob) => { return new Promise((resolve, reject) => { const fileReader = new FileReader(); fileReader.readAsText(filePart); fileReader.onload = (e) => { const str = e.target?.result as string; // 大致取一半 const sampleStr = str?.slice(4, 4 + str?.length / 2); if (sampleStr.indexOf("�") === -1) { resolve(void 0); } else { reject(new Error(編碼格式錯誤,請上傳 UTF-8 格式文件")); } }; fileReader.onerror = () => { reject(new Error(文件內容讀取失敗,請檢查文件是否損壞")); }; }); }; export default async function (file: File) { const samples = getSamples(file); let res = true; for (const filePart of samples) { try { await isUtf8(filePart); } catch (error) { res = false; break; } } return res; }