天空 Blond - 卢恩符文
type: channel
link: https://www.skyblond.info/tag/%E5%8D%A2%E6%81%A9%E7%AC%A6%E6%96%87/
items: 【代码札记】卢恩符文编码 - Proof of concept

【代码札记】卢恩符文编码 - Proof of concept
type: item
date: 2020-03-02T07:23:00+08:00
description: 最近在学习Clojure，我认为学习一门编程语言，只看书不实践是不行的。故决定使用Clojure写一个类似Base64的卢恩符文编码。这里使用Elder Futhark字母表，关于此字母表的更多信息还请移步维基百科。这篇文章是Proof of Concept性质的，本文将使用Java/Kotlin编写相同功能的代码，以确定具体的实现细节和编码方式。原理Base64关于Base64详细的编解码方式，请移步维基百科。简单来说就是将要编码的字节按照顺序摆好，然后选定6位为一组算出来对应的数字，再以预先选定好的方式将数字替换成字母。解码时反向操作，通过字母找到对应的数字，按顺序将数字翻译回二进制，按顺序放好便是原来的被编码字节。卢恩符文同样按照Base64的思路，将字母换成卢恩符文，并修改一下位数，可以达到目的。这里选用的Elder Futhark字母表具有如下字母：ᚠᚢᚦᚨᚱᚲᚷᚹᚺᚾᛁᛃᛇᛈᛉᛊᛏᛒᛖᛗᛚᛝᛟᛞ对应Unicode编码：ᚠ ᚢ ᚦ ᚨ ᚱ ᚲᚷ ᚹ ᚺ ᚾ ᛁ ᛃᛇ ᛈ ᛉ ᛊ ᛏ ᛒᛖ ᛗ ᛚ ᛝ ᛟ ᛞ一共是24个字母。它还具有三种标点：᛫᛬᛭对应Unicode编码：᛫ ᛬ ᛭第一个标点是空格，即单词间的分隔符。中间的是句号，即句子间的分隔符。最后一个不知道，我也没能查到相关的说明。 以下方式只是我选定的一个方式，不唯一，而且很灵活 卢恩符文与Base64首先不一样之处在于字符数量，Base64选了64个字符作为编码字符，他正好是2的6次幂，刚好可以用6个二进制位来表示，这也是为什么要把字节摆好，每六个位一组翻译成数字的原因。而卢恩符文只有24个，加上标点符号也才27个，正好卡在5位和6位二进制数能表达的范围之间，很尴尬，因此这套系统涉及非二进制运算，所以远不及Base64这些利用二进制位操作的方式高效迅速。当然，我写这个东西就是奔着好玩去的，能用就行。如果换一个角度想，这实际上就是进制转换的问题。Base64是利用64进制表示数据，那么卢恩符文也可以用24进制来表示数据。对于每一块数据（可以是任意字节），按照字节顺序放好，翻译成数的形式，再按照数制转换的方式操作即可。得到数字之后按照表进行翻译便可得到卢恩符文编码。解码时则反向操作。以上便是基本原理，当实际实现时还需要有些调整或技巧。下面将分解代码进行介绍。代码分解定义常量首先是定义字母表：val rawElderFutharkCharacters = charArrayOf( 'ᚠ', 'ᚢ', 'ᚦ', 'ᚨ', 'ᚱ', 'ᚲ', 'ᚷ', 'ᚹ', 'ᚺ', 'ᚾ', 'ᛁ', 'ᛃ', 'ᛇ', 'ᛈ', 'ᛉ', 'ᛊ', 'ᛏ', 'ᛒ', 'ᛖ', 'ᛗ', 'ᛚ', 'ᛝ', 'ᛟ', 'ᛞ' ).sortedArray()这里要保证一定是个有序数组，其中原数组的最后两个字母是字母表正确的顺序，但是Unicode编码却是相反的。因为后面解码时要用二分查找确定字符在数组中的位置，所以一定要保证被查找的数组是有序的，否则将不能正确解码。然后是定义块长度。这里我选了三种不同的长度，实际上大家可以随意选择，只要电脑能跑得动就可以。const val minBlockSize = 128 const val middleBlockSize = 256 const val maxBlockSize = 512然后是一些趁手的工具函数。fun randomBlockSize(): Int { return when (Random.nextInt(0, 3)) { 0 -> minBlockSize 1 -> middleBlockSize 2 -> maxBlockSize else -> throw IllegalStateException() } } fun getSpace(blockSize: Int): Char { return when (blockSize) { minBlockSize -> '᛫' // ᛫ middleBlockSize -> '᛬' // ᛬ maxBlockSize -> '᛭'// ᛭ else -> throw IllegalArgumentException() } } fun getBlockSize(c: Char): Int { return when (c) { '᛫' -> minBlockSize // ᛫ '᛬' -> middleBlockSize // ᛬ '᛭' -> maxBlockSize// ᛭ else -> throw IllegalArgumentException() } }以上分别是随机取一个块长度、按照块长度取一个分隔符和通过分隔符确定块长度。前二者供编码时使用，最后一…