前言
大家好,我是bigsai大赛哥,好久不见,甚是想念。
行了,咱们步入正轨,已经进入2022,在2021这一年,很多人的快乐消遣是在吃瓜快乐中度过的,有的作为主动吃瓜群众第一手掌握消息,有的作为第二手或者被动吃瓜者(比如我就是)。
然而,现在吃瓜可有难度了,因为有的瓜可能是假的,某博上搜不到,在一些网站上、聊天出现一个神秘串串!!
这一串是啥玩意,惊天大瓜表达的啥意思,该怎么解读?
这年头,没点知识连吃瓜群众都当不成(手动狗头)!作为程序员,不光要知道吃瓜的内容,还要知道吃瓜背后的技术!
好了,也不藏着掖着了,这一串就是大名鼎鼎的摩尔斯电码,也称摩斯密码,当然这是以文本的形式直接展现了,你在谍战剧中、战争剧中那些电报的滴滴哒哒的其实多半就是摩斯密码,趁着这个机会,好好了解一下摩尔斯密码吧!
摩尔斯电码源来
摩尔斯电码是怎么被发明的呢?是某个叫摩尔斯的天才发明的吗?
其实在摩尔斯之前,就有非常笨重的电报机,不过这种电报机用了26根线表示26种字母(肯定没学过计算机,妥妥的暴力美学),在实用方面很差。
在电气时代刚流行的时代,并没有电话手机,人们探索的第一步是如何用电去传讯消息,在这期间摩尔斯发明了电报并且获得了专利,并且他的团队(有说是他的助手艾尔菲德·维尔发明的摩尔斯电码)配套发明了一套传输的规则被称为摩尔斯电码。
在当时利用电去传输消息信号是非常了不起的发明,而电报机接收方会根据电报电流通过控制一直笔打印发送方按下电报机的内容,电流通过长划线就长,电流通过时间段划线就短,没有电流通过纸上空白就增长。
然后接收方根据摩尔斯电码规则转译成对应的字符单词即可。主要用点( · )和划(—)的不同排列组合表示不同的数字或字符,然后点划之间、字符之间、字母之间停顿时间都是不同的。
摩尔斯电码为什么用点划两种表示一些单词字母呢?
大家可以考虑一下,如果一种符号确实理论上行得通,但是一个符号能够表示的内容太少,一个连续点表示1、两个连续点表示2、三个连续点表示3…… n种数字字符就需要n个数量符号数才能表示,这样下去符号使用效率是非常低效了。
如果是三种符号表示,确实能够表示的内容非常多,长度为5的符号就可以表示243个字符。能够表达的内容其实已经远远超过日常使用(0-9数字,26个字母,几个常用符号)。看起来好像很紧凑但是三种符号讯号根本不好传递,很容易出现混淆问题(比如在电报等其他传输那么会分成长、中、短三种不容易甄别,远不如长短两种容易区分)。
所以2就是一个非常神奇的数字,无论在计算机还是大自然都是非常巧妙的,01、长短、快慢、高低……都可以用两种符号表示,并且这些内容在现实生活中也是非常容易展示实现的,并且使用两种符号能够表示内容数量也是可以接收的,长度为5的符号就可以可以表示2^5=32种数字字符,所以这种长度还是能够被接收的。
摩尔斯电码艺术
我们关注摩尔斯电码的一些含义。上面提到摩尔斯团队早期发明的摩尔斯电码是一些表示数字的点和划,用一个电键可以敲击出点、划以及中间的停顿(长按,短按表示点(.)、划(—),松开不按表示停顿),点划、字符、单词等时长和停顿为:
- 点( · ):1 (读 滴 dit ,时间占据1t )
- 划(—):111 (读 嗒 dah ,时间占据3t )
- 字符内部的停顿(在点和划之间):0 (时间占据1t )
- 字符间停顿:000 ( 时间占据3t )
- 单词间的停顿:0000000 ( 时间占据7t )
有了上面的规则,我们大致能知道摩尔斯电码长什么样,那么怎么甄别它代表什么内容呢?这时候需要查找一本代码表才能知道每个字母数字符号等对应的内容,其中一些主要内容如下:
来源维基百科
我靠,这个看起来好像有点记忆难度啊,确实是有难度的,根据这些内容符号的特性,有些教授给摩尔斯密码搞成一棵二叉搜索树让大家更便捷记忆摩尔斯密码,二叉树表示的国际摩尔斯电码。图中每一分叉的左支为点(·),右支为划(-),直到到达所需要表示的字符为止,这样一棵树可以更容易找到相似内容的联系:
来自维基百科
不过,摩尔斯电码还是非常有智慧的(这里不清楚是发明者这么有智慧还是记忆大师发现这么牛批的规律),摩尔斯电码的字母和数字还有着一套象形记忆的方式,这个可不是跟咱们牛批的中文有点相似么,其具体的记忆图为:
来源dreamstime.com
一个MORSE CODE 的摩尔斯电码的表示和记忆为:
掌握摩尔斯密码
好了,通过上面的介绍,想必你对摩尔斯电码有了一定的了解,对于我们普通人来说,不需要会记住每个字母数字对应的摩尔斯电码,我们需要掌握的就是能够懂得摩尔斯电码编解码的方式和规则即可。
简单的说,我们要掌握发送和接收的规则,将单词字母转成摩尔斯电码发送,将接收的摩尔斯电码转成单词单词字母即可。
比如我们现在有:ge gie hao 这段话,其中
a : .- ;e : .;g : --.;h : .... ;i : ..;o: ---
那么纸面上对应的摩尔斯编码为(视觉上可甄别的距离):
- --. . --. .. . .... .- ---
如果用声音来表示(滴哒),那就是这样的:
- --. . / --. .. . / .... .- ---
- 哒滴 滴 哒哒滴 滴滴 滴 滴滴滴滴 滴哒 哒哒哒
上面就大概是声音的传播过程(/表示单词停顿时间长一些),如果用非常精确的二进制来表示,0表示没数据,1表示有数据(电铃按下),其实哒是滴的三倍时常,其二进制对应为:
- --. . / --. .. . / .... .- ---
- 11011101 000 1 0000000 111011101 000 101 000 1 0000000 1010101 000 10111 000 11101110111
可能看起来不是很直观,我优化一下(实际上01是连续的没有括号的)
- --. . / --. .. . / .... .- ---
- 11011101)000(1)0000000(111011101)000(101)000(1)0000000(1010101)000(10111)000(11101110111)
- 哒哒滴 滴 (大停顿) 哒哒滴 滴滴 滴 (大停顿) 滴滴滴滴 滴哒 哒哒哒
上面就是比较标准的摩尔斯电码,其中三个1表示哒(三倍滴的时常),一个1表示滴,0表示没有电流数据,这个空档期也要把握火候的,滴哒之间是1t空闲时间,几个滴哒组成的字符之间是3t空闲时间,几个字符组成的一个单词之间是7t空闲时间。
这样,摩尔斯电码的规则你就差不多是拿捏了。同样给你一个摩尔斯电码,比照电码表也很容易给它转成对应语句。
不过在那个时代很多电报是按照长度收费的,然而很多人就用一些简要的单词字母表示一句话,于是常用缩写被很多人使用,这里不进行太多介绍,知道有点类似暗语就比如plmm:
此外,摩尔斯电码还有一些特殊符号,表示发错了、停止、终止、错误等等用来确保摩尔斯电码发送的正确性(毕竟人肯定会有脑子糊涂或者手抖时刻就按错了是吧)。
中文电码
对于欧美一些国家来说,他们用那些单词和字母使用标准的摩尔斯电码来通讯是没有任何问题的,毕竟26字母+数字+10个数字+少量符号就足够了,自摩尔斯电码在1835年发明后,一直只能用来传送英语或以拉丁字母拼写的文字,但是在中国甚至其他国家,怎么用电报进行通信呢?
拼音?
拼音虽然勉强传递一些消息,但是拼音会有很多造成很多解释错误,举个例子:
tai shuai le 可以表示太帅了,也可以表示太衰了。
ni tai mei le 可以表示你太美了,还能表示你太没了,还能表示镍钛没了……
主要是中文博大精深,所以拼音行不太通顺,于是清朝时候政府雇外国人设计了中文电报,中文电码表采用了四位阿拉伯数字作代号,简称“四码电报”,从0001到9999按四位数顺序排列,用四位数字表示最多一万个汉字、字母和符号。
中文电码,又称标准中文电码、中文商用电码、中文电报码或中文电报明码,原本是于电报之中传送中文信息的方法,它是第一个把汉字化作电子讯号的编码表,大家只需要知道它在初始时候采用的这种方式就行了。
如果大家想查阅相关中文汉字对应的数字,可以在下面网站上查询:
https://apps.chasedream.com/chinese-commercial-code/
百科对应的中文电码也有:
https://baike.baidu.com/item/%E4%B8%AD%E6%96%87%E7%94%B5%E7%A0%81/2667759?fr=aladdin
但是中文电码是无理码并且数量也太多了,所以一般用户根本没法记忆使用,随着通信发展、电话、手机计算机的发展,中文电码的应用场景还是比较少的。
现在的各个网站中的中文摩斯密码,大家实现的大多不是标准的中文电码表对应的数字,很多是借助了其他编码—Unicode编码。Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode给每个字符提供了一个唯一的数字,不论是什么平台、不论是什么程序、不论是什么语言。
所以大部分实现中文摩斯密码的时候将对应中文字符转成4字节unicode(UCS-4),然后再将这四个字符进行摩尔斯编码即可。
当然,各家实现方案细节上还是有所区别的,但是问题不大,但是大部分对其编码过程只对中文进行Unicode编码保证英文与标准的摩尔斯电码进行统一。
还有就是为了让解码过程更容易,在中文摩斯密码中每个字符之间用\划分,这样通过\可以准确知道一个字符的起始位置直接进行对应转换即可,就不用担心因为字符、数字凑在一起造成的混淆处理了。
实现一个简单的中文摩斯密码
上面说了那么多,对于程序员来说,写的code才是真的,这里面针对上面的介绍,实现一个简单的摩斯密码啦。
这里面实现说明一下:
- 标准形式无论中英文都以`\`作为字符划分
- 中文的处理不采取标准中文电码表,这里采用转成Unicode编码的4个16进制数字
- 不处理空格,字符间用斜杠分割(放开头),中文字符内的Unicode字符间用空格分开(本质属于一个中文字符内)
- 要将字符转成大写(或者小写),在进行Unicode编码时候16进制有的字母也要转成统一大小写
实现的代码为:
- import java.util.HashMap;
- import java.util.Locale;
- import java.util.Map;
- //公众号:bigsai
- //2021 1.3
- public class MorseCode {
- Map<Character, String> encMap = new HashMap<Character, String>();// 摩尔斯编码表集合
- Map<String, Character> decMap = new HashMap<String, Character>();// 摩尔斯解码表集合
- public static void main(String[] args) {
- MorseCode morseCode=new MorseCode();
- String val="big赛6啊 不错 sai66";
- String encode=morseCode.Encryption(val);
- String decode=morseCode.Decryption(encode);
- System.out.println(encode);
- System.out.println(decode);
- }
- public MorseCode() {
- encMap.put('A', ".-");
- encMap.put('B', "-...");
- encMap.put('C', "-.-.");
- encMap.put('D', "-..");
- encMap.put('E', ".");
- encMap.put('F', "..-.");
- encMap.put('G', "--.");
- encMap.put('H', "....");
- encMap.put('I', "..");
- encMap.put('J', ".---");
- encMap.put('K', "-.-");
- encMap.put('L', ".-..");
- encMap.put('M', "--");
- encMap.put('N', "-.");
- encMap.put('O', "---");
- encMap.put('P', ".--.");
- encMap.put('Q', "--.-");
- encMap.put('R', ".-.");
- encMap.put('S', "...");
- encMap.put('T', "-");
- encMap.put('U', "..-");
- encMap.put('V', "...-");
- encMap.put('W', ".--");
- encMap.put('X', "-..-");
- encMap.put('Y', "-.--");
- encMap.put('Z', "--..");
- /* 数字电码0-9 */
- encMap.put('0', "-----");
- encMap.put('1', ".----");
- encMap.put('2', "..---");
- encMap.put('3', "...--");
- encMap.put('4', "....-");
- encMap.put('5', ".....");
- encMap.put('6', "-....");
- encMap.put('7', "--...");
- encMap.put('8', "---..");
- encMap.put('9', "----.");
- /* 标点符号,可自增删 */
- encMap.put(',', "--..--"); // ,逗号
- encMap.put('.', ".-.-.-"); // .句号
- encMap.put('?', "..--.."); // ?问号
- encMap.put('!', "-.-.--"); // !感叹号
- encMap.put('\'', ".----.");// '单引号
- encMap.put('\"', ".-..-.");// "引号
- encMap.put('=', "-...-"); // =等号
- encMap.put(':', "---..."); // :冒号
- encMap.put(';', "-.-.-."); // ;分号
- encMap.put('(', "-.--."); // (前括号
- encMap.put(')', "-.--.-"); // )后括号
- encMap.put(' ', " "); // 留空格,这里的星号是自定义的
- for(Character ch:encMap.keySet()){
- decMap.put(encMap.get(ch),ch);
- }
- }
- boolean isChinese(char ch){
- //获取此字符的UniCodeBlock
- Character.UnicodeBlock ub = Character.UnicodeBlock.of(ch);
- // GENERAL_PUNCTUATION 判断中文的“号
- // CJK_SYMBOLS_AND_PUNCTUATION 判断中文的。号
- // HALFWIDTH_AND_FULLWIDTH_FORMS 判断中文的,号
- if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
- || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
- || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
- || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B
- || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION // 判断中文的。号
- || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS // 判断中文的,号
- || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION // 判断中文的“号
- ){
- return true;
- }
- return false;
- }
- //带中文的转成unicode
- String Encryption(String str){
- str=str.toUpperCase();
- // System.out.println(str);
- StringBuilder sBuilder=new StringBuilder();
- char chs[]=str.toCharArray();
- for(char ch:chs){
- if(ch==' '){//不处理空格
- continue;
- }
- sBuilder.append("\\");//转义字符 字符间斜杠分开
- if(isChinese(ch)){
- String unicodeStr=Integer.toHexString(ch).toUpperCase();//转成unicoede
- for(int i=0;i<unicodeStr.length();i++){
- sBuilder.append(encMap.get(unicodeStr.charAt(i)));
- if(i!=unicodeStr.length()-1)
- sBuilder.append(' ');//一个字符见的 摩斯密码用空格隔开
- }
- }else {
- sBuilder.append(encMap.get(ch));
- }
- }
- return sBuilder.toString();
- }
- String Decryption(String morseCode){
- StringBuilder sBuilder=new StringBuilder();
- String morseStrs[]=morseCode.split("\\\\");//转义字符
- for(String morseStr:morseStrs){
- //一个字符 可能中
- if(morseStr!=null&&!"".equals(morseStr)){//去掉开头空的
- String strs[]=morseStr.split(" ");
- if(strs.length==1){//非中文直接找
- sBuilder.append(decMap.get(morseStr));
- }else {//中文先转成4位unicode然后转成中文
- StringBuilder unicodeStr=new StringBuilder();
- for(String uniChar:strs){
- if(uniChar!=null&&!"".equals(uniChar)){//去掉开头空的
- unicodeStr.append(decMap.get(uniChar));
- }
- }
- int chr = Integer.parseInt(unicodeStr.toString(), 16);
- sBuilder.append((char)chr);//(char)别忘了
- }
- }
- }
- return sBuilder.toString();
- }
- }
测试为:
空格不处理
结语
到此,摩尔斯电码的内容介绍就结束啦,对于摩尔斯电码,我也只是介绍一点点,实现也是简单实现一个中文的摩斯密码转换,有可能情况没考虑(有错误欢迎指正,今天写的比较匆忙),大家参考学习即可啦!
另外,在这个季节,祝愿大家在新的一年万事如意,快快乐乐!【编辑推荐】