定义接口
Map.java
// 支持泛型,支持两个键和值
public interface Map<K, V> {
void add(K key, V value);
V remove(K key); // 删除后将key所对应的value返回给用户
boolean contains(K key); // 查询是否包含
V get(K key); // 获取键的值
void set(K key, V newValue); // 修改键值对
int getSize(); // 获取大小
boolean isEmpty(); // 是否为空
}
Map类
LinkedListMap.java
public class LinkedListMap<K, V> implements Map<K, V> {
// 定义节点的内部类
private class Node {
public K key; // key与value成对放在node里
public V value;
public Node next;
// 构造方法
public Node(K key, V value, Node next) {
this.key = key;
this.value = value;
this.next = next;
}
public Node(K key) {
this(key, null, null);
}
public Node() {
this(null, null, null);
}
@Override
public String toString() {
return key.toString() + ":" + value.toString();
}
}
private Node dummyHead; // 虚拟头节点
private int size;
// 构造函数
public LinkedListMap() {
// key,value都为null
dummyHead = new Node();
size = 0;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
private Node getNode(K key) {
// 指向整个链表的第一个节点
Node cur = dummyHead.next;
while (cur != null) {
// 如果cur的键等于要找的键,找到jiedian
if (cur.key.equals(key))
return cur;
cur = cur.next;
}
// 没有找到 返回null
return null;
}
@Override
public boolean contains(K key) {
// 返回找到的节点不为null
return getNode(key) != null;
}
@Override
public V get(K key) {
// 先找到key所对应的节点
Node node = getNode(key);
return node == null ? null : node.value;
}
@Override
public void add(K key, V value) {
// 首先确认一下当前的映射中是否已经有了key key应是唯一的
Node node = getNode(key);
if (node == null) {
// 如果node为空,直接在链表头添加元素
dummyHead.next = new Node(key, value, dummyHead.next);
size++;
}
// 否则映射中已经包含数据对,更新已有key的value
else
node.value = value; // node的value等于用户传来的value
}
@Override
public void set(K key, V newValue) {
// 先寻找是否包含对应的数据对key
Node node = getNode(key);
if (node == null)
// 如果不存在,报错key不存在
throw new IllegalArgumentException(key + "doesn't exist.");
// 如果存在,更新节点的新value
node.value = newValue;
}
@Override
public V remove(K key) {
Node prev = dummyHead;
// 通过循环找到要删除的节点进行标记,利用prev对待删除节点的前一个节点进行标记
while (prev.next != null) {
if (prev.next.key.equals(key))
break;
prev = prev.next;
}
// 将待删除节点存入delNode中,prev的next就是delNode的下一个节点,使delNode.next=null进行断链;
if (prev.next != null) {
Node delNode = prev.next;
prev.next = delNode.next;
delNode.next = null;
size--; // 维护size;
return delNode.value; // 删除后需要将删除节点的值返回去
}
// 没找到删除节点时返回null
return null;
}
}
词频统计测试
编写文件操作类用于对文本进行分词统计
FileOperation.java
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Scanner;
// 文件相关操作
public class FileOperation {
// 读取文件名称为filename中的内容,并将其中包含的所有词语放进words中
public static boolean readFile(String filename, ArrayList<String> words){
if (filename == null || words == null){
System.out.println("filename is null or words is null");
return false;
}
// 文件读取
Scanner scanner;
try {
File file = new File(filename);
if(file.exists()){
FileInputStream fis = new FileInputStream(file);
scanner = new Scanner(new BufferedInputStream(fis), "UTF-8");
scanner.useLocale(Locale.ENGLISH);
}
else
return false;
}
catch(IOException ioe){
System.out.println("Cannot open " + filename);
return false;
}
// 简单分词
// 这个分词方式相对简陋, 没有考虑很多文本处理中的特殊问题
// 在这里只做demo展示用
if (scanner.hasNextLine()) {
String contents = scanner.useDelimiter("\\A").next();
int start = firstCharacterIndex(contents, 0);
for (int i = start + 1; i <= contents.length(); )
if (i == contents.length() || !Character.isLetter(contents.charAt(i))) {
String word = contents.substring(start, i).toLowerCase();
words.add(word);
start = firstCharacterIndex(contents, i);
i = start + 1;
} else
i++;
}
return true;
}
// 寻找字符串s中,从start的位置开始的第一个字母字符的位置
private static int firstCharacterIndex(String s, int start){
for( int i = start ; i < s.length() ; i ++ )
if( Character.isLetter(s.charAt(i)) )
return i;
return s.length();
}
}
main函数
创建数组words存放文本中所有的单词,遍历words将单词按照相应的逻辑存入map中,如果单词不存在map中,添加相应的key与value,这时的key是String类型的单词,词频在第一次添加进map时默认为1;当单词存在于map中时,只获取相应key的value进行+1,更新词频。
// 映射测试 词频统计
public static void main(String[] args) {
System.out.println("Pride and Prejudice");
// 存储书中所有的单词
ArrayList<String> words = new ArrayList<>();
if (FileOperation.readFile("pride-and-prejudice.txt", words)) {
// 先反馈一下书中一共有多少单词
System.out.println("Total words:" + words.size());
// key为单词String,每个单词对应频率值为int型
LinkedListMap<String, Integer> map = new LinkedListMap<>();
for (String word : words) {
// 如果map中存在word,则给它的频率加1,获取到value+1
if (map.contains(word))
map.set(word, map.get(word) + 1);
else
// 如果不存在,则将word添加进map,初始频率默认为1
map.add(word, 1);
}
// 映射中可以看书中一共多少单词
System.out.println("Total different words:" + map.getSize());
// 查看书中出现某一单词出现的词频是多少次
System.out.println("Frequency of PRIDE:" + map.get("pride"));
System.out.println("Frequency of PREJUDICE:" + map.get("prejudice"));
}
}
测试结果
Pride and Prejudice
Total words:125901
Total different words:6530
Frequency of PRIDE:53
Frequency of PREJUDICE:11