`
MouseLearnJava
  • 浏览: 460394 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

中文排序

    博客分类:
  • Java
阅读更多

对于一个数组或者列表或者集合的元素进行排序是一个比较常用的需求。现有的Java类库也提供了API来实现这样的功能,比如Arrays.sort以及Collections.sort的方法。另外,我们也可以用Collator来实现中文的排序。

package demo;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

public class TestCollator {

	public static void main(String[] args) {

		List<String> list = new ArrayList<String>();
		list.add("中");
		list.add("文");
		list.add("拼");
		list.add("音");
		list.add("鑫");
		list.add("犇");

		Collections.sort(list, Collator.getInstance(Locale.CHINA));

		for(String ele: list)
		{
			System.out.println(ele);
		}

	}
}


输出的结果是:







中文排序后的结果,并不完全正确。

这是因为Java使用的是Unicode编码,而常用的中文编码是GB2312,它包含了7000个字符集而且是按照拼音排序的,也是连续的。GB18030和GBK都是在此基础上扩展起来的,这样就会造成Unicode的不连续性,最终导致对有些中文字符排序不完全正确的结果。


为了能够更好地对中文进行排序,我们可以采用拼音或者笔画来对中文进行排序。本文采用了拼音来排序。将汉语转换成拼音的开源项目有PinYin4j,下载的地址如下:

http://sourceforge.net/projects/pinyin4j/files/pinyin4j-2.5.0/pinyin4j-2.5.0/pinyin4j-2.5.0.zip/download

编写汉字转换成拼音的工具类

package demo;

import java.io.UnsupportedEncodingException;
import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;

public final class PinYinUtils {

	private PinYinUtils() {
	}
	
	/**
	 * 判断一个字符是否是中文字符
	 */
	private static boolean isChineseCharacter(char c) {
		return String.valueOf(c).matches("[\\u4E00-\\u9FA5]+");
	}

	/**
	 * 将一个含有中文的字符串转换成拼音。
	 * Note: 这里只是将中文转换成拼音,其它的各种字符将保持原来的样子。
	 */
	public static String populatePinYing(String aChineseValue) {

		if (null == aChineseValue) {
			return null;
		}

		StringBuilder sb = new StringBuilder();
		char[] charArray = aChineseValue.toCharArray();

		HanyuPinyinOutputFormat outputFormat = new HanyuPinyinOutputFormat();
		outputFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
		outputFormat.setVCharType(HanyuPinyinVCharType.WITH_V);
		outputFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);

		for (int i = 0; i < charArray.length; i++) {
			if (isChineseCharacter(charArray[i])) {
				try {
					sb.append(PinyinHelper.toHanyuPinyinStringArray(
							charArray[i], outputFormat)[0]);
				} catch (BadHanyuPinyinOutputFormatCombination e) {
					e.printStackTrace();
				}
			} else {
				sb.append(charArray[i]);
			}
		}
		return sb.toString();
	}

	/**
	 * 将一个含有中文的字符串转换成拼音。
	 * Note: 这里只是将中文转换成拼音,其它的各种字符将保持原来的样子。
	 * 
	 * 在這裡多了一個參數needToCorrectSpelling,这个主要用于调整姓氏的发音。
	 * 如 '单', 这个字作为姓氏念 shan, 但同时也有dan的发音等。
	 * 
	 * 为了保证姓氏发音的正确性,将了这个参数和相关的简单逻辑
	 */
	public static String populatePinYing(String aChineseValue,
			boolean needToCorrectSpelling) {

		if (null == aChineseValue) {
			return null;
		}

		StringBuilder sb = new StringBuilder();
		char[] charArray = aChineseValue.toCharArray();

		HanyuPinyinOutputFormat outputFormat = new HanyuPinyinOutputFormat();
		outputFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
		outputFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
		outputFormat.setVCharType(HanyuPinyinVCharType.WITH_V);

		String surname = null;
		for (int i = 0; i < charArray.length; i++) {
			if (isChineseCharacter(charArray[i])) {
				try {
					if (needToCorrectSpelling && 0 == i) {
						surname = SurnameDictionary
								.populateCorrectSpelling(charArray[i]);
						if (null == surname) {
							sb.append(PinyinHelper.toHanyuPinyinStringArray(
									charArray[i], outputFormat)[0]);
						} else {
							sb.append(surname);
						}
					} else {
						sb.append(PinyinHelper.toHanyuPinyinStringArray(
								charArray[i], outputFormat)[0]);
					}

				} catch (BadHanyuPinyinOutputFormatCombination e) {
					e.printStackTrace();
				}
			} else {
				sb.append(charArray[i]);
			}
		}
		return sb.toString();
	}

	public static void main(String[] args) throws UnsupportedEncodingException {
		String x = "拼音4j%^**Cool12568 カ キ ";
		System.out.println(populatePinYing(x));
	}
}



本文的一个简单例子是对名字进行排序,所以下面创建两个类 User 和 NameComparator
package demo;

import java.io.Serializable;

public class User implements Serializable {

	private static final long serialVersionUID = -1221268239932915488L;

	private String name;

	private int age;

	/**
	 * @param name
	 * @param age
	 */
	public User(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public User() {

	}

	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * @param name
	 *            the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * @return the age
	 */
	public int getAge() {
		return age;
	}

	/**
	 * @param age
	 *            the age to set
	 */
	public void setAge(int age) {
		this.age = age;
	}

}


package demo;

import java.util.Comparator;

public class NameComparator implements Comparator<User> {

	public int compare(User u1, User u2) {

		String name1 = u1.getName();
		String name2 = u2.getName();

		return PinYinUtils.populatePinYing(name1).compareTo(
				PinYinUtils.populatePinYing(name2));
	}

}


测试代码如下:

package demo;

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

public class Test {

	public static void main(String[] args) {

		run();
	}

	private static void run() {
		List<User> list = prepareTestUserList();

		printListBeforeSorting(list);

		Collections.sort(list, new NameComparator());

		printListAfteSorting(list);
	}

	private static void printListAfteSorting(List<User> list) {
		System.out.println("After sorting.....");
		printList(list);
	}

	private static void printListBeforeSorting(List<User> list) {
		System.out.println("Before sorting.....");
		printList(list);
	}

	private static void printList(List<User> list) {
		for (User user : list) {
			System.out.println(user.getName());
		}
	}

	private static List<User> prepareTestUserList() {
		List<User> list = new ArrayList<User>();
		User u = new User();
		u.setName("张三");
		u.setAge(21);
		list.add(u);

		u = new User();
		u.setName("李四");
		u.setAge(18);
		list.add(u);

		u = new User();
		u.setName("王五");
		u.setAge(25);
		list.add(u);

		u = new User();
		u.setName("鑫鑫");
		u.setAge(89);
		list.add(u);

		u = new User();
		u.setName("范范");
		u.setAge(89);
		list.add(u);

		u = new User();
		u.setName("单一号");
		u.setAge(89);
		list.add(u);

		u = new User();
		u.setName("犇犇");
		u.setAge(89);
		list.add(u);

		return list;
	}

}


Before sorting.....
张三
李四
王五
鑫鑫
范范
单一号
犇犇
After sorting.....
犇犇
单一号
范范
李四
王五
鑫鑫
张三

从上述的排序结果,可以看出“单一号”并没有在一个准确的位置上。产生这种情况是因为中文存在多音字,而且姓氏中的发音会不一样。如“单”在姓氏中需要发“shan”,用PinYin4j能够返回多音字,因为不知道哪一个正确,我在代码中返回了第一个 “dan”。
为了能够保证姓氏发声的正确性,个人想到的有三种:1. 数据库中存储姓氏发音的表
2. 将姓氏的发音存储在一个key-value的properties文件中
3. 创建一个姓氏发音的字典类

本文简单采用第三种方式来实现:


package demo;


public class SurnameDictionary {

	public static String populateCorrectSpelling(char surname) {

		//TODO:
		if ('单' == surname) {
			return "shan";
		}

		return null;
	}
	
}



比较器中加入参数,将SurnameDictionary运用上去。

package demo;

import java.util.Comparator;

public class NameComparator implements Comparator<User> {

	public int compare(User u1, User u2) {

		String name1 = u1.getName();
		String name2 = u2.getName();

		return PinYinUtils.populatePinYing(name1, true).compareTo(
				PinYinUtils.populatePinYing(name2, true));
	}

}


重新运行结果,我们将得到正确的名字排序结果:

Before sorting.....
张三
李四
王五
鑫鑫
范范
单一号
犇犇
After sorting.....
犇犇
范范
李四
单一号
王五
鑫鑫
张三

3
2
分享到:
评论
4 楼 cb_0312 2016-06-22  
SurnameDictionary文章我没看完,现在懂了
3 楼 cb_0312 2016-06-22  
SurnameDictionary.populateCorrectSpelling(charArray[i])

这个类是什么?我怎么找不到相关信息?
2 楼 MouseLearnJava 2013-07-17  
oaklet 写道
只能做到这一步了,再向下没法做了,毕竟有些怪字音不确定,非人力所能为,例如
常见的姓氏用字异读有:
  重:Chóng 音崇;
  区:ōu 音欧;
  仇:Qiú 音求;
  秘:Bì 音闭;
  冼:Xiǎn 音显;
  解:Xiè 音谢;
  折:Shè 音舌;
  单:Shàn 音善;
  朴:Piáo 音瓢;
  翟:Zhá 音宅;
  查:Zhā 音渣;
  万俟:Mò qí 音莫奇;
  尉迟:Yù chí 音玉迟;等等。
  还有一些姓氏用字,一字两音,同一个作为姓氏的汉字,由于有两个读音,就代表了两个不同的姓,而且不一源流。如:“乐”姓,北方读音与“月”字同,而南方读音则与“勒”字同音;“召”姓,汉族人读者作“少”,而傣族人则读作“赵”;原籍在中原一带的“罩”姓,读音作“谈”,而注籍两广或壮族的则读音与“秦”字同。这些同字不同音者,分别表示不同的姓,不能认为是同一姓氏在不同地区、不同族属的不同读法。


非常感谢你的评论。 姓氏用字异读方面,个人觉得能够通过建立一个发音词典库等方式加以改进。像姓氏一字多读的情况,发什么音不确定性太大,不好做。

在中文排序(如名字)上,尽可能考虑到姓氏异读的情况,对姓氏一字多音的情况保持一颗宽容的心吧。

其他博友有好的建议或意见,也请一起分享吧
1 楼 oaklet 2013-07-16  
只能做到这一步了,再向下没法做了,毕竟有些怪字音不确定,非人力所能为,例如
常见的姓氏用字异读有:
  重:Chóng 音崇;
  区:ōu 音欧;
  仇:Qiú 音求;
  秘:Bì 音闭;
  冼:Xiǎn 音显;
  解:Xiè 音谢;
  折:Shè 音舌;
  单:Shàn 音善;
  朴:Piáo 音瓢;
  翟:Zhá 音宅;
  查:Zhā 音渣;
  万俟:Mò qí 音莫奇;
  尉迟:Yù chí 音玉迟;等等。
  还有一些姓氏用字,一字两音,同一个作为姓氏的汉字,由于有两个读音,就代表了两个不同的姓,而且不一源流。如:“乐”姓,北方读音与“月”字同,而南方读音则与“勒”字同音;“召”姓,汉族人读者作“少”,而傣族人则读作“赵”;原籍在中原一带的“罩”姓,读音作“谈”,而注籍两广或壮族的则读音与“秦”字同。这些同字不同音者,分别表示不同的姓,不能认为是同一姓氏在不同地区、不同族属的不同读法。

相关推荐

Global site tag (gtag.js) - Google Analytics