文档章节

如何选择使用字符串还是数字呢?

杨尚川
 杨尚川
发布于 2015/08/31 16:17
字数 888
阅读 5887
收藏 114

在我多年的开发经验中,经常发现的一个情况就是,很多项目的对象字段或者是数据库字段本来是数字类型的,却被定义成字符串类型,这无关痛痒吗?

对于小项目来说,可能没什么影响,反正只要业务逻辑正确即可,性能没什么问题,因为数据也不多,用户也不多。

然而,对于大数据处理来说,这个可不是小事,从字符串替换为数字类型,可以极大地节省内存、磁盘存储以及网络带宽,减少IO的代价,而且很多数据结构和算法使用数字类型比字符串要更快。

我们来看一个例子,假设你有很多的日志需要处理,而每条日志都有一个唯一的标识,标识类似这样的格式:

F5051582611729507844
3832154813577306424
F1624235934976711017
3810376634214027595
F6884923813121317381
7278044081826528150

看到这些标识,你怎么想?我的第一反应应该是数字,可是怎么有个F呢?我想可以把它当做16进制。后来发现可以把F当做负号,这就是一个64位的长整型。

那么如果你把这些标识当成字符串,会有什么不同呢?

当然有,如果你每秒要处理这样的日志百万或者千万条,每条处理结果可能会包含百万或者千万个这样的标识元素构成的集合,这个不同就会体现的非常明显。

下面,我们来分析一下标识3832154813577306424的存储占用情况:

1、内存占用

当做字符串:我们知道,JAVA中字符串是由字符构成的,一个字符是由2个字节构成的(这是JAVA的悲剧了),上述标识有19个字符,所以,占用的内存大小为:19*2+4=42(字节),+4是因为字符串使用一个整型保存字符串的哈希值。

当做数字:如当做长整型,则占用的内存大小为8字节。

这里有5倍以上的差距了吧。

2、序列化字节大小

当我们需要通过网络传输这些标识或者需要把这些标识存储到磁盘中的时候,我们就需要把这些标识转换为字节数组,如何转换为字节数组呢?我们可以使用多种编码方式。

当做字符串:我们知道,JAVA中字符串转换为字节数组可以使用多种编码方式,我们看看常见的编码方式对如上字符串编码之后的字节数:

String abc = "3832154813577306424";

System.out.println("3832154813577306424 length:"+abc.length());
System.out.println(Charset.defaultCharset().name()+":"+abc.getBytes().length);
System.out.println("unicode:"+abc.getBytes("unicode").length);
System.out.println("gbk:"+abc.getBytes("gbk").length);
System.out.println("gb2312:"+abc.getBytes("gb2312").length);
System.out.println("ISO-8859-1:"+abc.getBytes("ISO-8859-1").length);

输出如下:

3832154813577306424 length:19
UTF-8:19
unicode:40
gbk:19
gb2312:19
ISO-8859-1:19

当做数字:如当做长整型,则占用的内存大小为8字节。

这里有2倍以上的差距了吧。

那么我们如何在长整型和字节数组之间转换呢?

String abc = "3832154813577306424";

System.out.println("3832154813577306424 length:"+abc.length());
System.out.println("long:"+ByteUtils.longToBytes(Long.parseLong(abc)).length);
byte[] bytes = ByteUtils.longToBytes(Long.parseLong(abc));
System.out.println("string:"+ByteUtils.bytesToLong(bytes));

输出如下:

3832154813577306424 length:19
long:8
string:3832154813577306424

public static byte[] longToBytes(long x) {
    ByteBuffer longBuffer = ByteBuffer.allocate(Long.BYTES);
    longBuffer.putLong(0, x);
    return longBuffer.array();
}
public static long bytesToLong(byte[] bytes) {
    return bytesToLong(bytes, 0, bytes.length);
}
public static long bytesToLong(byte[] bytes, int offset, int length) {
    ByteBuffer longBuffer = ByteBuffer.allocate(Long.BYTES);
    longBuffer.put(bytes, offset, length);
    longBuffer.flip();//need flip
    return longBuffer.getLong();
}


© 著作权归作者所有

杨尚川

杨尚川

粉丝 1102
博文 220
码字总数 1624053
作品 12
东城
架构师
私信 提问
加载中

评论(31)

水湄听雨
水湄听雨
如果仅仅是按照计算和内存来说,我们当然会想到尽量用数字而不是字符串,(个人观点:)但实际中字符串的使用还是相对多一些,因为实际使用的时候要考虑精度、业务扩展等等因素,我们会选择字符串,虽然效率相对低一点,但对硬件来说负担不大,而且能够让程序能够在很长一段时间都能正确的执行。
GavinTop
GavinTop

引用来自“sxgkwei”的评论

有个更重要的问题。你是要速度快呢,还是必须保证数字的正确性。比如钱,我觉得还是保存成字符串比较保险,如果是字符串,就强制了上面写代码的人必须用 BigDecimal 之类的对象来计算加减法和乘除法——当前钱一般都是加减法。那么就能保证算处理toString之后也是正确的数值,你如果存一个数字,再是double来计算,到时候系统就死给你看,领导不批死你。
钱要存 int 型的分值,千万不能float或者double
积少成多
积少成多

引用来自“zenyue”的评论

如果一早确定这就是个数值且范围明确那肯定使用数值类型,但如果不清楚是否完全是数值(比如外来的长得像数值的数据)或范围不明确,使用字符串可以避免挖坑。

就是应该这样
白豆腐徐长卿
白豆腐徐长卿
学习了。
AkataMoKa
AkataMoKa

引用来自“吾爱”的评论

电话号码带有特殊符号"-"
身份证号带有特殊符号"x"
还有一些超长整数,已超出整形范围

以上这些情况是我不得不采用varchar类型的原因,至于存储空间,个别字段就算差10倍对整体影响也几乎忽略不计。

引用来自“杨尚川”的评论

对于普通的应用来说,性能优化的效果可能不明显,但是对于大数据项目来说,那就不一样了,假设你有100台机器构成的集群,你在关键的地方,比如把String换成Long,把内存占用降低到原来的1/5,这样的优化那就是意义非凡了。现在很多公司都使用HDFS来存储大量的数据,对这些数据的分析往往就会涉及这些问题。
HDFS 用于储存对数据,主要是用于 Hadoop 计算吗? 我猜 HDFS 可能不适合比较小的文件(~5M),而且这些文件是需要被外部访问的
杨尚川
杨尚川 博主

引用来自“OSC首席大弟子”的评论

等到硬件大跃进之后,这些性能问题将都不是问题,所以没必要纠结用什么
硬件大跃进之后,软件对硬件的消耗也会随之大跃进,所以,硬件发展的任何阶段,性能优化都是有意义的而且是必要的。
杨尚川
杨尚川 博主

引用来自“learn_more”的评论

就这两个原因就让你丢弃字符串选择数字这种方式!!你们有没有遇到过下级子公司数据同步到总部的时候会出现主键重复的问题?有没有遇到过使用int做javabean属性默认值为0的问题?还有有么有遇到过ID被猜测的漏洞?
当然,这些都是个人意见,也都是把数字放在主键上的使用,所以不喜勿喷!!!!个人经验而已
你说的这些问题跟你是使用字符串或者数字无关,跟你怎么处理事情有关。 当你需要把下级子公司数据同步到总部的时候出现主键冲突,这种情况就是典型的架构设计不合理的问题,有两种解决办法:1、定义服务接口,各自通过接口交互,这样跟你内部如何设计主键没有半点关系。2、使用分布式原子计数器来做递增主键,这样你的主键就不会冲突。至于使用int做javabean属性默认值为0,这个根本就不是问题,问题是你的默认值有没有含义,问题是你在UI如何处理默认值。还有最后ID被猜测的漏洞,这个更不是问题,就算你的ID不是连续的,还是有很多办法找出来的。再说了,总不能没有安全机制吧,就算ID是连续的也不应该有问题。
杨尚川
杨尚川 博主

引用来自“luqy”的评论

UUID现在也不错啊,永不重复。
计算机无法产生真正的随机数,既然如此,那么UUID就做不到100%不重复。
杨尚川
杨尚川 博主

引用来自“朱宏青”的评论

我觉得主要还是业务需求上导致的 比如发票号 这东西是全数字 但是有0打头 而且位数不固定 那么只能存成字符串而不是数字
0打头应该是输出的时候统一格式化的问题,跟数字本身没有关系吧。你在内部存储的时候还是数字,只是在输出格式化的时候,前面不足的位数补0。
杨尚川
杨尚川 博主

引用来自“吾爱”的评论

电话号码带有特殊符号"-"
身份证号带有特殊符号"x"
还有一些超长整数,已超出整形范围

以上这些情况是我不得不采用varchar类型的原因,至于存储空间,个别字段就算差10倍对整体影响也几乎忽略不计。
对于普通的应用来说,性能优化的效果可能不明显,但是对于大数据项目来说,那就不一样了,假设你有100台机器构成的集群,你在关键的地方,比如把String换成Long,把内存占用降低到原来的1/5,这样的优化那就是意义非凡了。现在很多公司都使用HDFS来存储大量的数据,对这些数据的分析往往就会涉及这些问题。
数值类型与字节数组之间的相互转换

我们在上文 如何选择使用字符串还是数字呢? 中阐述了使用数值类型的好处,那么问题来了,如何在数值类型与字节数组之间相互转换呢? 我们先看看单个数值类型和字节数组之间的转换,我们以I...

杨尚川
2015/09/04
366
0
Python入门之你必须了解的基础知识

一:python的安装与一生中的第一个程序 要写python程序必须要安装python啊!我以为是直接写呢,晕。我还是先去看看资料为什么要先安装python。通过查询我知道了,原来计算机程序分几种,1种是...

番茄炒蛋
2011/05/20
11
0
Python入门之你必须了解的基础知识

一:python的安装与一生中的第一个程序 要写python程序必须要安装python啊!我以为是直接写呢,晕。我还是先去看看资料为什么要先安装python。通过查询我知道了,原来计算机程序分几种,1种是...

番茄炒蛋
2011/05/20
9.4K
9
Python 入门之基本数据类型

为什么我要学习Python这门语言呢?其实很简单,我想拓展技术面的同时,尝试更多的方向,可能最后会不了了之,谁知道呢?有可能的话,我会向爬虫和数据分析这个方向走。所以也就开始了我的Pyt...

YJK923
2018/08/17
0
0
漏写的数字 hihoCoder[Offer收割]编程练习赛38/hihoCoder1649

题意: 给一个字符串,字符串的内容是从数字 X 写到数字 Y,中间漏一个数字(一定不是 X),且这些数都在 100 以内,所以可能是一位数也可能是两位数。 思路: 首先,我们知道的是第一个数字...

l_mark
2017/12/04
0
0

没有更多内容

加载失败,请刷新页面

加载更多

可见性有序性,Happens-before来搞定

写在前面 上一篇文章并发 Bug 之源有三,请睁大眼睛看清它们 谈到了可见性/原子性/有序性三个问题,这些问题通常违背我们的直觉和思考模式,也就导致了很多并发 Bug 为了解决 CPU,内存,IO ...

tan日拱一兵
39分钟前
3
0
网络七层模型与TCP/UDP

为了使全球范围内不同的计算机厂家能够相互之间能够比较协调的进行通信,这个时候就有必要建立一种全球范围内的通用协议,以规范各个厂家之间的通信接口,这就是网络七层模型的由来。本文首先...

爱宝贝丶
42分钟前
4
0
Jenkins World 贡献者峰会及专家答疑展位

本文首发于:Jenkins 中文社区 原文链接 作者:Marky Jackson 译者:shunw Jenkins World 贡献者峰会及专家答疑展位 本文为 Jenkins World 贡献者峰会活动期间的记录 Jenkins 15周岁啦!Jen...

Jenkins中文社区
今天
10
0
杂谈:面向微服务的体系结构评审中需要问的三个问题

面向微服务的体系结构如今风靡全球。这是因为更快的部署节奏和更低的成本是面向微服务的体系结构的基本承诺。 然而,对于大多数试水的公司来说,开发活动更多的是将现有的单块应用程序转换为...

liululee
今天
8
0
OSChina 周二乱弹 —— 我等饭呢,你是不是来错食堂了?

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @ 自行车丢了:给主编推荐首歌 《クリスマスの夜》- 岡村孝子 手机党少年们想听歌,请使劲儿戳(这里) @烽火燎原 :国庆快来,我需要长假! ...

小小编辑
今天
1K
14

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部