编程之路总结:编码与字符集

我曾经以为理所当然应该知道的事,而如今的一些程序员却一脸茫然。 先声明,我本人对Java没有偏见。 起因是这样的,同事拿着一段Java代码给我看,咨询下啥啥功能的事。我便细细观摩,看到关于读写文件的代码–将文件以字符串形式读到内存中。 看了会儿,我说:“这块有点瑕疵,没有指定文本的编码格式” 他说:“什么编码?以前也这么写的,运行起来没有问题。” ……(后续交流省略)….. 从我近年来和多位从事移动开发的程序员接触来看,我的感触就是移动互联网发展太快了,以至于他们完美的错过了这个行业很基础的知识灌溉—-或者忙着写业务了,或者手中正翻看xx天速成、包教包会的书,或者忙着追各种吹牛x的黑科技了, 好了,抱怨了一通,我转回正题。为什么要指定文本的编码格式?这事儿要从上世纪的蛮荒时代说起。 百花齐放 我们都知道,计算机的中储存以字节(8比特)为单位,人们阅读的文本在计算机存储都是一堆字节,而不是什么“世界你好,hello world”之类的可阅读文本。 为了解码这些字节,必须按照一定的规范将文本映射为计算机可以储存的字节。最早的可知晓的规范就是非常著名的ASCII编码,比如 0x30 代表字符 0, 0x41 代表字符 A 等等 ASCII编码,全称为美国信息交换标准代码, 在1967年作为规范标准发布,主要用来表示现代英文的编码约定(可以理解,那时计算机的主导是美国,到现在美国也是引领者),只定义了128个字符。 随着计算机的发展,越来越多的国家和地区使用计算机,自然因为语言不同,也有了制定适合自己国家和地区的文本编码了,诸如ISO系列,ibm系列,Windows系列,像中文的big-5,gb系列等, 详细请参考 这里,闲聊一句,上世纪80、90年代BP寻呼机,支持汉显和不支持汉显的价格有时能差一倍。 当编码多了,最大的问题是不同编码之间无法相互兼容,只有程序员能明白这其中的苦楚;若还是不明白,问问微软在上世纪是如何在各个国家和地区适配多语言的。 Unicode一统 文本编码不能相互兼容,这为软件行业的发展、互联网的推广带来了很大的麻烦,统一字符编码势在必行。最终统一的结果也就是今天大家看到的Unicode— 是由一个名为 Unicode 学术学会(Unicode Consortium)的机构制订的字符编码系统,支持现今世界各种不同语言的书面文本的交换、处理及显示。该编码于1990年开始研发,1994年正式公布,最新版本是2019年5月7日的Unicode 12.1.0(这段摘自百度百科)。 字符集和编码 这里单独说下字符集和编码的关系。 平时我们交流时,很少说字符集,通常都是Unicode编码、GB2312编码,UTF8之类的。 严谨的来说,包括Unicode、ASCII、GB2312在内的编码规范,其实分为两部分:字符集、编码方式。 字符集,指的是该规范收录了哪些字符,可以称之为字符的集合。例如,ASCII收录了数字、大小写英文字母、一些常用的符号和一些控制打印的字符;GBK2312收录包括ASCII在内的符号,以及常用的汉字和符号,还收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。 编码方式,指将收录的字符如何映射到计算机中的字节存储。简单来说,ASCII是单字节编码,为每一个收录的字符分配一个字节的数值表示,比如 0x30 代表字符 0, 0x41 代表字符 A。 GBK2312是双字节编码,为每一个收录的字符分配两个字节的数值表示。 UTF8,是Unicode字符集的一种编码方式,变长字节; UTF16,是Unicode字符集的一种编码方式,两个字节(不考虑Java中的字符代理对情况);另外,考虑到网络传输,UTF16还要区分大小端(大小尾)。两个数字 0xFFFE,0xFEFF,估计还是有人能看懂的。 UTF32,是Unicode字符集的一种编码方式,四个字节; UTF7,有谁听说过吗?是Unicode字符集的一种编码方式,变长字节。 另外,强烈建议在大部分场景使用UTF8编码方式,这是真正的万国编码。 为什么要指定文本编码 答:因为鬼知道我不指定编码,软件会怎么解码文本? 联通为什么干不过移动 答:微软记事本告诉你https://www.iteye.com/blog/mfkvfn-1703414 在windows操作系统中,新建一个文本文件,用词本打开,输入"联通"两个字之后,保存,关闭,然后再次打开,你会发现这两个字已经消失了,代之的是个乱码!而输入移去就不会有问题。 据说这就是联通之所以拼不过移动的原因。 Windows NT(Windows 2000及以上)版本的记事本 ,亦默认安装于Windows 2000和Windows XP中,可以侦查到缺乏字节序标记的Unicode文件。这个功能由一个Windows API提供,名为IsTextUnicode() 。但是,这个功能是不完美的,副作用是一些小写字体的ASCII文字,会错误判断为UTF-16。 在2006年5月18日,有报道称,对于一个含有类似"this app can break"这样短语的文档(甚至是类似"aaaa aaa aaa aaaaa"的简单短语),记事本会将其打开并显示为Unicode二进制文本。如果安装了相关中文字体则会显示二进制码所对应的汉字。造成这个错误的字符串可能形如“4个字母+空格+3个字母+空格+3个字母+空格+5个字母 ”,或者“4个字母+空格+5个字母+空格+5个字母+空格+5个字母 ”,并且文档末尾没有换行符。有人认为这是一个复活节彩蛋 ,但事实并非如此。还有人认为这是由于记事本的Unicode的检测算法所引起。 这个问题已在Windows Vista版本的Notepad中被修复。 但是,记事本还有一个问题,就是用ANSI保存的文档有时会被误认为Unicode。如在记事本中输入“联通 ”并保存,再次打开的时候会显示乱码,目前尚未被修复。

December 4, 2019 · 1 min · holdsky

PNG IHDR:获取PNG图像宽和高

通过读取png图像文件的头信息 #include "stdio.h" namespace { class CFileStream { public: CFileStream (FILE *fs):m_fs(fs) {} ~CFileStream() { fclose(m_fs); } inline operator FILE*() { return m_fs; } inline operator bool() { return m_fs != 0; } private: FILE *m_fs; }; inline unsigned long Convert4char( unsigned char c[4]) //网络端字节序 { unsigned long n = ((unsigned long)c[0]) << 24; n += ((unsigned long)c[1]) << 16; n += ((unsigned long)c[2]) << 8; n += ((unsigned long)c[3]); return n; } } #define png_signature_len 8 //png文件签名 #define png_chunk_len_len 4 //idhr长度信息 #define png_chunk_type_len 4 //ihdr类型码 #define png_ihdr_data_len 13 //ihdr数据 + (BOOL)imageSizeFromPNGFile:(NSString *)pngFile size:(CGSize *)size; { if (!pngFile || !size) { return NO; } CFileStream fs = fopen([pngFile UTF8String], "rb"); if (!fs) { return NO; } unsigned char buf[png_signature_len+png_chunk_len_len+png_chunk_type_len+png_ihdr_data_len]; if (sizeof(buf) != fread(buf, 1, sizeof(buf), fs)) { return NO; } //校验文件签名 if ( memcmp(buf, "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", png_signature_len) != 0 ) { return NO; } //校验ihdr长度 if (memcmp(buf + png_signature_len, "\x00\x00\x00\x0d", png_chunk_len_len) != 0) { return NO; } //校验ihdr类型码 if (memcmp(buf + png_signature_len +png_chunk_len_len, "IHDR", png_chunk_len_len) != 0) { return NO; } size->width = Convert4char(buf + png_signature_len+png_chunk_len_len+png_chunk_type_len ); size->height = Convert4char(buf + png_signature_len+png_chunk_len_len+png_chunk_type_len+4); return YES; } #undef png_signature_len #undef png_chunk_len_len #undef png_chunk_type_len #undef png_ihdr_data_len

December 3, 2019 · 1 min · holdsky

Objective-C:动态创建新类(Class)

动态创建一个类,NewClass ,继承自NSObject Class newClass = objc_allocateClassPair([NSObject class], "NewClass", 0); //向运行时注册这个类 objc_registerClassPair(newClass); //从运行时销毁这个类 objc_disposeClassPair(newClass);

November 29, 2019 · 1 min · holdsky

学习笔记:使用Android V8 (J2V8)JavaScript引擎

集成 在Android Studio的Android工程中,需要在build.gradle文件内容里添加依赖指令,然后gradle构建就会自动化集成J2V8引擎 dependencies { implementation 'com.eclipsesource.j2v8:j2v8:5.0.103@aar' } J2V8的最新版本,可以在marven仓库中查看 https://mvnrepository.com/artifact/com.eclipsesource.j2v8/j2v8 使用示例 示例摘自https://eclipsesource.com/blogs/tutorials/getting-started-with-j2v8/ import com.eclipsesource.v8.V8; public static void main(String[] args) { V8 runtime = V8.createV8Runtime(); int result = runtime.executeIntegerScript("" + "var hello = 'hello, ';\n" + "var world = 'world!';\n" + "hello.concat(world).length;\n"); System.out.println(result); runtime.release(); } 访问JavaScript对象(Object) 假设有这样一段JS脚本 var jsobj = {}; jsobj.hello = "world"; 在J2V8中可以直接访问jsobj对象 import com.eclipsesource.v8.V8; public static void main(String[] args) { V8 runtime = V8.createV8Runtime(); runtime.executeVoidScript("" + "var jsobj = {};\n" + "jsobj.hello = 'world';\n"); // 访问jsobj的属性 V8Object jsobj = runtime.getObject("jsobj"); System.out.println(jsobj.getString("hello")); jsobj.release(); runtime.release(); } 需要注意, V8Object 需要自己手动释放 ...

November 28, 2019 · 1 min · holdsky

学习笔记:macOS 选择文件对话框示例

NSOpenPanel控件 - (void)p_selectFileDialog:(void(^)(NSString* filePath))block { NSOpenPanel* panel = [NSOpenPanel openPanel]; [panel setCanChooseDirectories:NO]; [panel setAllowsMultipleSelection:NO]; [panel setMessage:@"提示语"]; // Display the panel attached to the document's window. [panel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result){ if (result == NSModalResponseOK) { //点击确定后的代码 block(panel.URL.path); } else { block(nil); } }]; }

November 28, 2019 · 1 min · holdsky

SSL EVP框架:AES加密

AES加密几个要点 对称加密(Symmetry Crypto) 加密和解密的钥匙是同一把—-加密方和解密方拥有相同的密钥(音yue,四声)。 密钥(key) 任意数据(不一定非得字符串),长度常见为128比特、192比特、256比特,也有支持512比特的算法库 初始化向量(IV) 这不是AES算法标准强制要求的,是一种增加AES破解难度的手段。IV和密钥共同参与加密和解密运算。IV长度固定为128比特。 加密块(block) 每次加密的块长度都是128比特;输入和输出长度相同,考虑补全模式的情况下,输出的最大长度 = 输入长度+128比特 加密模式 是指当需要加密的源数据长度大于128比特时,加密块和加密块之间的运算模式。 常见的有 CBC密码分组链接模式、ECB电码本模式(不推荐)、CTR计算器模式、CFB密码反馈模式、OFB输出反馈模式。 补全模式模式(padding) 因为AES一个加密块为16字节,当源数据的最后一个块不足16字节时,需要手动补全为16字节。 一般使用PKCS7模式,手动补全N个N,N为不足的字节数。 例如,最后一个块为 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x10 需要补6个字节,那么就是6个6 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x10 0x06 0x06 0x06 0x06 0x06 0x06 EVP AES 加密 #include "openssl/crypto.h" #include "openssl/aes.h" #include "openssl/evp.h" //引入c++的string,作为buffer使用 #include <string> //std::string 作为返回值其实可以右值优化的。 std::string encrypt(std::string &data , std::string &key ,std::string &iv) { //这里应该做必要的参数校验,如检查key和iv长度 //指定算法模式 EVP_CIPHER *ciper = NULL; switch (data.size()) { case 128 / 8: ciper = EVP_aes_128_cbc();break; case 192 / 8: ciper = EVP_aes_192_cbc();break; case 256 / 8: ciper = EVP_aes_256_cbc();break; default:return "需要做一些错误处理"; } //初始化ctx EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX_init(ctx); //指定加密算法及key和iv int ret = EVP_EncryptInit_ex(ctx, ciper, NULL, (unsigned char *)key.c_str(), (unsigned char *)iv.c_str()); if(ret != 1) {//EVP_EncryptInit_ex failed EVP_CIPHER_CTX_free(ctx); return "需要做一些错误处理"; } // 设置padding EVP框架默认使用的就是PKCS7 EVP_CIPHER_CTX_set_padding(ctx, 1); std::string buffer; buffer.append(0,AES_BLOCK_SIZE + data.size()); int mlen = 0; //进行加密操作 ret = EVP_EncryptUpdate(ctx, (unsigned char *)buffer.c_str(), &mlen, (unsigned char *)data.c_str(),(int)buffer.size()); if(ret != 1) { EVP_CIPHER_CTX_free(ctx); return "需要做一些错误处理"; } int flen = 0; //结束加密操作 ret = EVP_EncryptFinal_ex(ctx, (unsigned char *)buffer.c_str()+mlen, &flen); if(ret != 1) { EVP_CIPHER_CTX_free(ctx); return "需要做一些错误处理"; } EVP_CIPHER_CTX_free(ctx); buffer.resize(mlen + flen); return buffer; } EVP AES 解密 std::string decrypt(std::string &data , std::string &key ,std::string &iv) { //这里应该做必要的参数校验,如检查key和iv长度 //指定算法模式 EVP_CIPHER *ciper = NULL; switch (data.size()) { case 128 / 8: ciper = EVP_aes_128_cbc();break; case 192 / 8: ciper = EVP_aes_192_cbc();break; case 256 / 8: ciper = EVP_aes_256_cbc();break; default:return "需要做一些错误处理"; } //初始化ctx EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX_init(ctx); //指定加密算法及key和iv int ret = EVP_DecryptInit_ex(ctx, ciper, NULL, (unsigned char *)key.c_str(), (unsigned char *)iv.c_str()); if(ret != 1) {//EVP_EncryptInit_ex failed EVP_CIPHER_CTX_free(ctx); return "需要做一些错误处理"; } // 设置padding功能 EVP框架默认使用的就是PKCS7 EVP_CIPHER_CTX_set_padding(ctx, 1); std::string buffer; buffer.append(0,data.size()); int mlen = 0; //进行解密操作 ret = EVP_DecryptUpdate(ctx, (unsigned char *)buffer.c_str(), &mlen, (unsigned char *)data.c_str(),(int) data.size()); if(ret != 1) { EVP_CIPHER_CTX_free(ctx); return "需要做一些错误处理"; } int flen = 0; //结束解密操作 ret = EVP_DecryptFinal_ex(ctx, (unsigned char *)buffer.c_str()+mlen, &flen); if(ret != 1) { EVP_CIPHER_CTX_free(ctx); return "需要做一些错误处理"; } EVP_CIPHER_CTX_free(ctx); buffer.resize(mlen + flen); return buffer; }

November 21, 2019 · 2 min · holdsky

iOS 获取开机启动后的累积时间

[[NSProcessInfo processInfo] systemUptime]; 或者 #include <mach/mach.h> #include <mach/mach_time.h> long long tm = mach_absolute_time(); static mach_timebase_info_data_t sTimebaseInfo; if ( sTimebaseInfo.denom == 0 ) //静态整型变量初始化时默认为0 { mach_timebase_info(&sTimebaseInfo); } return tm * sTimebaseInfo.numer / sTimebaseInfo.denom / (1000*1000);

November 20, 2019 · 1 min · holdsky

Objective-C 网络对时的简单实现

头文件 // // ZLNetworkTime.h // // Created by zxs.zl on 2018/3/12. // #import <Foundation/Foundation.h> @interface ZLNetworkTime : NSObject +(void)startSyncTimeFromNetwork; +(BOOL)syncTimeFromNetworkDone; +(NSTimeInterval)timeIntervalSince1970; @end 实现文件 // // ZLNetworkTime.m // // Created by zxs.zl on 2018/3/12. // #if ! __has_feature(objc_arc) #error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag #endif #import "ZLNetworkTime.h" struct _ZLTimeBase_ { _ZLTimeBase_() { init_state = not_init; init_count = 0; } NSTimeInterval systemUptimeBase; NSTimeInterval networkTimBbase; enum { not_init, initing, inited, } init_state; int init_count; }s_timeBase; @implementation ZLNetworkTime +(NSTimeInterval)timeIntervalSince1970 { if (s_timeBase.init_state == s_timeBase.inited) return s_timeBase.networkTimBbase + [[NSProcessInfo processInfo] systemUptime] - s_timeBase.systemUptimeBase; else return [[NSDate date] timeIntervalSince1970]; } +(BOOL)syncTimeFromNetworkDone { return s_timeBase.init_state == s_timeBase.inited; } +(void)startSyncTimeFromNetwork { if (s_timeBase.init_state != s_timeBase.not_init) return; if (s_timeBase.init_count >= 3) return; @synchronized(self){ if (s_timeBase.init_state != s_timeBase.not_init) return; s_timeBase.init_state = s_timeBase.initing; } s_timeBase.init_count++; NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; config.timeoutIntervalForRequest = 3; config.requestCachePolicy = NSURLRequestReloadIgnoringCacheData; NSURLSession * session = [NSURLSession sessionWithConfiguration:config]; NSURL * url = [NSURL URLWithString:@"http://time.tianqi.com/"]; NSTimeInterval systemUptime = [[NSProcessInfo processInfo] systemUptime]; NSURLSessionDataTask * dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse*)response; if ([httpResponse isKindOfClass:[NSHTTPURLResponse class]] && httpResponse.statusCode == 200 ) { NSString * dateStr = httpResponse.allHeaderFields[@"Date"]; if (dateStr.length > 0) { //Mon, 12 Mar 2018 07:21:32 GMT NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; formatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"]; [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]]; [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss 'GMT'"]; NSDate *date = [formatter dateFromString:dateStr]; if (date) { s_timeBase.systemUptimeBase = [[NSProcessInfo processInfo] systemUptime]; s_timeBase.networkTimBbase = [date timeIntervalSince1970] + s_timeBase.systemUptimeBase - systemUptime; s_timeBase.init_state = s_timeBase.init_state; return; } } } s_timeBase.init_state = s_timeBase.not_init; }]; [dataTask resume]; } @end

November 20, 2019 · 2 min · holdsky

sudo 运行sh脚本,Permission denied

运行一个sh脚本,以普通用户运行,提示 Permission denied,z再使用sudo运行,还是提示 Permission denied。 于是,我用 ls -all 打印脚本的属性 -rw-rw-rw-@ 1 zjon staff 209535797 11 19 15:18 abc.sh 原来是没有执行属性 x,那么就分配属性 chmod 0766 abc.sh

November 19, 2019 · 1 min · holdsky

iOS 推出指定的UIViewController

UINavigationController 提供的常用接口是push和pop,但都是操作最顶层的页面 topViewController,若要操作指定的页面,可以访问属性 viewControllers @implementation UINavigationController(zlAddition) - (void)zl_popTheViewController:(UIViewController *)viewController animated:(BOOL)animated { if (viewController.navigationController == self) { if (self.topViewController == viewController) { [self popViewControllerAnimated:animated]; } else { NSMutableArray *arr = [self.viewControllers mutableCopy]; if (arr.lastObject != viewController) { [arr removeObject:viewController]; [self setViewControllers:arr animated:animated]; } } } } @end

November 19, 2019 · 1 min · holdsky