状态机编程思想:删除代码注释(支持C/C++和Java)

3,545 阅读4分钟

前言

有时为了信息保密或是单纯阅读代码,我们需要删除注释。
之前考虑过正则表达式,但是感觉实现起来相当麻烦。而状态机可以把多种情况归为一类状态再行分解,大大简化问题。本文就是基于状态机实现的。

删除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,欢迎提出。

参考