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

实践中的重构11_茴香豆的多种写法

阅读更多
鲁迅先生的名文孔乙己中,孔乙己纠结于茴香豆的茴字有多种写法,可惜小孩子不愿意学,可惜了孔乙己的一片良苦用心。
一个功能,不同的程序员的实现,基于各种因素,可能是千差万别的。即使是同一个程序员,一般也会有多种方式来完成该程序的编写。面对各种不同的可能实现方式,挑选出适合当前场景的实现,就变成了程序员责无旁贷的任务。

	/**
	 * 这个是最早的程序版本,把一个字符串转换成一个16进制编码的字符串。
	 * 看到这段代码的时候,第一反应是怎么不用StringBuilder。而是字符串相加。
	 * 其次,这个是一个常用的工具类,每天的调用量很大,性能看上去不怎么好。
	 * */
	public static String str2HexString_0(String str) {
		String ret = "";
		byte[] b = str.getBytes();

		for (int i = 0; i < b.length; i++) {
			String hex = Integer.toHexString(b[i] & 0xFF);

			if (hex.length() == 1) {
				hex = '0' + hex;
			}

			ret += hex.toUpperCase();
		}

		return ret;
	}

	/**
	 * 第一次尝试修改。核心思想是用StringBuilder代替String。展开数字对String的转换,避免调用toUpperCase方法。
	 * */
	public static String str2HexString_1(String str) {

		StringBuilder sb = new StringBuilder(str.length() << 1);

		byte[] data = str.getBytes();

		for (int i = 0; i < data.length; i++) {

			int left = (data[i] & 0xF0) >> 4;
			append(sb, left);

			int right = data[i] & 0x0F;
			append(sb, right);

		}

		return sb.toString();
	}

	private static void append(StringBuilder sb, int i) {
		switch (i) {
		case 0:
			sb.append("0");
			break;
		case 1:
			sb.append("1");
			break;
		case 2:
			sb.append("2");
			break;
		case 3:
			sb.append("3");
			break;
		case 4:
			sb.append("4");
			break;
		case 5:
			sb.append("5");
			break;
		case 6:
			sb.append("6");
			break;
		case 7:
			sb.append("7");
			break;
		case 8:
			sb.append("8");
			break;
		case 9:
			sb.append("9");
			break;
		case 10:
			sb.append("A");
			break;
		case 11:
			sb.append("B");
			break;
		case 12:
			sb.append("C");
			break;
		case 13:
			sb.append("D");
			break;
		case 14:
			sb.append("E");
			break;
		case 15:
			sb.append("F");
			break;
		default:
			break;
		}
	}

	/**
	 * 测试发现,第一次的修改速度提高了。但是code变长了,看起来不是很舒服。 想了想不用switch是可以提高code的简洁程度的。
	 * 于是有了第2次修改。
	 * */
	public static String str2HexString_2(String str) {

		StringBuilder sb = new StringBuilder(str.length() << 1);

		byte[] data = str.getBytes();

		for (int i = 0; i < data.length; i++) {

			int left = (data[i] & 0xF0) >> 4;
			sb.append("0123456789ABCDEF".charAt(left));

			int right = data[i] & 0x0F;
			sb.append("0123456789ABCDEF".charAt(right));

		}

		return sb.toString();
	}

	/** 想了想用数组不用charAt会不会更快呢。于是有了下面的版本。 */
	public static String str2HexString_3(String str) {

		StringBuilder sb = new StringBuilder(str.length() << 1);

		byte[] data = str.getBytes();

		for (int i = 0; i < data.length; i++) {

			int left = (data[i] & 0xF0) >> 4;
			sb.append(digit2string[left]);

			int right = data[i] & 0x0F;
			sb.append(digit2string[right]);

		}

		return sb.toString();
	}

	private static final String[] digit2string = { "0", "1", "2", "3", "4",
			"5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", };

	/**
	 * 测试发现第3次修改的性能不怎么样。而且code不易读了。算是一个失败的修改。
	 * 出去抽了一根烟,突然醒悟到为什么一定要用StringBuilder呢,
	 * 这个程序实质上就是一个对数据的操作。既然是对数据的操作,那就直接操作数据好了。干嘛要多绕一圈呢。 于是有了下面的版本。
	 * */
	public static String str2HexString_4(String str) {

		byte[] oldData = str.getBytes();
		char[] tmpData = new char[oldData.length << 1];

		for (int i = 0; i < oldData.length; i++) {
			int left = (oldData[i] & 0xF0) >> 4;
			tmpData[i << 1] = "0123456789ABCDEF".charAt(left);

			int right = oldData[i] & 0x0F;
			tmpData[(i << 1) + 1] = "0123456789ABCDEF".charAt(right);
		}
		return new String(tmpData);
	}

	/** 网友gdpglc的思路.*/
	public static String str2HexString_5(String str) {

		byte[] oldData = str.getBytes();
		char[] tmpData = new char[oldData.length << 1];

		for (int i = 0; i < oldData.length; i++) {
			int left = (oldData[i] & 0xF0) >> 4;
			tmpData[i << 1] = digit2char[left];

			int right = oldData[i] & 0x0F;
			tmpData[(i << 1) + 1] = digit2char[right];
		}
		return new String(tmpData);
	}

	private static final char[] digit2char = { '0', '1', '2', '3', '4', '5',
			'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', };

	/**
	 * 返回一个随机的字符串。150是基于该程序使用场景的抽样得到的长度。
	 * */
	private static String getRandomString() {
		StringBuilder sb = new StringBuilder();
		Random r = new Random();
		int length = 150 + r.nextInt(50);
		for (int i = 0; i < length; i++) {
			sb.append('a' + r.nextInt(26));
		}
		return sb.toString();
	}

	/**
	 * 随机单元测试。 结果见perfResult.txt。
	 * 
	 * */
	@Test
	public void test() {
		for (int i = 0; i < 100; i++) {
			String inputString = getRandomString();
			String result_0 = str2HexString_0(inputString);
			String result_1 = str2HexString_1(inputString);
			String result_2 = str2HexString_2(inputString);
			String result_3 = str2HexString_3(inputString);
			String result_4 = str2HexString_4(inputString);
			String result_5 = str2HexString_5(inputString);
			Assert.assertEquals(result_0, result_1);
			Assert.assertEquals(result_1, result_2);
			Assert.assertEquals(result_2, result_3);
			Assert.assertEquals(result_3, result_4);
			Assert.assertEquals(result_4, result_5);
			Assert.assertEquals(result_5, result_0);
		}
	}


性能结果报告:

100000次执行的性能结果。
单位ms。

str2HexString_0 132016
str2HexString_1   8906
str2HexString_2   5297
str2HexString_3   7859
str2HexString_4   4203
str2HexString_5   3985
分享到:
评论
27 楼 gdpglc 2010-12-29  
gtssgtss 写道
gdpglc 写道
gtssgtss 写道
这不是重构,这是优化
消除重复代码与消除临时变量才是重构

那为什么eclipse里边的右键菜单里的重构功能,包括了改类名、改方法名等等...


改名太没技术含量啦,直接忽略

就像eclipse自动生成get、set,也能叫写代码?


get set 是javabean的规范,你在这里说eclipse...

不想和你争论什么叫技术含量,我只希望我的代码,不会给后来人带来麻烦... 其实也包括我自已,因为我记不住两周以前写的代码,需要修改就得重看代码。

gtssgtss 写道
。。。原来大家认为文学水平也算计算机水平么。。。

你不经意见好象说了一句真话。你自已写个文档试试别人能不能看懂就知道了...



26 楼 gtssgtss 2010-12-29  
。。。原来大家认为文学水平也算计算机水平么。。。
25 楼 zhang_xzhi_xjtu 2010-12-29  
改名太没技术含量!
老大难问题,名字一直都是一个系统清晰的关键。
24 楼 gtssgtss 2010-12-29  
gdpglc 写道
gtssgtss 写道
这不是重构,这是优化
消除重复代码与消除临时变量才是重构

那为什么eclipse里边的右键菜单里的重构功能,包括了改类名、改方法名等等...


改名太没技术含量啦,直接忽略

就像eclipse自动生成get、set,也能叫写代码?
23 楼 gdpglc 2010-12-28  
gtssgtss 写道
这不是重构,这是优化
消除重复代码与消除临时变量才是重构

那为什么eclipse里边的右键菜单里的重构功能,包括了改类名、改方法名等等...
22 楼 gtssgtss 2010-12-28  
这不是重构,这是优化
消除重复代码与消除临时变量才是重构
21 楼 zhang_xzhi_xjtu 2010-12-28  
可读性因人而异,性能可以用数据说话。
20 楼 gdpglc 2010-12-28  
tcgdy0201 写道
用hashmap代替数组,无论是代码可读性还是效率都比数组高

HashMap 采用的是散列表,散列表是用一个复杂的算法定位元素,而数组,是在一个连续的地址空间里,通过简单的地址记算查找元素,你的结论是怎么得出来的,要不要象楼主一样,做做测试,用数据说话。
19 楼 tcgdy0201 2010-12-28  
用hashmap代替数组,无论是代码可读性还是效率都比数组高
18 楼 kinjo 2010-12-24  
gdpglc 写道
"0123456789ABCDEF".charAt(left)
地方还可以优化
char[] chs={'0','1'........'F'}
chs[left]

不过我一定不会这么用。我宁可用Character.forDigit方法,这样的代码可读性太差了。这么在乎性能的理由是什么?你在写系统软件吗?

把乘2写成<<1,唉。我觉得真是精力过剩了..........

这哪能叫重构.... 整个一个反重构




乘2写成<<1  其实我也经常这样写,如果追求性能的话呵呵,但是就是可读性差很多,可能需要注释下
17 楼 mfkvfn 2010-12-20  
peak 写道
switch 这个方法用的实在是太丑陋了,换成一个hashmap,每次传进一个key取value多好啊


hashMap性能不好。用hashMap还不如用switch
16 楼 zhmiao 2010-12-20  
写的真是不错,这几天正好也需要用到。
15 楼 peak 2010-12-20  
switch 这个方法用的实在是太丑陋了,换成一个hashmap,每次传进一个key取value多好啊
14 楼 zhang_xzhi_xjtu 2010-12-19  
gdpglc 写道
这个代码存在一个潜在的bug。
byte[] data = str.getBytes(); 

这行代码。String的getBytes方法是和当前java程序的默认字符集相关的。
我想编码的东西最终还是要还原的,如果还原时的字符集和编码时的不同,则会产生乱码。


没错,这个是一个潜在的bug,这个是在早期架构的时候,目光有局限性。
一直以来,所有部署的机器都是同一个默认字符集,所以用部署的方法弥补了这个问题。

系统中所有编码相关的地方基本上都有这个问题。
13 楼 gdpglc 2010-12-18  
这个代码存在一个潜在的bug。
byte[] data = str.getBytes(); 

这行代码。String的getBytes方法是和当前java程序的默认字符集相关的。
我想编码的东西最终还是要还原的,如果还原时的字符集和编码时的不同,则会产生乱码。
12 楼 faye.feelcool 2010-12-18  
本来就是一个茴字怎么写的,纯技术的讨论。

不过我补充一句:不管是可读性,和高性能都是有目的的,如果你所谓目的的前提不存在,所作的一切都是枉然。

就像我花5元要碗牛肉面,你给我满汉全席,我不会不好意思,但我不会多给你超过5元钱。
11 楼 yxbwzx 2010-12-18  
三人行,必有吾师,总能在别人身上学到益处
10 楼 zhang_xzhi_xjtu 2010-12-18  
pop1030123 写道
这个类还有什么用处?还可以用来在字符编码间转码吧?

算是一个简单的编码,和base64相似,但是没有base64紧凑。
9 楼 zhang_xzhi_xjtu 2010-12-18  
To gdpglc
不好意思,理解错了你说的代码的位置。
主贴已经更新。

这个只是性能提高的一个方面。
    public char charAt(int index) {
        if ((index < 0) || (index >= count)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index + offset];
    }


还有一个方面是因为append的不同实现。
    public AbstractStringBuilder append(String str) {
	if (str == null) str = "null";
        int len = str.length();
	if (len == 0) return this;
	int newCount = count + len;
	if (newCount > value.length)
	    expandCapacity(newCount);
	str.getChars(0, len, value, count);
	count = newCount;
	return this;
    }

    public AbstractStringBuilder append(char c) {
        int newCount = count + 1;
	if (newCount > value.length)
	    expandCapacity(newCount);
	value[count++] = c;
	return this;
    }
8 楼 zhang_xzhi_xjtu 2010-12-18  
gdpglc 写道
zhang_xzhi_xjtu 写道
gdpglc 写道
"0123456789ABCDEF".charAt(left)
地方还可以优化
char[] chs={'0','1'........'F'}
chs[left]

不过我一定不会这么用。我宁可用Character.forDigit方法,这样的代码可读性太差了。这么在乎性能的理由是什么?你在写系统软件吗?

把乘2写成<<1,唉。我觉得真是精力过剩了..........

这哪能叫重构.... 整个一个反重构


你的改法和我的方法3没有本质区别,但是性能结果不理想。

这个过程也许有一点点过。但是反重构我是不同意的。
软件本身就不是一个简单的事情。看到<<或者>>如果你觉得不舒服我只能说个人的理解不一样。
这个类一看就知道应该是底层协议用的,当然是比较底层的。


1)"0123456789ABCDEF".charAt(left)
2)char[] chs={'0','1'........'F'}

charAt源码:
public char charAt(int index) {
        if ((index < 0) || (index >= count)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index + offset];
    }

这两行代码的效率是不一样的。因为1)的代码比2)的代码多了一个方法调用、参数传递还有里边判断的过程。

这个的底层协议,导致数据长度比原来增加了一倍,这个方案本身就是粗糙的,然后拼命的提高代码效率...

个人喜好吧


我说了和3本质是一样的,麻烦能不能看看3.

相关推荐

Global site tag (gtag.js) - Google Analytics