40行代码获得百词斩实体书的单词次序
这篇文章主要是用来记录我做这件事的次序,如果需要写代码的思路,请使用页面内搜索定位到 ”开始编程“ 这个二级标题。
前情提要
六级要来了,要背单词了,又拿出来大二时候买的百词斩的实体书了。但是这次我要脱离app
用这本实体书,毕竟我还是知道我自己的自制力的。
但是,用实体书的话我怎样才能够检查我背单词的效果呢?
- 找人检查?就我这烂英语水平就不在别人面前丢人了;
- 手盖住上面的单词,看下面的索引?还是改不掉偷看的毛病,我也不想在这本书上写写画画。
于是乎,我就想找个方法来把这些单词提取出来。
失败的提取的方法
首先,直接手敲是不可能手敲的,这辈子都不可能的。3000多个单词,要敲到猴年马月去啊?!
照相+复印
在我用两个台灯加一个手机闪光灯做补光的强大光源的照射下,纸张变的如此明亮。但是iPad 8MP 的辣鸡摄像头照出来的东西糊的要死,就算用 ps 增加了对比度,增加了亮度,打印出来照样是糊到要死。所以 pass。
照相+OCR
我还是想再试试其他的方法。不过就算使用联网的 ocr 平台,或者是 OneNote
的识别,结果都差强人意。可能纠错花的时间比较少,但是还是会花许多时间。
那就,只能自己干了。
自行编程
因为百词斩不是需要一直联网的,所以它肯定是要把自己的数据保存在本地端的。所以,能够找到它本地端的数据并加以处理,事情应当很快就能够迎刃而解了。
找寻文件
找到目录
单词这个东西小而零碎,并且肯定会有上万行,在本地保存的话,肯定要用到数据库;在安卓,肯定用的是 sqlite
数据库;又因为每个人用到的单词都不一样,所以它们应该会有两种保存的方法:
- 不同的书用不同的数据库;
- 使用同一个数据库,不同的书用一个文本文件标明主键方便查找。
而手机里数十G的文件又不是那么好搜索的,于是我就打开了万恶的百度,但你还别说,刚好让我找到了一个人和我有着同样的想法:
- 百词斩(最新版本6.2.14 )安卓版本, 在手机上运行时, 数据会存在类似以下目录中:
/data/media/0/Android/data/com.jiongji.andriod.card/files/baicizhan
具体路径有可能会不完全一样, 但是用关键字
jiongji
或者baicizhan
查找应该能找到
- 该路径下有几个关键文件:
- lookup.db : 所有单词都存放在这个数据库文件中, 每个单词都会有一个唯一的word id.
- roadmap/road_map_xxx.baicizhan 要背的每一本书都会在此目录下有一个这样的文件。 该文件是文本文件, 其中用"wid":xxxx 定义若干个word id.
- 只要根据word id找到对应的单词即可。 但是不可能手工去一个一个地查询每个单词, 每本书都可能对应着几千个单词呢。所以接下来就要自己写程序了。1
从这句话可知:百词斩用的是第二个方法。
按照他这个思路实施的话,我只需要按照他说的两个关键字查找就行了,但是垃圾的 MIUI
并不支持对文件夹名进行搜索。但是我之前还是看过一点关于安卓开发的,虽然有些毒瘤公司例如百度、阿里巴巴、腾讯会把自己在系统中的文件写在系统的根目录下,但是安卓建议的是应该把文件通通写在 /android/
的目录下,此时鉴定百词斩是不是毒瘤公司的时间到了!
可喜可贺,百词斩并不是一家毒瘤公司,他老老实实地把文件写在了 /android/data/com.jiongji.android.card/
中,虽然比着之前直接写在baicizhan
的目录中要难找一些,但还是不太困难的一件事情。
之后就是直接把这个目录下的baicizhan
的文件夹全部打包,然后用QQ 发给自己的电脑。
找寻标记文件格式
这个过程并不算太难,在寻找教程的时候,破解百词斩单词数据之旅 回复中的第22楼明确指出了在roadmap/road_map_xxx.baicizhan
的文件中是用json
格式写的。2
直接打开,龟龟,没有序列化过的文件就像一个垃圾桶,但毕竟是为了节约流量嘛,crlf 字符还是挺占空间的,可以理解,但是自己看还是导入 vs 来个快捷键,轻松序列化一下吧。
还是这样子看起来舒服。
开始编程
前两步实际上并不算什么,花费了 40 余分钟就搞好了,但是后面的问题才是真正的问题,应当如何编程?因为我之前从来没有接触过 json
,数据库这门课也是睡过去的,我也不太清楚如何编程,不过还是走一步看一步吧。
json的解析
引入依赖
直接导包
因为我不是为了专门学习 json
,也懒得自己造轮子,所以我先想到的就是用前人的伟大经验。
经过多次辗转,我找到了阿里巴巴的 fastjson
项目,从 github
上下载下来源码,第一个难题就摆在我面前,怎么导?
下载下来的文件并不是直接编译好的jar
包,但是天真的我并不知道这件事情,所以我不断的在 IDEA
中的 project structure
中寻找,油腻的师姐在哪里。 导入依赖,删除依赖。折腾了几次,搞得我真的是心烦意乱。在这折腾的过程中,我想到了之前写安卓项目的时候优雅,从容:写一行依赖项,Android Studio
就直接帮你全下下来,配置好,你自己拿来用就够了。
在我搜索的过程中,我才认识到一个事实, github
上分享的代码大部分都只是源代码,并没有经过编译,我需要自己编译才能够导出来所需的 jar
包。而 fastjson
中含有 pom.xml
文件,这是 Maven
的标志,所以我需要 Maven
将它编译。
我还知道了我刚才说的那种优雅的做法实际上是 android
用了 grandle
来管理依赖,知道了这的我像打开了新世界的大门一样。
Maven管理
下载Maven
这个是我一定要拿出来单独说的事情, ISP
太小气,或者怪防火长城,Maven
的下载只有几kps,于是我只能冒着被装垃圾文件的风险跑到脚本之家去下载,幸好脚本之家还是不错的,没有垃圾下载器,更没有垃圾文件。
引入依赖
在IDEA
中新建一个 Maven
项目,在项目根目录下的 pom.xml
按下 alt+ ins
键,输入 fastjson
自动导入,一键下载,方便快捷。写 Android
时的优雅仿佛又浮现在了我的面前。
学习使用
那就是去找写文件的demo了,看了不少的文章,大致思路就是:
- 利用
InputStream
读入文件; - 利用
IOUtils
对于字符串进行编码; - 利用
fastjson
中的parseArray
方法将json
文本转化成对应的javaBean
元素。
其中使用fastjson解析json文件 这篇文章还是很不错的,大部分思路就是从这里搬的。3
连接数据库
因为用的是数据库嘛,当然要连接数据库的。
用 SQL Server
首先,这是一个鞭尸现场,之后不可行。先把原来的 SQL Server 2008 R2
卸载,实在是难安又难卸,鬼知道老师为啥还非得说现在是教学,不要用那么新的,够用就好云云。去您妈个头,再也不见,这是真的垃圾,之前舍友安安卸卸愣是把系统给搞崩,又重新安装系统才算了,这么垃圾的东西早该进坟堆了。
换上了 SQL Server 2017 Developer
,十年之后的东西比十年之前好用的不止一倍。但自己还是愚钝,并没有找到方法,折腾了半个下午,放弃。
用 SQLiteman 分析数据库构成
一个字,好用。比着 SQL Server
这个软件只能查看前一百行的数据,它能够查看数据库中所有的资源。
可以看到整个数据库有10个表,每个表的属性有 json
中解析出来的id主键,有单词,有音标,有中文意思,有 freq
,还有每个单词的长度。
找到这些之后,就应当把数据库放在项目下了。
数据库导入
这个不是难点,直接打开数据库,然后下载对应的 jdbc
文件就ok。
这些完成了之后,如何编码已经非常清晰了,下面就是介绍具体的思路了。
编码构成
语言选择
实际上这种编码用解释型语言,或者pop的语言,但是我不会py
也不怎么会c++
的导包,所以只能用oop的语言来写一堆pop的屎山了。
大致构成
- 连接数据库;
- 导入
json
; json
解析;- 遍历每一个解析出的对象:
- 查表,找出;
- 写入文本。
代码分块解析
连接数据库
private static final String DBURL = "jdbc:sqlite:\\src\\main\\resources\\lookup.db";//数据库相对位置
/*
在main函数中
*/
Connection connection = null;
try {
Class.forName("org.sqlite.JDBC");
connection = DriverManager.getConnection(DBURL);
} catch (SQLException | ClassNotFoundException e) {
e.printStackTrace();
System.exit(-1);
}
// System.out.println("open db successfully");
connection.setAutoCommit(true);
新建一个 Connection
对象,并将其指向数据库的位置。
Class.forName
此方法含义是:加载参数指定的类,并且初始化它。4
setAutoCommit
此方法的含义是:参数为 true
时,SQL
命令的提交(commit)由驱动程序负责。5
导入并解析json
InputStream inputStream = new FileInputStream("src\\main\\java\\helloworld\\word");
String string = IOUtils.toString(inputStream, "utf8");
List<word> words = JSON.parseArray(string, word.class);
第一三行获取文件并格式化,没什么好说的。
第五行,parseArray
方法,看源码解析6,传出来的是一个 list
,所以就要用 list
接收了。
访问
list
的元素,不能像访问数组一样直接用下标访问,而是用objects.get(id)
的方法来访问。
写到这里的时候,我本身是想写相对路径的,但是怎么搞都找不到文件,明明放在同一个文件夹中的啊,无奈,先用的绝对路径。之后上网上找思路的时候,貌似说是必须要将项目下的路径写全才可以,所以我就这么写了,如果你知道如何才能写的更简便的话一定要告诉我啊。
解析 json
的时候,需要一个 Java Bean
,这个就是 word.class
:
public class word {
private int topic_id;
private String options;
private String tag_id;
private String word_level_id;
//Getter and Setter
}
查表
for (word d : words) {
int topic_id = d.getTopic_id();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
for each
语句访问每一个list
的元素,然后获取主键。
createStatement
是为了创建状态并将sql语句送给数据库。
数据库返回一个 resultset
元素。
由于单词只会在一张表中,而且十张表中的属性相同,我就使用了 union
子句在十张表中进行查询。
select word
from table1
where topic_id = " + topic_id
union
//……
写入数据
int i = 0;
int mm = 1;
File file = new File("word"+mm+".txt");
//for each前
if(i == 300){
mm++;
file = new File("word"+mm+".txt");
i%=300;
}
FileWriter fileWriter = new FileWriter(file.getName(),true);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
bufferedWriter.write(resultSet.getString("word")+'\n');
bufferedWriter.close();
fileWriter.close();
//for each 中
我需要每300个单词保存在一个文件中,所以有了这个需求。
这个姑且算个难点:将数据写入多个文本文件中,我也给标出来吧。
实质上也没有什么难度,每一个文件的文件名都是一行字符串,用一个会变的量再将两部分固定的量拼在一起就够了。
方法就是设置一个遍历变量 i
每三百个的时候,再 new
一个新文本出来。
其他就是正常的 I/O
操作,参考了网上的资源7写出来了一个不是很好的代码,最后保存的时候有的文件是299有的是300,不过不太影响,也就懒得深究了。
尾巴
说实话,在编码的时候就想着写出来的时候要写个博炫耀一下的,在中间休息的时候也会想想自己在写什么,写出来之后的兴奋劲。但是编码编了两天多,期间遇到了大大小小的问题,最后才写出来40行的代码。我就在想之前轮子哥说过的一句话,大学生要写这样的代码写10k行才能够出师,而这样的代码我觉得满打满算这两年写过的不会超过一千行,自己光顾着玩了,还是没有把时间用在正地方,导致编码量才如此地少吧。
更博的时候感觉还挺兴奋,但是到最后,写道现在凌晨一点半才写完,看着左下角字数统计写了将近3.5k字,头懵懵的,好几天也没睡好觉了,追的番现在更新也没来得及看,还是坚持写完,现在室友已经躺在床上了。
突然感觉自己自己的自制力在想做好一件事上的时候,还是不错的,也不像自己所想的,我没有一点自制力的,还是要承认自己一下的。
2019年9月23日01点34分于开封
这里应该怎么才能右对齐呢?
markdown
貌似调不了对齐啊!