ACM-JAVA中使用StreamTokenizer输入纯数字字符串

本文最后更新于:December 8, 2021 pm

积土成山,风雨兴焉;积水成渊,蛟龙生焉;积善成德,而神明自得,圣心备焉。故不积跬步,无以至千里,不积小流无以成江海。齐骥一跃,不能十步,驽马十驾,功不在舍。面对悬崖峭壁,一百年也看不出一条裂缝来,但用斧凿,能进一寸进一寸,能进一尺进一尺,不断积累,飞跃必来,突破随之。

目录

今天做到了一道题,需要读入数字字符串,结果发现用StreamTokenizer不能输入,输出永远都是null。然后就搜了一下,现在整理记录一下。

解决办法

先把解决办法说出来。后面就是讲原理。

1
2
3
4
5
6
cin.ordinaryChars('0', '5') ; //做题时,注意要将5改成9
cin.wordChars('0', '5'); //做题时,注意要将5改成9
cin.nextToken();
String ss = (String)cin.sval;
cout.println(ss);
cout.flush();

大概说一下。两个参数,第二个参数做题时需要写到9。因为不写到9,那么当字符串全是9时,任然会输出null。现在是5,那么输入全是6及其后面数字的字符串就会输出null。还有一点就是,虽然现在只写到了5,但是只要纯数字字符串中有一个在5前面的数字出现,那么5后面的数字也可以出现。也就是,只要在纯数字字符串中出现了范围内的数字,那么再写范围外的数字一样会输出。如下:

1
2
3
4
5
6
7
8
9
10
11
cin.ordinaryChars('0', '5') ;
cin.wordChars('0', '5');
cin.nextToken();
String ss = (String)cin.sval;
cout.println(ss);
cout.flush();

//输入
168976897968
//输出
168976897968

只能用于输入字符串,如果在正常输入数字时没有删除(注释),或者是放在输入数字的语句前面,则数字不能正常输入。

前知

  • ordinaryChar(int ch) - 指定字符在这个tokenizer中保持原义,即只会把当前字符认为普通的字符,不会有其他的语义。

  • ordinaryChars(int low, int hi) - 指定范围内的字符保持语义,同上

  • wordChars(int low, int hi) - 字符low与hi之间的所有字符都被当作为单词的要素。 一个单词是由一个单词要素后面跟着0个或者更多个单词要素或者数字要素。

上面的三个方法需要知道,因为后面就需要用到。在这也列一些其他的基本方法,了解就好。

  • commenChar(int ch) - 指定某个字符为注释字符,此字符之后直到行结尾都被stream tokenizer忽略。

  • eolIsSignificant(boolean flag) - 决定一个行结束符是否被当作一个基本的符号处理,如果是true,则被当作一个基本符号,不当作普通的分隔符,如果是false,则保持原义,即当作普通的分隔符。

  • lineno() - 返回当前流所在的行号。

  • lowerCaseMode(boolean flag) - 决定是否读取一个单词时是否转变成小写。

  • nextToken() - 分析下一个。

  • ordinaryChar(int ch) - 指定字符在这个tokenizer中保持原义,即只会把当前字符认为普通的字符,不会有其他的语义。

  • ordinaryChars(int low, int hi) - 指定范围内的字符保持语义,同上

  • parseNumbers() - 当stream tokenizer遭遇到一个单词为双精度的浮点数时,会把它当作一个数字,而不是一个单词。

  • pushBack() - 回退,会引起下一个nextToken方法返回当前值。

  • quoteChar(int ch) - 指定当前字符为当前tokenizer中的分隔符,在两个符号之间被当作一个字符串解析。

  • resetSyntax() - 重置语法表使所有的字符都被认为是“ordinary”。

  • slashSlashComments(boolean flag) - 如果为true,则//之间的都被认为是注释,反之,不是。

  • slashStartComments(boolean flag) - 如果为true,则//之后到行结尾的所有都被认为是注释,反之,不是。

  • whitespaceChars(int low, int hi) - 字符low与hi之间的所有字符都被当作为空格符,即被认识为tokenzier的分隔符。

  • wordChars(int low, int hi) - 字符low与hi之间的所有字符都被当作为单词的要素。 一个单词是由一个单词要素后面跟着0个或者更多个单词要素或者数字要素。

源码解释

StreamTokenizer源码和解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private StreamTokenizer() {
// 将a-z的字符作为一个普通单词要素,即可以正常将这些字符解析
wordChars('a', 'z');
wordChars('A', 'Z');
// 将ASCII码表中160-255的字符作为一个普通单词要素,即可以正常将这些字符解析。一个单词(String)是通过多个单词要素(char)组成的
wordChars(128 + 32, 255);
// StreamTokenizer默认认为空白符就是分隔符
// 将ASCII码表中0-' '的字符作为空白分隔符,因为StreamTokenizer默认认为空白符就是分隔符,所以这些字符也就变成了分隔符
whitespaceChars(0, ' ');
// 将/字符作为注解符,也就是说当一个token中包括/时,/后面的字符全部不再解析
commentChar('/');
// 指定 " 为分隔符
quoteChar('"');
// 指定 ' 为分隔符
quoteChar('\'');
//当stream tokenizer遭遇到一个单词为双精度的浮点数时,会把它当作一个数字,而不是一个单词
parseNumbers();
}

原因

StreamTokenizer类的使用,这个类的读入非常迅速。但当输入的字符串为数字时,该串不会被读取。原因是,sval读取的是被定界符所包围的单词,单词是由一个单词要素后面跟着0个或者更多个单词要素或者数字要素组成的,而数字不是单词要素。所以我们得先将数字转化为单词要素后再读入。最开始的解决办法就是这么处理的。

拓展

在上面的源码中,要注意区分whitespaceCharsquoteChar,他们两个都是指定分隔符,但是他们两个指定的分隔符是有区别的,whitespaceChars指定的分隔符是空白分隔符。

在用StreamTokenizer做算法题的时候主要就用quoteChar和wordChars两个方法。

  • quoteChar:用来设置分隔符。

  • whitespaceChars:用来设置空白分隔符。

  • wordChars:用来设置单词要素。

指定单词要素

StreamTokenizer默认将/作为注解符,/后面的东西都不会被解析。如果想读取/,就可以直接用

st.wordChars(‘/‘, ‘/‘);

这个方法将/作为一个普通的单词要素,这样/就可以作为一个普通的字符被读取解析了。

如果不想用空格作为分隔符了,也可以用这个方法,这样就可以读入有空的字符串了

st.wordChars(‘ ‘, ‘ ‘);

指定分隔符

如果想设置别的字符作为分隔符,可以使用quoteChar方法来设置

如想让a作为分隔符

st.quoteChar(‘a’);

但是最好还是用

st.whitespaceChars(‘a’, ‘a’);

这个来设置分割符,因为:

使用StreamTokenizer时要注意它默认设置是有很多字符不是单词要素的,所以会读取解析不了,如有需要需要自己用wordChars方法手动设置。我们可以简单的理解为StreamTokenizer只能读取26个英文字母、汉字、数字(只是简单的理解,他其实还可以读入别的字符,只是那些字符在算法题中几乎用不到)。其他的符号都是不可以读入的,需要我们自己用wordChars()方法设置。