1. 使用场景

在由不同编程语言构成的系统中,经常会出现下面的场景:一开始某个子系统(非Java编写)在硬盘上生成了某个二进制文件,下一步交给Java代码进行处理。并且文件的一些附加信息会保存在文件名中。


比如【173】VS2022调试通过海康温度报警SDK的C++代码 中生成的文件,文件名就用英文方括号 [] 来显示不同的信息。

例如

FireAlarm[192.168.1.56][20220107153455][101.5][0].jpg

第一个方括号表示IP。
第二个方括号表示日期时间。
第三个方括号表示温度。
第四个方括号表示测温单位: 0- 摄氏度(℃),1- 华氏度(℉),2- 开尔文(K)。
其中如果数值为空,就用空的方括号表示。对应的图片文件生成后,就需要Java来对文件做进一步处理,并读取和文件相关的一系列参数加以处理。那么,怎么用Java来读取文件名中的数据呢?这就是下一节讨论的内容。

2. 解析方括号

我把文件名看作程序的输入参数,参数类型是字符串。我同时也把文件名看作表达式。下文用表达式来指代文件名。
Java代码使用了 Parser 的方式来编写代码。 因为这个表达式逻辑比较简单,所以就没有使用巴科斯范式(BNF)。

整个代码结构如下:

java 中括号 类 java中括号代表什么_java

其中Main类包含main方法,做测试用。

包 zhangchao.parser是用来解析表达式的包。其中只有 Parser 类的 getStrArr 方法是对外提供的公共方法。

2.1 单词(token)

表达式是由不同的单词(token)组成的。每个单词是一个或多个字符组成的集合。在这个例子中单词分成标识符单词(对应类IdToken)和标识符单词(对应类StrToken)。为了方便编程实现,我还加了个抽象类AbstractToken。继承关系如下图所示:

java 中括号 类 java中括号代表什么_java_02

AbstractToken.java

package zhangchao.parser;

/**
 * 所有类型单词的抽象类
 * @author zhangchao
 */
abstract class AbstractToken {

    /**
     * 判断单词类型是不是字符串。
     * @return true是字符串,false不是字符串。
     */
    boolean isString() {
        return false;
    }

    /**
     * 判断单词是不是标识符。
     * @return true是标识符,false不是标识符。
     */
    boolean isIdentifier() {
        return false;
    }


    /**
     * 以字符串类型,返回单词内容
     * @return 单词的字符串内容
     */
    abstract String show();
}

IdToken.java

package zhangchao.parser;

/**
 * 标识符类型的单词
 *
 * @author zhangchao
 */
class IdToken extends AbstractToken {
    // 单词的值
    String value;

    @Override
    boolean isIdentifier() {
        return true;
    }

    @Override
    String show() {
        return value;
    }
}

StrToken.java

package zhangchao.parser;

/**
 * 字符串类型的单词
 * @author zhangchao
 */
class StrToken extends AbstractToken{
    // 单词的值
    String value;

    @Override
    boolean isString() {
        return true;
    }

    @Override
    String show() {
        return value;
    }
}

2.2 词法分析器Lexer

词法分析器Lexer负责把表达式拆成多个单词。

Lexer.java

package zhangchao.parser;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 词法分析器
 * @author zhangchao
 */
class Lexer {

    // 保存单词
    private ArrayList<AbstractToken> queue = new ArrayList<AbstractToken>();


    /**
     * 预读取表达式。
     * 从index给定的位置开始,计算字符串类型的单词的长度。
     *
     * @param arr  表达式转化成的字符数组。
     * @param index 从数组中index位置开始,计算字符串类型的单词的长度。
     * @return 字符串类型的单词的长度。
     */
    private int preRead(char[] arr, int index) {
        int result = 0;
        for (int i = index; i < arr.length; i++) {
            char c = arr[i];
            if (c == '[') {
                break;
            } else if (c == ']') {
                break;
            } else {
                result ++;
            }
        }
        return result;
    }

    /**
     * 解析输入的表达式字符串,并且解析成【单词Token】
     * @param str 输入的表达式字符串。
     */
    void addQueue(String str) {
        char[] arr = str.toCharArray();
        int length = arr.length;
        for (int i = 0; i < length;) {
            char c = arr[i];
            if (c == '[') {
                IdToken t = new IdToken();
                t.value = String.valueOf(c);
                queue.add(t);
                i++;
            } else if (c == ']') {
                IdToken t = new IdToken();
                t.value = String.valueOf(c);
                queue.add(t);
                i++;
            } else {
                int size = preRead(arr, i);
                char[] tmp = Arrays.copyOfRange(arr, i, i + size);
                StrToken strToken = new StrToken();
                strToken.value = new String(tmp);
                queue.add(strToken);
                i = i + size;
            }
        }
    }

    /**
     * 因为只关心在 [] 中的字符串,所以不在 [] 中的 【单词】 都要删除
     */
    void delNonsenseToken() {
        int length = this.queue.size();
        if (0 == length) {
            return;
        }
        for (int i = length - 1; i >= 0; i--) {
            AbstractToken t = queue.get(i);
            if (t.isString()) {
                // 开头或者结尾的字符串,肯定不在 [] 之中
                if (queue.size() - 1 == i || 0 == i) {
                    queue.remove(i);
                } else if (i - 1 >= 0) {
                    AbstractToken left = queue.get(i - 1);
                    if (left.isIdentifier() && left.show().equals("]")) {
                        queue.remove(i);
                    }
                } else if (i + 1 < queue.size()) {
                    AbstractToken right = queue.get(i + 1);
                    if (right.isIdentifier() && right.show().equals("[")) {
                        queue.remove(i);
                    }
                }
            } // end if (t.isString()) {
        }
    }



    List<AbstractToken> getQueue() {
        queue.trimToSize();
        return queue;
    }


}

2.3 语法分析器Parser

语法分析器Parser负责解析单词,构造抽象语法树(AST)。类 AstNode 是抽象语法树的节点。

AstNode.java

package zhangchao.parser;

import java.util.List;
import java.util.ArrayList;

/**
 * 抽象语法树  AST 的节点
 * @author zhangchao
 */
class AstNode {

    List<AbstractToken> children = new ArrayList<>();

    String eval() {
        return children.get(1).show();
    }
}

Parser.java

package zhangchao.parser;

import java.util.ArrayList;
import java.util.List;

/**
 * 语法分析器
 * @author zhangchao
 */
public class Parser {

    /**
     * 词法分析器
     */
    private Lexer lexer = new Lexer();


    /**
     * 对输入的字符串进行语法分析,生成抽象语法树AST。
     * @param str 输入的字符串
     * @return 抽象语法树节点AstNode列表。
     */
    private List<AstNode> parser(String str) {
        lexer.addQueue(str);
        lexer.delNonsenseToken();
        List<AbstractToken> queue = lexer.getQueue();

        ArrayList<AstNode> allNodes = new ArrayList<>();

        int size = queue.size();
        for (int i = 0; i < size; i++) {
            AbstractToken current = queue.get(i);
            AbstractToken add1 = null;
            AbstractToken add2 = null;
            if (i + 1 < size) {
                add1 = queue.get(i + 1);
            }
            if (i + 2 < size) {
                add2 = queue.get(i + 2);
            }

            if (current.isIdentifier() && current.show().equals("[") &&
                    null != add1 && add1.isIdentifier() && add1.show().equals("]")) {
                AstNode node = new AstNode();
                node.children.add(current);
                StrToken emptyToken = new StrToken();
                emptyToken.value = "";
                node.children.add(emptyToken);
                node.children.add(add1);
                allNodes.add(node);
            }
            if (current.isIdentifier() && current.show().equals("[") &&
                    null != add1 && add1.isString() &&
                    null != add2 && add2.isIdentifier() && add2.show().equals("]")) {
                AstNode node = new AstNode();
                node.children.add(current);
                node.children.add(add1);
                node.children.add(add2);
                allNodes.add(node);
            }
        }
        allNodes.trimToSize();
        return allNodes;
    }


    /**
     * 输入形如  COMM_ALARM_ACS_CapPic[0][20220107153455][36.099][192.168.1.1][].jpg  字符串,
     * 此方法会把 [] 中的内容提取出来,
     * 并用列表形式返回。对比Lexer,增强了对空括号的处理。
     * @param str 形如  COMM_ALARM_ACS_CapPic[0][20220107153455][36.099][192.168.1.1].jpg  字符串
     * @return 从[]中提取的字符串,组成的字符串列表
     */
    public List<String> getStrArr(String str) {
        List<AstNode> allNodes = this.parser(str);
        ArrayList<String> result = new ArrayList<>();
        for (AstNode node : allNodes) {
            result.add(node.eval());
        }
        return result;
    }
}

2.4 测试

Main.java

package zhangchao;

import zhangchao.parser.Parser;

import java.util.List;

/**
 * 主类,保护main方法。
 * @author zhangchao
 */
public class Main {


    public static void main(String[] args) {


        String name = "COMM_ALARM_ACS_CapPic[][20220107153455][36.099][37.69.15.243][].jpg";
        Parser p = new Parser();
        List<String> stringList = p.getStrArr(name);
        for (String str : stringList) {
            System.out.println("str=" + str);
        }
    }

}

输出结果:

str=
str=20220107153455
str=36.099
str=37.69.15.243
str=