StreamTokenizer
Макар и StreamTokenizer да не е извлечен от InputStream или OutputStream, той работи само с InputStream обекти, така че направо си принадлежи към IO частта на библиотеката.
Класът StreamTokenizer се използва за разбиването на InputStream последователност от “tokens,” които са парчета текст разделени с нещо по ваш избор. Например вашите токен могат да бъдат думи, а те могат да бъдат разделени с интервали и пунктуация.
Да видим програма за броенето на появата на думи в текст:
//: c10:SortedWordCount.java
// Counts words in a file, outputs
// results in sorted form.
import java.io.*;
import java.util.*;
import c08.*; // Contains StrSortList
class Counter {
private int i = 1;
int read() { return i; }
void increment() { i++; }
}
public class SortedWordCount {
private FileReader file;
private StreamTokenizer st;
private HashMap counts = new HashMap();
SortedWordCount(String filename)
throws FileNotFoundException {
try {
file = new FileReader(filename);
st = new StreamTokenizer(new BufferedReader(file));
st.ordinaryChar('.');
st.ordinaryChar('-');
} catch(FileNotFoundException e) {
System.out.println(
"Could not open " + filename);
throw e;
}
}
void cleanup() {
try {
file.close();
} catch(IOException e) {
System.out.println(
"file.close() unsuccessful");
}
}
void countWords() {
try {
while(st.nextToken() !=
StreamTokenizer.TT_EOF) {
String s;
switch(st.ttype) {
case StreamTokenizer.TT_EOL:
s = new String("EOL");
break;
case StreamTokenizer.TT_NUMBER:
s = Double.toString(st.nval);
break;
case StreamTokenizer.TT_WORD:
s = st.sval; // Already a String
break;
default: // single character in ttype
s = String.valueOf((char)st.ttype);
}
if(counts.containsKey(s))
((Counter)counts.get(s)).increment();
else
counts.put(s, new Counter());
}
} catch(IOException e) {
System.out.println(
"st.nextToken() unsuccessful");
}
}
Collection values() {
return counts.values();
}
Set keySet() { return counts.keySet(); }
Counter getCounter(String s) {
return (Counter)counts.get(s);
}
Iterator sortedKeys() {
Iterator e = counts.keySet().iterator();
StrSortList sv = new StrSortList();
while(e.hasNext())
sv.add((String)e.next());
// This call forces a sort:
return sv.iterator();
}
public static void main(String[] args) {
try {
SortedWordCount wc =
new SortedWordCount(args[0]);
wc.countWords();
Iterator keys = wc.sortedKeys();
while(keys.hasNext()) {
String key = (String)keys.next();
System.out.println(key + ": "
+ wc.getCounter(key).read());
}
wc.cleanup();
} catch(Exception e) {
e.printStackTrace();
}
}
} ///:~
Има смисъл да бъдат сортирани, но доколкото Java 1.0 и Java 1.1 нямат никакви сортировки, това ще трябва да се направи. Лесно става с StrSortList. (Беше създаден в глава 8 и е част от пакета създаден в нея глава. Помнете че началната директория на поддиректориите на примерите от тази книга трябва да е на вашия "път до класовете" за да може те да се компилират.)
За отваряне на файла се използва FileInputStream , а за превръщането му в токени се създава StreamTokenizer от FileInputStream. В StreamTokenizer има списък от разделители по подразбиране, а вие може да добавите още чрез методи. Тука е използван ordinaryChar( ) за да каже “Този знак не е интересен за мен,” така че парсерът не го включва изобщо в думите. Например казвайки st.ordinaryChar('.') ще получим точките да не се включват в състава на думите които се разделят. Повече информация може да намерите в онлайн документацията която идва с Java.
В countWords( ) токените се изтеглят един по един от потока, а ttype информацията се използва за да се реши какво да се прави с всеки, тъй като токенът може да бъде край на файл, число, стринг или единствен знак.
Щом се намери токенът, пита се HashMap counts дали вече го съдържа като ключ. Ако да, съответният Counter обект се инкрементира за да покаже, че е намерен още един случай на тази дума. Ако не, нов Counter се създава – и доколкото конструкторът на Counter се инициализира с единица, това отразява и първата поява.
SortedWordCount не е тип HashMap, така че не е наследен. Той изполнява специфична функция, та макар и keys( ) и values( ) методите трябва да ги има, това още не значи че ще се използва наследяване понеже много от HashMap методите не стават тук. Освен това други методи като getCounter( ), които вземат Counter за конкретен String и sortedKeys( ), който дава Iterator, завършват промяната на интерфейса на SortedWordCount.
В main( ) може да се види използването на SortedWordCount за отваряне и броене на думи във файл – това са точно два реда код. После се извлича Iterator към сортирания списък на ключове (думите) и се използва за измъкване на съответния ключ и броя на срещанията му Count. Забележете че викането на cleanup( )е необходимо за да се осигури затварянето на файла.
Втори пример с използване на StreamTokenizer има в глава 17.
StringTokenizer
Макар и да не е част от IO библиотеката, StringTokenizer има достатъчно подобна на StreamTokenizer функционалност така че ще се опише тук.
StringTokenizer връща токените в стринг един по един. Тези токени са последователни знаци разделени с табулации, интервали и знаци за нов ред. Така токените в стринга “Where is my cat?” са “Where”, “is”, “my” и “cat?” Като при StreamTokenizer,вие може да поръчате на StringTokenizer да разделя входния стринг по всякакъв желан начин, но при StringTokenizer това се прави чрез даване на втори аргумент на конструктора, който е String от желаните разделители. Изобщо, ако ви трябва по-усъвършенстван начин, използвайте StreamTokenizer.
Следващия токен се иска от StringTokenizer обекта чрез метода nextToken( ), който връща или токена, или празен стринг за да покаже че няма повече токени.
Като пример следната програма изпълнява ограничен анализ на изречение, оглеждайки за ключови фрази за да разбере дали се предполага радост или тъга.
//: c10:AnalyzeSentence.java
// Look for particular sequences
// within sentences.
import java.util.*;
public class AnalyzeSentence {
public static void main(String[] args) {
analyze("I am happy about this");
analyze("I am not happy about this");
analyze("I am not! I am happy");
analyze("I am sad about this");
analyze("I am not sad about this");
analyze("I am not! I am sad");
analyze("Are you happy about this?");
analyze("Are you sad about this?");
analyze("It's you! I am happy");
analyze("It's you! I am sad");
}
static StringTokenizer st;
static void analyze(String s) {
prt("\nnew sentence >> " + s);
boolean sad = false;
st = new StringTokenizer(s);
while (st.hasMoreTokens()) {
String token = next();
// Look until you find one of the
// two starting tokens:
if(!token.equals("I") &&
!token.equals("Are"))
continue; // Top of while loop
if(token.equals("I")) {
String tk2 = next();
if(!tk2.equals("am")) // Must be after I
break; // Out of while loop
else {
String tk3 = next();
if(tk3.equals("sad")) {
sad = true;
break; // Out of while loop
}
if (tk3.equals("not")) {
String tk4 = next();
if(tk4.equals("sad"))
break; // Leave sad false
if(tk4.equals("happy")) {
sad = true;
break;
}
}
}
}
if(token.equals("Are")) {
String tk2 = next();
if(!tk2.equals("you"))
break; // Must be after Are
String tk3 = next();
if(tk3.equals("sad"))
sad = true;
break; // Out of while loop
}
}
if(sad) prt("Sad detected");
}
static String next() {
if(st.hasMoreTokens()) {
String s = st.nextToken();
prt(s);
return s;
}
else
return "";
}
static void prt(String s) {
System.out.println(s);
}
} ///:~
За да бъде анализиран всеки стринг се влиза в while цикъл и токените се изваждат от стринга. Забележете първия if оператор, който казва да continue (да отиде в началото на цикъла и да влезе в него пак) ако токенът на е нито “I” нито “Are.” Това значи че ще взимате токени досато биват намирани “I” или “Are”. Бихте могли да искате да използвате == вместо equals( ) метода, но това не би работило коректно, понеже == сравнява стойности на манипулатори, докато equals( ) сравнява съдържанията.
Логиката на останалата част от метода analyze( ) е че се търси за “I am sad,” “I am not happy” или “Are you sad?” Без break оператора кодът би бил даже по-объркан отколкото е. Така че да сте предупредени че типичен парсер (този е елементарен пример за такъв) нормално има таблица за тези токени и код който се движи по нея, като си променя състоянието в зависимост от появата на нови токени .
Ще считате StringTokenizer само за кратък път към един опростен StreamTokenizer. Обаче ако имате String който искате да разделите и StringTokenizer е твърде ограничен, всичко което трябва да направите е да го превърната в поток със StringBufferInputStream и после да създадете с него много по-мощния StreamTokenizer.
Сподели с приятели: |