前言
有時為了資訊保密或是單純閱讀程式碼,我們需要刪除註釋。
之前考慮過正規表示式,但是感覺實現起來相當麻煩。而狀態機可以把多種情況歸為一類狀態再行分解,大大簡化問題。本文就是基於狀態機實現的。
刪除C/C++程式碼註釋
需要考慮的情況
- //
- /* */
- //和/* */巢狀(注意不存在/* */和/* */巢狀)
- 折行註釋(用\間隔)
- 字元中存在的/和*
- 字串中存在的//和/* */
- 字串中的折行程式碼(用\間隔)
- 標頭檔案中可能存在的/
- 狀態轉移描述
狀態轉移描述
思路參考了部落格怎樣刪除C/C++程式碼中的所有註釋?淺談狀態機的程式設計思想,寫得很贊。
本文基於上面所述博文進行了以下修改或是優化:
- 原博文沒有考慮/***/的情況(其中*的個數為奇數),已修正
- 切換到了windows平臺下,支援windows換行\r\n(並請注意:如果原檔案末尾沒有回車,會自動插入)
- 狀態量優化為列舉常量
- 狀態轉移由if...else...elseif結構改為switch...case結構,更為清晰,對於大型程式碼,效率更高
其中,除狀態NOTE_MULTILINE_STAR
外,其餘狀態下均需進行字元(串)處理,以保持正確輸出。詳見文末程式碼。
刪除Java程式碼註釋
需要考慮的情況
- //
- /* */
- /** */
- //和/**/巢狀(注意不存在/* */和/* */巢狀,不存在/** */和/** */巢狀,不存在/* */和/** */巢狀)
- //和/** */巢狀
- 字元中存在的/和*
- 字串中存在的//、/**/以及/** */
- 狀態轉移描述
狀態轉移描述
可以看到,java中的註釋規則更為簡單,其中/** */完全可以用/* */的狀態涵蓋。且不會出現折行註釋和字串折行的情況,因此狀態更加簡單,有興趣的可以畫一畫,這裡就不畫圖了。換句話說,上面刪除C/C++註釋的程式完全可以用來刪除java註釋。
程式
package code_tools;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.Scanner;
/**
* @author xiaoxi666
* @version 1.0.0 2017.12.01
*/
public class deleteCAndCplusplusAndJavaNote {
/**
* 狀態
*/
enum State {
CODE, // 正常程式碼
SLASH, // 斜槓
NOTE_MULTILINE, // 多行註釋
NOTE_MULTILINE_STAR, // 多行註釋遇到*
NOTE_SINGLELINE, // 單行註釋
BACKSLASH, // 折行註釋
CODE_CHAR, // 字元
CHAR_ESCAPE_SEQUENCE, // 字元中的轉義字元
CODE_STRING, // 字串
STRING_ESCAPE_SEQUENCE// 字串中的轉義字元
};
/**
* @function 刪除程式碼中的註釋,以String形式返回
* @param strToHandle 待刪除註釋的程式碼
* @return 已刪除註釋的程式碼,String字串形式
*/
public static String delete_C_Cplusplus_Java_Note(String strToHandle) {
StringBuilder builder = new StringBuilder();
State state = State.CODE;// Initiate
for (int i = 0; i < strToHandle.length(); ++i) {
char c = strToHandle.charAt(i);
switch (state) {
case CODE:
if (c == '/') {
state = State.SLASH;
}else {
builder.append(c);
if(c=='\'') {
state=State.CODE_CHAR;
}else if(c=='\"') {
state=State.CODE_STRING;
}
}
break;
case SLASH:
if (c == '*') {
state = State.NOTE_MULTILINE;
} else if (c == '/') {
state = State.NOTE_SINGLELINE;
} else {
builder.append('/');
builder.append(c);
state = State.CODE;
}
break;
case NOTE_MULTILINE:
if(c=='*') {
state=State.NOTE_MULTILINE_STAR;
}else {
if(c=='\n') {
builder.append("\r\n");//保留空行,當然,也可以去掉
}
state=State.NOTE_MULTILINE;//保持當前狀態
}
break;
case NOTE_MULTILINE_STAR:
if(c=='/') {
state=State.CODE;
}else if(c=='*') {
state=State.NOTE_MULTILINE_STAR;//保持當前狀態
}
else {
state=State.NOTE_MULTILINE;
}
break;
case NOTE_SINGLELINE:
if(c=='\\') {
state=State.BACKSLASH;
}else if(c=='\n'){
builder.append("\r\n");
state=State.CODE;
}else {
state=State.NOTE_SINGLELINE;//保持當前狀態
}
break;
case BACKSLASH:
if(c=='\\' || c=='\r'||c=='\n') {//windows系統換行符為\r\n
if(c=='\n') {
builder.append("\r\n");//保留空行,當然,也可以去掉
}
state=State.BACKSLASH;//保持當前狀態
}else {
state=State.NOTE_SINGLELINE;
}
break;
case CODE_CHAR:
builder.append(c);
if(c=='\\') {
state=State.CHAR_ESCAPE_SEQUENCE;
}else if(c=='\'') {
state=State.CODE;
}else {
state=State.CODE_CHAR;//保持當前狀態
}
break;
case CHAR_ESCAPE_SEQUENCE:
builder.append(c);
state=State.CODE_CHAR;
break;
case CODE_STRING:
builder.append(c);
if(c=='\\') {
state=State.STRING_ESCAPE_SEQUENCE;
}else if(c=='\"') {
state=State.CODE;
}else {
state=State.CODE_STRING;//保持當前狀態
}
break;
case STRING_ESCAPE_SEQUENCE:
builder.append(c);
state=State.CODE_STRING;
break;
default:
break;
}
}
return builder.toString();
}
/**
* @function 從指定檔案中讀取程式碼內容,以String形式返回
* @param inputFileName 待刪除註釋的檔案
* @return 待刪除註釋的檔案中的程式碼內容,String字串形式
* @note 輸入檔案格式預設為 UTF-8
*/
public static String readFile(String inputFileName) {
StringBuilder builder = new StringBuilder();
try {
FileInputStream fis = new FileInputStream(inputFileName);
InputStreamReader dis = new InputStreamReader(fis);
BufferedReader reader = new BufferedReader(dis);
String s;
// 每次讀取一行,當改行為空時結束
while ((s = reader.readLine()) != null) {
builder.append(s);
builder.append("\r\n");// windows系統換行符
}
reader.close();
dis.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
return builder.toString();
}
/**
* @function 將刪除註釋後的程式碼儲存到指定新檔案
* @param outputFileName 儲存“刪除註釋後的程式碼”的檔案的檔名
* @param strHandled 刪除註釋後的程式碼
*/
public static void writeFile(String outputFileName, String strHandled) {
try {
FileOutputStream fos = new FileOutputStream(outputFileName);
OutputStreamWriter dos = new OutputStreamWriter(fos);
BufferedWriter writer = new BufferedWriter(dos);
writer.write(strHandled);
writer.close();
dos.close();
fos.close();
System.out.println("code that without note has been saved successfully in " + outputFileName);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* @function 讀取待處理檔案,刪除註釋,處理過的程式碼寫入新檔案
* @param args
*/
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
//待刪除註釋的檔案
System.out.println("The fileName that will be delete note:");
String inputFileName = in.nextLine();
//儲存“刪除註釋後的程式碼”的檔案
System.out.println("The fileName that will save code without note:");
String outputFileName = in.nextLine();
String strToHandle = readFile(inputFileName);
String strHandled = delete_C_Cplusplus_Java_Note(strToHandle);
writeFile(outputFileName, strHandled);
}
}複製程式碼
說明
- 本程式保留註釋佔用行,也就是說,註釋以外的程式碼原樣保留(行數也不會變),註釋行變為空白。
- 不檢測檔案字尾(這就意味著把程式碼寫在.txt裡面也可以處理),有需求的可以自行新增。
- 本程式適用於windows平臺,其他平臺如linux和mac請替換“\r\n”換行符。檔案格式預設為UTF。
- 有興趣的可以封裝成圖形介面,直接拖入檔案處理,更好用。
- 本程式經過大量測試未發現bug,若讀者發現bug,歡迎提出。