如何在Java中实现FSM有限状态机
我有工作要做,我需要你的帮助。 我们想要实现一个如何在Java中实现FSM有限状态机,java,design-patterns,state-machine,fsm,state-pattern,Java,Design Patterns,State Machine,Fsm,State Pattern,我有工作要做,我需要你的帮助。 我们想要实现一个FSM-有限状态机,来识别字符序列(比如:a,B,C,a,C),并判断它是否被接受 我们认为应该实现三个类:State、Event和Machine。 state类在FSM中表示一个节点,我们想用状态设计模式实现它,每个节点将从抽象类状态扩展,每个类将处理不同类型的事件并指示到新状态的转换。你认为这是个好主意吗 第二件事,我们不知道如何保存所有的转换。我们再次考虑用某种map来实现它,它保持起点,并获得下一个状态的某种向量,但我不确定这是一个好主意
FSM-有限状态机
,来识别字符序列(比如:a,B,C,a,C),并判断它是否被接受
我们认为应该实现三个类:State
、Event
和Machine
。
state
类在FSM
中表示一个节点,我们想用状态设计模式实现它,每个节点将从抽象类状态扩展,每个类将处理不同类型的事件并指示到新状态的转换。你认为这是个好主意吗
第二件事,我们不知道如何保存所有的转换。我们再次考虑用某种map
来实现它,它保持起点,并获得下一个状态的某种向量,但我不确定这是一个好主意
我很乐意得到一些关于如何实施它的想法,或者你可以给我一些出发点
我应该如何保存FSM,也就是说我应该如何在程序开始时构建树?
我在谷歌上搜索了一下,找到了很多例子,但没有任何帮助
非常感谢。状态机的核心是转换表,它将状态和符号(您称之为事件)转换为新状态。这只是一个状态的双索引数组。为了健全和类型安全,将状态和符号声明为枚举。我总是以某种方式(特定于语言)添加一个“length”成员来检查数组边界。当我手工编写FSM的代码时,我会将代码格式化为行和列格式,并使用空格键。状态机的其他元素是初始状态和接受状态集。接受状态集最直接的实现是由状态索引的布尔数组。然而,在Java中,枚举是类,您可以在每个枚举值的声明中指定一个参数“accepting”,并在枚举的构造函数中初始化它
对于机器类型,可以将其作为泛型类编写。它需要两个类型参数,一个用于状态,一个用于符号,一个用于转换表的数组参数,一个用于初始状态。唯一的另一个细节(尽管很关键)是必须调用Enum.ordinal()来获取适合为转换数组编制索引的整数,因为没有语法可以直接声明带有枚举索引的数组(尽管应该有)
要抢占一个问题,EnumMap
对转换表不起作用,因为所需的键是一对枚举值,而不是一个枚举值
enum State {
Initial( false ),
Final( true ),
Error( false );
static public final Integer length = 1 + Error.ordinal();
final boolean accepting;
State( boolean accepting ) {
this.accepting = accepting;
}
}
enum Symbol {
A, B, C;
static public final Integer length = 1 + C.ordinal();
}
State transition[][] = {
// A B C
{
State.Initial, State.Final, State.Error
}, {
State.Final, State.Initial, State.Error
}
};
嗯,我建议你用Flyweight来实现这些状态。目的:避免大量小对象的内存开销。状态机可以变得非常非常大
我不确定是否需要使用设计模式状态来实现节点。状态机中的节点是无状态的。它们只是将当前输入符号与当前状态的可用转换相匹配。也就是说,除非我完全忘记了它们是如何工作的(这是绝对可能的)
如果我在编写代码,我会这样做:
interface FsmNode {
public boolean canConsume(Symbol sym);
public FsmNode consume(Symbol sym);
// Other methods here to identify the state we are in
}
List<Symbol> input = getSymbols();
FsmNode current = getStartState();
for (final Symbol sym : input) {
if (!current.canConsume(sym)) {
throw new RuntimeException("FSM node " + current + " can't consume symbol " + sym);
}
current = current.consume(sym);
}
System.out.println("FSM consumed all input, end state is " + current);
Map<Integer, Map<Symbol, Integer>> fsm; // A state is an Integer, the transitions are from symbol to state number
FsmState makeState(int stateNum) {
return new FsmState() {
public FsmState consume(final Symbol sym) {
final Map<Symbol, Integer> transitions = fsm.get(stateNum);
if (transisions == null) {
throw new RuntimeException("Illegal state number " + stateNum);
}
final Integer nextState = transitions.get(sym); // May be null if no transition
return nextState;
}
public boolean canConsume(final Symbol sym) {
return consume(sym) != null;
}
}
}
接口FsmNode{
公共布尔canConsume(符号sym);
公共FsmNode消费(符号sym);
//这里还有其他方法来确定我们所处的状态
}
列表输入=getSymbols();
FsmNode current=getStartState();
用于(最终符号sym:输入){
如果(!current.canConsume(sym)){
抛出新的运行时异常(“FSM节点”+current+“不能使用符号”+sym);
}
电流=电流消耗(sym);
}
System.out.println(“FSM消耗了所有输入,结束状态为“+电流”);
在这种情况下Flyweight会做什么?在FsmNode下面可能会有这样的东西:
interface FsmNode {
public boolean canConsume(Symbol sym);
public FsmNode consume(Symbol sym);
// Other methods here to identify the state we are in
}
List<Symbol> input = getSymbols();
FsmNode current = getStartState();
for (final Symbol sym : input) {
if (!current.canConsume(sym)) {
throw new RuntimeException("FSM node " + current + " can't consume symbol " + sym);
}
current = current.consume(sym);
}
System.out.println("FSM consumed all input, end state is " + current);
Map<Integer, Map<Symbol, Integer>> fsm; // A state is an Integer, the transitions are from symbol to state number
FsmState makeState(int stateNum) {
return new FsmState() {
public FsmState consume(final Symbol sym) {
final Map<Symbol, Integer> transitions = fsm.get(stateNum);
if (transisions == null) {
throw new RuntimeException("Illegal state number " + stateNum);
}
final Integer nextState = transitions.get(sym); // May be null if no transition
return nextState;
}
public boolean canConsume(final Symbol sym) {
return consume(sym) != null;
}
}
}
映射fsm;//状态为整数,从符号到状态编号的转换
FsmState makeState(int stateNum){
返回新的FsmState(){
公共FSM状态消费(最终符号sym){
最终映射转换=fsm.get(stateNum);
如果(转换==null){
抛出新的RuntimeException(“非法状态号”+stateNum);
}
final Integer nextState=transitions.get(sym);//如果没有转换,则可能为null
返回下一状态;
}
公共布尔canConsume(最终符号sym){
返回消耗(sym)!=null;
}
}
}
这将根据需要创建状态对象,它允许您使用更有效的底层机制来存储实际的状态机。我在这里使用的(Map(Integer,Map(Symbol,Integer)))不是特别有效的
请注意,Wikipedia页面关注的是许多相似对象共享相似数据的情况,Java中的字符串实现就是这样。在我看来,Flyweight更为通用,它涵盖了任何生命周期短的对象的按需创建(使用更多CPU以节省更高效的底层数据结构) EasyFSM是一个动态Java库,可用于实现FSM
您可以在以下位置找到相同的文档:
此外,您还可以从以下网址下载该库:
您可以用两种不同的方式实现有限状态机
备选案文1:
具有预定义工作流的有限状态机:如果您提前知道所有状态,并且状态机几乎是固定的,并且将来不会有任何更改,则建议使用该状态机
确定应用程序中所有可能的状态
识别应用程序中的所有事件
确定应用程序中可能导致状态转换的所有条件
事件的发生可能会导致状态转换
通过确定状态和转换的工作流来构建有限状态机
e、 g如果事件1发生在状态1,则状态将更新为
public class Action {
private String mActionName;
public Action(String actionName) {
mActionName = actionName;
}
String getActionName() {
return mActionName;
}
@Override
public String toString() {
return mActionName;
}
}
public class StateImpl implements IState {
private HashMap<String, IState> mMapping = new HashMap<>();
private String mStateName;
public StateImpl(String stateName) {
mStateName = stateName;
}
@Override
public Map<String, IState> getAdjacentStates() {
return mMapping;
}
@Override
public String getStateDesc() {
return mStateName;
}
@Override
public void addTransit(Action action, IState state) {
mMapping.put(action.toString(), state);
}
@Override
public void removeTransit(String targetStateDesc) {
// get action which directs to target state
String targetAction = null;
for (Map.Entry<String, IState> entry : mMapping.entrySet()) {
IState state = entry.getValue();
if (state.getStateDesc().equals(targetStateDesc)) {
targetAction = entry.getKey();
}
}
mMapping.remove(targetAction);
}
}
public class FiniteStateMachineImpl implements IFiniteStateMachine {
private IState mStartState;
private IState mEndState;
private IState mCurrentState;
private ArrayList<IState> mAllStates = new ArrayList<>();
private HashMap<String, ArrayList<IState>> mMapForAllStates = new HashMap<>();
public FiniteStateMachineImpl(){}
@Override
public void setStartState(IState startState) {
mStartState = startState;
mCurrentState = startState;
mAllStates.add(startState);
// todo: might have some value
mMapForAllStates.put(startState.getStateDesc(), new ArrayList<IState>());
}
@Override
public void setEndState(IState endState) {
mEndState = endState;
mAllStates.add(endState);
mMapForAllStates.put(endState.getStateDesc(), new ArrayList<IState>());
}
@Override
public void addState(IState startState, IState newState, Action action) {
// validate startState, newState and action
// update mapping in finite state machine
mAllStates.add(newState);
final String startStateDesc = startState.getStateDesc();
final String newStateDesc = newState.getStateDesc();
mMapForAllStates.put(newStateDesc, new ArrayList<IState>());
ArrayList<IState> adjacentStateList = null;
if (mMapForAllStates.containsKey(startStateDesc)) {
adjacentStateList = mMapForAllStates.get(startStateDesc);
adjacentStateList.add(newState);
} else {
mAllStates.add(startState);
adjacentStateList = new ArrayList<>();
adjacentStateList.add(newState);
}
mMapForAllStates.put(startStateDesc, adjacentStateList);
// update mapping in startState
for (IState state : mAllStates) {
boolean isStartState = state.getStateDesc().equals(startState.getStateDesc());
if (isStartState) {
startState.addTransit(action, newState);
}
}
}
@Override
public void removeState(String targetStateDesc) {
// validate state
if (!mMapForAllStates.containsKey(targetStateDesc)) {
throw new RuntimeException("Don't have state: " + targetStateDesc);
} else {
// remove from mapping
mMapForAllStates.remove(targetStateDesc);
}
// update all state
IState targetState = null;
for (IState state : mAllStates) {
if (state.getStateDesc().equals(targetStateDesc)) {
targetState = state;
} else {
state.removeTransit(targetStateDesc);
}
}
mAllStates.remove(targetState);
}
@Override
public IState getCurrentState() {
return mCurrentState;
}
@Override
public void transit(Action action) {
if (mCurrentState == null) {
throw new RuntimeException("Please setup start state");
}
Map<String, IState> localMapping = mCurrentState.getAdjacentStates();
if (localMapping.containsKey(action.toString())) {
mCurrentState = localMapping.get(action.toString());
} else {
throw new RuntimeException("No action start from current state");
}
}
@Override
public IState getStartState() {
return mStartState;
}
@Override
public IState getEndState() {
return mEndState;
}
}
public class example {
public static void main(String[] args) {
System.out.println("Finite state machine!!!");
IState startState = new StateImpl("start");
IState endState = new StateImpl("end");
IFiniteStateMachine fsm = new FiniteStateMachineImpl();
fsm.setStartState(startState);
fsm.setEndState(endState);
IState middle1 = new StateImpl("middle1");
middle1.addTransit(new Action("path1"), endState);
fsm.addState(startState, middle1, new Action("path1"));
System.out.println(fsm.getCurrentState().getStateDesc());
fsm.transit(new Action(("path1")));
System.out.println(fsm.getCurrentState().getStateDesc());
fsm.addState(middle1, endState, new Action("path1-end"));
fsm.transit(new Action(("path1-end")));
System.out.println(fsm.getCurrentState().getStateDesc());
fsm.addState(endState, middle1, new Action("path1-end"));
}
}
public static void main(String[] args) {
String s = "A1@312#";
String digits = "0123456789";
int state = 0;
for (int ind = 0; ind < s.length(); ind++) {
if (state == 0) {
if (s.charAt(ind) == '@')
state = 1;
} else {
boolean isNumber = digits.indexOf(s.charAt(ind)) != -1;
if (state == 1) {
if (isNumber)
state = 2;
else if (s.charAt(ind) == '@')
state = 1;
else
state = 0;
} else if (state == 2) {
if (s.charAt(ind) == '#') {
state = 3;
} else if (isNumber) {
state = 2;
} else if (s.charAt(ind) == '@')
state = 1;
else
state = 0;
} else if (state == 3) {
if (s.charAt(ind) == '@')
state = 1;
else
state = 0;
}
}
} //end for loop
if (state == 3)
System.out.println("It matches");
else
System.out.println("It does not match");
}