开发过程中,未意识到文件可能大于int的最大值,所以埋下了隐患。今天被测试提了个bug。主要原因是
QJsonValue
的
toInt()
返回的是4字节的
int
类型,拿来存一个大于有符号四字节整数的值会溢出。这种边界值界定不明确导致bug的情况还是比较常见,做个总结分享帮助大家避坑。
此外就是csdn上对于这块的教程属实是又少又粗略。
* 本程序是测试Qt 程序存储64位整数 至Json文件 并加载读出
#
include
<QtCore/QCoreApplication>
#
include
<QJsonObject>
#
include
<QJsonValue>
#
include
<QDebug>
#
include
<QDir>
#
include
<QJsonDocument>
#
include
<QByteArray>
#
include
<iostream>
#
include
<climits>
#
include
<cfloat>
#
include
<iomanip>
void
printTypeInfo
(
)
;
Type Size(bytes) Minimum Maximum
------------------------------------------------------------------------------------------------------------
bool 1
char 1 -128 127
int 4 -2147483648 2147483647
uint 4 4294967295
short 2 -32768 32767
ushort 2 65535
long 4 -2147483648 2147483647
ulong 4 4294967295
ll 8 -9223372036854775808 9223372036854775807
ull 8 18446744073709551615
float 4 1.17549e-038 3.40282e+038
double 8 2.22507e-308 1.79769e+308
ldouble 8 2.22507e-308 1.79769e+308
------------------------------------------------------------------------------------------------------------
int
main
(
int
argc
,
char
*
argv
[
]
)
QCoreApplication
a
(
argc
,
argv
)
;
using namespace std
;
* 第一部分、往文件中写
QJsonObject root
;
QJsonObject obj
;
obj
[
"path"
]
=
"YangNaifeng"
;
qint64 llData
=
1
;
obj
[
"size"
]
=
llData
;
root
.
insert
(
"fileList"
,
obj
)
;
QJsonDocument doc
;
doc
.
setObject
(
root
)
;
auto
path
=
QCoreApplication
::
applicationDirPath
(
)
;
auto
outJson
=
path
+
"/"
+
"sad.json"
;
QDir
dir
(
path
)
;
if
(
dir
.
mkpath
(
path
)
)
QFile
file
(
outJson
)
;
file
.
open
(
QIODevice
::
WriteOnly
)
;
file
.
write
(
doc
.
toJson
(
)
)
;
file
.
close
(
)
;
qCritical
(
)
<<
QString
::
fromStdWString
(
L
"创建文件夹失败!"
)
;
* 从文件中读 并解析
QFile
readFile
(
outJson
)
;
readFile
.
open
(
QIODevice
::
ReadOnly
)
;
QByteArray data
=
readFile
.
readAll
(
)
;
QJsonDocument docRead
=
QJsonDocument
::
fromJson
(
data
)
;
if
(
!
docRead
.
isNull
(
)
&&
docRead
.
isObject
(
)
)
{
QJsonObject obj
=
docRead
.
object
(
)
;
if
(
obj
.
contains
(
"fileList"
)
&&
obj
[
"fileList"
]
.
isObject
(
)
)
{
QJsonObject fileListObj
=
obj
[
"fileList"
]
.
toObject
(
)
;
QString path
=
fileListObj
[
"path"
]
.
toString
(
)
;
auto
eDoubleType
=
QJsonValue
::
Double
;
cout
<<
fileListObj
[
"size"
]
.
type
(
)
;
auto
size
=
fileListObj
[
"size"
]
.
toDouble
(
)
;
qint64 nSize
=
QString
::
number
(
size
,
'f'
,
0
)
.
toLongLong
(
)
;
qDebug
(
)
<<
"Path:"
<<
path
;
qDebug
(
)
<<
"Size:"
<<
size
;
qDebug
(
)
<<
"fileList not found"
;
qDebug
(
)
<<
"Invalid JSON document"
;
return
a
.
exec
(
)
;
void
printTypeInfo
(
)
using namespace std
;
cout
<<
left
;
cout
<<
setw
(
8
)
<<
"Type"
;
cout
<<
setw
(
22
)
<<
"Size(bytes)"
;
cout
<<
setw
(
22
)
<<
"Minimum"
;
cout
<<
setw
(
22
)
<<
"Maximum"
;
cout
<<
endl
;
cout
<<
"------------------------------------"
;
cout
<<
"------------------------------------"
;
cout
<<
"------------------------------------"
;
cout
<<
endl
;
cout
<<
setw
(
8
)
<<
"bool"
;
cout
<<
setw
(
22
)
<<
sizeof
(
bool
)
;
cout
<<
setw
(
22
)
;
cout
<<
setw
(
22
)
;
cout
<<
endl
;
cout
<<
setw
(
8
)
<<
"char"
;
cout
<<
setw
(
22
)
<<
sizeof
(
char
)
;
cout
<<
setw
(
22
)
<<
(
int
)
CHAR_MIN
;
cout
<<
setw
(
22
)
<<
(
int
)
CHAR_MAX
;
cout
<<
endl
;
cout
<<
setw
(
8
)
<<
"int"
;
cout
<<
setw
(
22
)
<<
sizeof
(
int
)
;
cout
<<
setw
(
22
)
<<
INT_MIN
;
cout
<<
setw
(
22
)
<<
INT_MAX
;
cout
<<
endl
;
cout
<<
setw
(
8
)
<<
"uint"
;
cout
<<
setw
(
22
)
<<
sizeof
(
unsigned
int
)
;
cout
<<
setw
(
22
)
;
cout
<<
setw
(
22
)
<<
UINT_MAX
;
cout
<<
endl
;
cout
<<
setw
(
8
)
<<
"short"
;
cout
<<
setw
(
22
)
<<
sizeof
(
short
)
;
cout
<<
setw
(
22
)
<<
SHRT_MIN
;
cout
<<
setw
(
22
)
<<
SHRT_MAX
;
cout
<<
endl
;
cout
<<
setw
(
8
)
<<
"ushort"
;
cout
<<
setw
(
22
)
<<
sizeof
(
unsigned
short
)
;
cout
<<
setw
(
22
)
;
cout
<<
setw
(
22
)
<<
USHRT_MAX
;
cout
<<
endl
;
cout
<<
setw
(
8
)
<<
"long"
;
cout
<<
setw
(
22
)
<<
sizeof
(
long
)
;
cout
<<
setw
(
22
)
<<
LONG_MIN
;
cout
<<
setw
(
22
)
<<
LONG_MAX
;
cout
<<
endl
;
cout
<<
setw
(
8
)
<<
"ulong"
;
cout
<<
setw
(
22
)
<<
sizeof
(
unsigned
long
)
;
cout
<<
setw
(
22
)
;
cout
<<
setw
(
22
)
<<
ULONG_MAX
;
cout
<<
endl
;
cout
<<
setw
(
8
)
<<
"ll"
;
cout
<<
setw
(
22
)
<<
sizeof
(
long
long
)
;
cout
<<
setw
(
22
)
<<
LLONG_MIN
;
cout
<<
setw
(
22
)
<<
LLONG_MAX
;
cout
<<
endl
;
cout
<<
setw
(
8
)
<<
"ull"
;
cout
<<
setw
(
22
)
<<
sizeof
(
unsigned
long
long
)
;
cout
<<
setw
(
22
)
;
cout
<<
setw
(
22
)
<<
ULLONG_MAX
;
cout
<<
endl
;
cout
<<
setw
(
8
)
<<
"float"
;
cout
<<
setw
(
22
)
<<
sizeof
(
float
)
;
cout
<<
setw
(
22
)
<<
FLT_MIN
;
cout
<<
setw
(
22
)
<<
FLT_MAX
;
cout
<<
endl
;
cout
<<
setw
(
8
)
<<
"double"
;
cout
<<
setw
(
22
)
<<
sizeof
(
double
)
;
cout
<<
setw
(
22
)
<<
DBL_MIN
;
cout
<<
setw
(
22
)
<<
DBL_MAX
;
cout
<<
endl
;
cout
<<
setw
(
8
)
<<
"ldouble"
;
cout
<<
setw
(
22
)
<<
sizeof
(
long
double
)
;
cout
<<
setw
(
22
)
<<
LDBL_MIN
;
cout
<<
setw
(
22
)
<<
LDBL_MAX
;
cout
<<
endl
;
cout
<<
"------------------------------------"
;
cout
<<
"------------------------------------"
;
cout
<<
"------------------------------------"
;
1.
QJsonValue
存储整数是按照double类型存储的,即使是1这么一个很小的数。
2.
QJsonValue
的
toInt()
是提供了double到int的转换。而不是文件本身存的就是
int
类型的数据。
3.
QJsonValue
之所以没有
tolonglong()
的接口是因为
double
类型是有精度损失的。
double
类型的有效数字位数:15-16位数字,double类型的有效数字位数是15位还是16位,主要取决于浮点数的值:a. 对于绝对值在1.0和
2^53
之间的正常值,double类型一般能表示15位有效数字。b. 当浮点数接近0时,指数部位全部为0,此时有效数字位数可以达到16位。3. 当浮点数接近
2^53
时,指数部位为最大值,此时有效数字位数只有15位。
4.为啥Qt存整数到Json不是按整形存,而是以double类型存?
通过上面的介绍有两个关键点:
-
JSON 是基于 JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集
-
JSON 支持number 类型
Javascript的数字存储使用了IEEE 754中规定的双精度浮点数数据类型,而这一数据类型能够安全存储 -(2^53-1) 到 2^53-1 之间的数值(包含边界值)。JSON 是 Javascript 的一个子集,所以它也遵守这个规则。
以下是rfc7159的说明:
Note that when such software is used, numbers that are integers and are in the range [-(2^53)+1, (2^53)-1] are interoperable in the sense that implementations will agree exactly on their numeric values.
这两个边界值可以通过 JavaScript 的 Number.MAX_SAFE_INTEGER 和 Number.MIN_SAFE_INTEGER 获取。
5.
对于特别大的数,保证精度首选字符串。
GMP是开源的数学运算库。当
Qt
自带的q
int
64
(依赖系统位数)也满足不了长度时,可以用它进行
数据
计算。
最近写的下载器
中
,当下载到2点几G时,突然出现startPo
int
与endPo
int
变为负数,导致http的Range请求头部出错。网上一查,原来是整数溢出。如下代码:
q
int
64
startPo
int
=0;
q
int
64
endPo
int
=0;
int
index=-
QFile file("file.
json
");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return;
Q
Json
Document doc = Q
Json
Document::from
Json
(file.readAll());
Q
Json
Object
json
= doc.object();
// 读取字符串类型的值
QString str
Value
=
json
.
value
("str_key").toString();
// 读取整数类型的值
int
int
Value
=
json
.
value
("
int
_key").to
Int
();
// 读取数组类型的值
Q
Json
Array array =
json
.
value
("array_key").toArray();
for (
int
i = 0; i < array.size(); ++i) {
QString str = array.at(i).toString();
// Do something
file.close();
2. 修改
JSON
数据
```cpp
Q
Json
Object
json
;
json
.insert("str_key", "new_
value
");
// 修改整数类型的值
json
.insert("
int
_key", 100);
// 修改数组类型的值
Q
Json
Array array;
array.append("new_element");
json
.insert("array_key", array);
Q
Json
Document doc(
json
);
QFile file("file.
json
");
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return;
file.write(doc.to
Json
());
file.close();
注意:在修改
JSON
数据
时,需要先创建
一个
新的Q
Json
Object对象,然后插入/修改需要修改的键值对,最后再将新创建的Q
Json
Object对象转换成Q
Json
Document对象并写入文件
中
。