关于sqlite:用SPSS估计HLM多层层次线性模型模型附代码数据

原文链接:http://tecdat.cn/?p=3230作为第一步,从一个不蕴含协变量的空模型开始 ( 点击文末“浏览原文”获取残缺代码数据 )。 每所学校的截距, 0J,而后设置为均匀, 00,和随机误差ü 0J。 将(2)代入(1)产生 要在SPSS中进行估算,请转至剖析→混合模型→线性... 相干视频 ** 拓端 ,赞13 呈现“ 指定主题”和“反复”菜单。在此示例中,分组变量是id,因而应将其放在“ 主题”框中。 在重复框放弃为空。它仅在剖析人员想要为反复测量指定协方差模式时应用 。单击持续。 弹出一个新菜单,用于指定模型中的变量。空模型没有自变量,因而将因变量mathach放在适当的框中。 空模型中的截距被视为随机变动。这不是默认设置,因而单击“ 随机”以获取以下菜单: 查看“ 蕴含截距”选项。另外,将id变量带到组合框中。的协方差类型无关时,只有一个随机效应,在这种状况下,随机截距。单击持续。 接下来,单击Statistics以抉择其余菜单以抉择在输入中报告哪些后果。 抉择参数估计值报告固定效应的估计值。单击持续,而后单击确定。局部后果如下: 这些后果对应于R&B中的表4.2。 下一步是预计一种平均数- 后果模型。 平均数之后果变项的回归模型在预计空模型之后,R&B开发了一种“平均数后果变项的回归”模型,其中将学校级变量meanses增加到截距模型中。该变量反映了每所学校的学生SES平均水平。方程式(1): 截距能够模仿成一个大均匀 00,再加上均匀得分SES的效应 01,加上随机误差ü 0J。 将(4)代入(1)失去 要在SPSS预计这个,再去剖析→混合模型→直线...。再次出现“ 指定主题”和“反复菜单 ”。将id放在“ 主题”框中,并将“ 反复”框保留为空。 单击持续。在下一个菜单中,指定依赖变量和独立变量。因变量将是mathach,单个协变量将是均值。 该meanses变量输出作为固定效应,所以点击固定按钮拉起固定效应菜单。将meanses变量带入Model框并确保选中Include Intercept。 单击持续。接下来,单击“ 随机”以关上“ 随机效应”菜单。选中“ 包含截距”以将截距指定为随机,并将分组变量id放在“ 组合”框中。它仅被视为固定效应。该协方差类型又是无关紧要,因为只有一个随机效应,随机截距。 最初,单击Statistics以抉择在输入中报告的内容。选中参数估计值旁边的复选框。 单击持续,而后单击确定。输入的一部分如下: 这与R&B中的表4.3绝对应。 下一步是预计随机系数模型。 随机系数模型接下来,R&B提供了一个模型,其中包含学生级别的SES而不是均匀SES,并且他们将学生SES的斜率视为随机的。一个简单因素是R&B以小组均匀为核心的学生SES后出现后果。群体均匀核心意味着从每个学生的集体SES中减去每个学生的学校的均匀SES。可怜的是,meanses变量编码为-1,0,1,因而只是每个学校平均值的粗略指标。为了更好地预计学校平均值,能够利用SPSS 中的Aggregate命令。 分组核心变量的第一步是找到每个群集的平均值。转到数据→聚合 呈现“ 聚合数据”菜单。示意每个组的变量称为“中断”变量; 将id放入Break Variable(s)框中。指标是从每所学校取得学生的均匀SES分数,因而将ses变量带到“ 变量摘要”框中。默认状况下,SPSS假设用户有趣味获取每个组的均值,因而无需更改性能。最初,确保选中“ 将聚合变量增加到流动数据集”单选按钮。 当初,数据中增加了一个新变量ses_mean(不要与三分法混同)。要实现组均匀居中,请从每个ses变量中减去ses_mean。转到变换→计算变量。 在呈现的菜单中,创立一个名为grp_ses的指标变量,该变量等于ses减去ses_mean。 单击确定。当初能够应用以组为核心的SES变量。 1级方程式如下: ...

March 1, 2024 · 1 min · jiezi

关于sqlite:教程navicat配合HTTP通道远程连接SQLite数据库

前言原因好奇的我想查看服务器上宝塔面板的SQLite数据库久别一月,特来水文。起因是我看到服务器上搭建的宝塔面板,好奇其中应用的SQLite数据库,想用navicat近程连贯看一下,奈何不会玩,特来写一篇文章解析一下,如何通过Navicat Premium工具下ntunnel_sqlite.php工具配合HTTP通道近程连贯SQLite数据库 次要指标实现2大重点ntunnel_sqlite.php应用办法HTTP通道连贯近程服务器的SQLite 猜你想问如何与狗哥分割进行探讨关注公众号【JavaDog程序狗】公众号回复【入群】或者【退出】,便可成为【程序员学习交换摸鱼群】的一员,问题轻易问,牛逼轻易吹。 此群劣势: 技术交换随时沟通任何私活资源收费分享实时科技动态领先通晓CSDN资源收费下载自己所有源码均群内开源,可收费应用 2.踩踩狗哥博客javadog.net 大家能够在外面留言,随便施展,有问必答 猜你喜爱文章举荐【我的项目实战】SpringBoot+uniapp+uview2打造H5+小程序+APP入门学习的聊天小我的项目【我的项目实战】SpringBoot+uniapp+uview2打造一个企业黑红名单吐槽小程序【模块分层】还不会SpringBoot我的项目模块分层?来这手把手教你!【ChatGPT】手摸手,带你玩转ChatGPT【ChatGPT】SpringBoot+uniapp+uview2对接OpenAI,带你开发玩转ChatGPT 注释三个问题1. 什么是SQLite?SQLite是一个过程内的库,实现了自力更生的、无服务器的、零配置的、事务性的 SQL 数据库引擎。 就像其余数据库,SQLite 引擎不是一个独立的过程,能够按应用程序需要进行动态或动静连贯,SQLite 间接拜访其存储文件。 2. 为何不能间接应用Navicat连贯?SQLite数据库端口没凋谢外网拜访的时候,Navicat在外网无法访问数据库 3. 什么是HTTP通道近程连贯?通过在服务器上运行PHP服务,配合官网的ntunnel_sqlite.php脚本进行连贯数据库 开始1.找到本机(本电脑) 上的ntunnel_sqlite.php找到navicat装置目录下ntunnel_sqlite.php 2. 将ntunnel_sqlite.php文件搁置到Linux服务器Tips:要搁置到外网能够拜访的地位 3. 验证http服务外网拜访ntunnel_sqlite.php文件 4. 验证连贯sqlite在Database File框中输出sqlite.db文件的门路,留神是绝对于ntunnel_sqlite.php文件所在位置的门路,点击Test Connection测试。本狗以宝塔面板中的default.db作为测试db,门路为ntunnel_sqlite.php同文件夹,显示Connection Success!即为胜利 5. 应用navicat进行HTTP通道连贯新建sqlite连贯抉择应用HTTP通道,通道地址写入方才上方测试的外网地址 惯例选项中,数据库文件写入方才测试的相对路径default.db 点击测试,验证是否胜利 点击确定,生成连贯 连贯胜利 总结本文通过介绍navicat配合HTTP通道,并实操近程连贯SQLite数据库。此文比拟水,只是记录一个不罕用的数据库的连贯步骤,特此记录。

August 24, 2023 · 1 min · jiezi

关于sqlite:Sqlite-并发读写的演进之路

概论sqlite 底层的存储基于 B-tree,B-Tree 对底层存储的根本读写单位是页面,而每个页面都由全局惟一的页面编号与之对应,一般来说页面编号从 1 开始递增。类 B-Tree 的存储引擎批改数据的流程如下图所示: 从上图中,须要辨别 B-Tree 类的存储引擎几个外围的模块: B-Tree 算法模块:从页面管理器中读取页面到内存,进行逻辑的批改,批改结束之后标记该页面为脏页面,这样页面管理器就晓得哪些页面被批改,后续须要进行落盘。页面管理器:负责向 B-Tree 算法模块提供依据页面编号读、写页面的接口。数据库文件:这其实不是一个模块,泛指在磁盘上的数据库相干文件,任何的批改最终都要落到数据库文件。在 sqlite 中,数据库文件是繁多文件,在其余存储引擎里可能是一组相干的文件。最上层的 B-Tree 算法模块,在进行写事务的时候,是首先向页面管理器发动读页面到内存中的申请,留神到 B-Tree 模块并不会间接跟数据库文件打交道,而是通过页面管理器模块(上面会开展说),批改了页面之后标记为“脏页面”,页面管理器最终负责将脏页面落盘到数据库文件中。 当初来谈谈“页面管理器”模块的具体工作,也有的实现称为“缓存管理器(buffer manager)”。这个模块负责:在内存中治理页面 在内存中治理页面。这波及到两局部内容: 如果页面以后不在内存中,须要依据页面编号到磁盘上加载页面。页面也并不是每一次读写时都要到磁盘上加载,有些时候页面曾经在缓存中存在了,这种状况下不须要到磁盘上加载页面数据。于是,“页面管理器”模块还须要负责保护这些内存中的页面缓存,何时淘汰这些页面、淘汰哪些内存中的页面、何时真正从磁盘上加载,都是这个模块的工作。对外部而言(这里的内部更多的是 B-Tree 算法模块),其实不须要也看不到页面缓存的细节,页面管理器对外提供依据页面编号读、写页面接口即可。谬误的复原,事务的治理、比方: 一次事务要批改 N 个页面,批改到两头的时候,过程解体了,这时候重新启动时须要复原到这个事务之前的数据胜利启动,即须要提供回滚事务的性能。同样的一个事务要批改 N 个页面,在事务还未提交的时候,如果事务级别不是 read uncommitted, 那么后面的批改成果不能被其余事务可见,这也是页面管理器须要做的事件,毕竟它对外提供了读、写页面的接口,同一个页面编号的页面什么时候的内容可见都由它来决定。有了这些根底的理解,咱们来看看 sqlite 在并发读写方面的演进之路 journal最早的页面管理器实现是基于 Journa l文件的,这个文件存储页面在批改之前的内容: 能够看到的是: Journal 文件存储了一个事务所要批改的页面在批改之前的内容,这个定义有点拗口,权且称为“旧页面内容”。每次一个事务提交之后,意味着这个事务所有队页面的批改都曾经落到了数据库文件中,这时候 Journal 文件里保留的旧页内容就不再须要了,能够被删除了。因为每次事务批改都要落盘到数据库文件,这些落盘操作波及到屡次磁盘寻道,即一次事务屡次随机磁盘寻道,这样代价其实是很大的。当须要事务回滚的性能时,页面管理器就能够从 Journal 文件中读出来旧页面内容笼罩回去。尽管这个算法很简略,然而缺点也显著:它没有任何的读写并发反对。每次开始一个写事务,从开始写事务,到这个写事务提交实现的过程两头,其余的读写事务都不能开始,能够说是“一写全卡住”。WAL从下面的剖析能够看出,以 Journal 文件的机制,每次写事务: 须要把内容批改全副落盘到数据库文件才算实现。这个过程两头,不能同时存在其余并发的读、写操作。从 sqlite3.7.0 版本开始(SQLite Release 3.7.0 On 2010-07-211,sqlite 引入了更常见的 WAL 机制来解决页面的读写并发问题,WAL 的原理如下图所示: WAL 机制中,事务对页面的批改: 并没有马上落到数据库文件里,而是首先写入 WAL 文件中。这样有两个益处: WAL文件是 append-only 的文件,在文件结尾处增加新内容,对写磁盘文件这种操作而言是更快的,因为少了很多磁盘寻道的流程。有了 WAL 之后,读写并发有了一些改善:因为事务的批改并没有马上落盘到数据库文件,所以就不可见,后续如果须要回滚事务的批改也更容易:不要这个事务批改的那局部 WAL 内容即可。因为批改有时候还未落盘,须要保护一个 wal 中页面的索引,用于依据页面编号定位到 WAL 中的页面。因为 wal 索引能够管制哪些 wal 文件内容“可见”,于是就能管制未提交的事务批改对读操作并不可见了。WAL 文件不能始终增长上来,须要定期把 WAL 文件中曾经提交的事务批改内容落盘到数据库文件,这个流程被称为“checkpoint”。在“checkpoint”之后,wal 索引就能够批改了。尽管 checkpoint 过程将 WAL 文件中的内容落盘到数据库文件,依然是针对数据库文件的随机写流程,有很多磁盘寻道操作,然而因为一次 checkpoint 累计了屡次写事务一次性落盘,代价小了一些。尽管同一时间依然只能有一个写事务在进行,然而读事务同时存在多个。其外围起因是因为批改并没有马上间接落盘到数据库文件中,这样批改的可见性就能够由 wal 索引来管制,即:写事务只管写,读事务只管读,只有管制这些写事务的批改不在 wal 索引中可见即可。WAL 尽管反对“一写多读”,而不是 Journal 文件那样的“一写全卡住”,然而还有一个问题没有解决:在做 checkpoint 操作的时候,连写事务也不能进行了。 ...

September 8, 2022 · 2 min · jiezi

关于sqlite:搭建盘口如何使用飞鸟-SQLite-生成平台系统文档和软件配置开发

搭建盘口如何应用飞鸟 SQLite 生成平台零碎文档和软件配置开发?思否用户TG电报duotebb版权,转载请注明出处。为什么要应用 SQLite 来保护文档?或者用它来管理软件配置文件? 在本文中,我将解释益处并演示如何应用 SQLite 生成文档。 我为一个大型衰弱我的项目保护了几份文件。依据文档的指标受众,文档以不同的形式显示雷同的数据。 第一个是公布规范列表,第二个是基于角色的访问控制矩阵。数据集随着我的项目对新用例的响应而增长和演变。领有从数据生成文档的自动化流程至关重要。 我没有尝试别离保护这两个文档并可能使它们不同步,而是决定应用 SQLite 来保留数据的地方正本并应用 SQL(结构化查询语言)查问来生成所需的文档格局代码。 SQLite 应用单个终端命令来拜访其所有性能。这个命令sqlite3能够轻松编写文档创立过程中的步骤脚本。将 SQLite 命令链接在一起的能力使该过程灵便且可反复。此外,这些命令的输入能够通过管道传输到内部文件中。大多数文档构建过程能够包含这些内部文件,以确保在文档中出现最新数据。 利用 SQL 语法的弱小性能,您还能够控制数据的程序以及跨多个页面的大型后果的拆分。 此外,因为整个过程是脚本化的,在我的例子中应用make,能够应用源代码管制来治理生成文档的命令。应用源代码版本零碎,如 Git GNU Make,能够让您跟踪对文档数据所做的更改历史记录。 文件类型我保护的数据集是区域衰弱信息替换中应用的医疗文档类型列表。数据集蕴含三个字段:文档名称、临床代码和模式。我将列表保留在一个简略的逗号分隔 (CSV) 数据文件中,例如:Discharge Letter,823701000000103,SNOMED-CTDrug/substance use,1064501000000103,SNOMED-CTEnd of Life Care Document,861411000000103,SNOMED-CT将数据导入 SQLiteCSV 文件是我的主数据源。如果我须要更改条目,我会在 CSV 文件中进行更改,而后重新处理数据。如果我向衰弱替换增加新的文档类型,我会将条目增加到 CSV 文件中,而后运行make load将数据加载到 SQLite 中以进行进一步解决。 我应用GNU Make来运行我须要的命令。没有必要应用make。如果您更相熟应用 bash 脚本或 Windows 批处理文件,请应用它们。对我来说,make 零碎是将一组命令组合在一起的简略办法。 make 零碎在您的我的项目文件夹中读取一个 makefile。makefile 蕴含要依照您要运行的程序运行的命令。makefile 能够有多个指标;由一个关键字后跟一个冒号字符示意。当您运行后跟指标名称的 make 命令时,只会执行该局部中的命令。 我在我的makefile中应用了两个指标,使我能够轻松加载数据并生成文档文档。上面的代码显示了我用来将 CSV 文件导入数据库表的 SQLite 命令。load: sqlite3 -cmd ".read create_tables.sql" -cmd ".mode csv" -cmd ".import document_types.csv document_types" document_types.db < count.sqlsqlite3命令行实用程序的-cmd参数容许您指定须要执行的命令。咱们须要应用多个-cmd序列来顺次执行咱们须要的命令。在下面的示例中,咱们首先读取并运行create_tables.sql文件中的 SQL 语句,该文件会删除并从新创立数据库文件document_types.db中的文档类型表。 ...

September 1, 2022 · 2 min · jiezi

关于sqlite:COMP3311-SQLite

COMP3311 21T1 - Assignment 2SQL, Python, SQLiteDatabase SystemsLast updated: Monday 29th Mar 05:47pm (most recent updates are in [..])Due : Friday 16th April 17:00AimsThis assignment aims to give you practice inuse of SQL in SQLite (i.e., sqlite3)writing scripts in Python that interact with a databasepopulating a RDBMS with a larger dataset, and analysing the data; making test data for testing database applications;Your task is to complete the functionality of some command-line tools via a combination of database code and Python code.SummarySubmission: Login to Course Web Site > Assignments > Assignment 2 > Assignment 2 Specification > Make Submission > upload required files > [Submit]Required Files (total 3 files): msearch, toprank, shortestYou may also submit the assignment via the give command from CSE machines: give cs3311 a2 msearch toprank shortestDeadline: Friday 16 April 2021 @ 17:00Late Penalty: Late submissions will have marks deducted from the maximum achievable mark at the rate of 2% of the total mark per hour that they are late (i.e.,48% per day).This assignment contributes 20 marks toward your total mark for this course.Downloads: a2.tgz, a2.zipNote that a2.tgz and a2.zip contain the same material.Each archive contains the IMDB database dump a2.db, plus a sample Python code file called sample.What To Do Now:read this specification carefully and completelylogin to a CSE linux machinecreate a directory for this assignmentunpack the supplied zip file into this directoryget familiar with the schema and data by exploring and querying the provided database using the command: sqlite3 a2.dbfamiliarise (read the code) yourself with the provided sample Python code file called sampletry out the sample code by running: ./sample YEAR where YEAR is a number representing a year, e.g., 1989 make sure sample is executable (by chmod u+x sample) and run itcomplete the assignment tasks using sample as a reference templatesubmit all these files (the 'Required Files') via WebCMS3 or give as described aboveDetails of the above steps are further elaborated below. You can edit and run the Python files on any CSE machines.IntroductionA successful movie (including TV show) not only entertains audience, but also enables film companies to gain tremendous profit. A lot of factors (such as gooddirectors, experienced actors, etc) are important for creating good movies. Nevertheless, famous directors and actors usually bring an attractive box-office income,but they do not necessarily guarantee a highly rated imdb score. This assignment is based on an IMDB dataset to build several small Python commands to showinteresting results.The dataset itself contains around 5000 movies, spanning across 100 years in 66 countries. There are more than 2000 movie directors, and thousands ofactors/actresses. It also contains the IMDB rating score, numbers of votes and various facebook likes. To give you a feel for the kind of data that you are dealing with,a (unordered) glimpse of the dataset is included below:cs3311@wagner:~/sqlite/a2$ sqlite3 a2.dbSQLite version 3.27.2 2019-02-25 16:06:06Enter ".help" for usage hints.sqlite>sqlite> select * from movie limit 10;1|Avatar|2009|PG-13|178|English|USA|760505847|237000000|7352|Pirates of the Caribbean: At World's End|2007|PG-13|169|English|USA|309404152|300000000|5553|Spectre|2015|PG-13|148|English|UK|200074175|245000000|17764|The Dark Knight Rises|2012|PG-13|164|English|USA|448130642|250000000|10786|John Carter|2012|PG-13|132|English|USA|73058679|263700000|22177|Spider-Man 3|2007|PG-13|156|English|USA|336530303|258000000|6258|Tangled|2010|PG|100|English|USA|200807262|260000000|9229|Avengers: Age of Ultron|2015|PG-13|141|English|USA|458991599|250000000|141010|Harry Potter and the Half-Blood Prince|2009|PG|153|English|UK|301956980|250000000|111711|Batman v Superman: Dawn of Justice|2016|PG-13|183|English|USA|330249062|250000000|2180sqlite>sqlite> .headers onsqlite>sqlite> select * from movie limit 10;id|title|year|content_rating|duration|lang|country|gross|budget|director_id1|Avatar|2009|PG-13|178|English|USA|760505847|237000000|7352|Pirates of the Caribbean: At World's End|2007|PG-13|169|English|USA|309404152|300000000|5553|Spectre|2015|PG-13|148|English|UK|200074175|245000000|17762021/4/9 COMP3311 21T1 - Assignment 2https://www.cse.unsw.edu.au/~... 2/74|The Dark Knight Rises|2012|PG-13|164|English|USA|448130642|250000000|10786|John Carter|2012|PG-13|132|English|USA|73058679|263700000|22177|Spider-Man 3|2007|PG-13|156|English|USA|336530303|258000000|6258|Tangled|2010|PG|100|English|USA|200807262|260000000|9229|Avengers: Age of Ultron|2015|PG-13|141|English|USA|458991599|250000000|141010|Harry Potter and the Half-Blood Prince|2009|PG|153|English|UK|301956980|250000000|111711|Batman v Superman: Dawn of Justice|2016|PG-13|183|English|USA|330249062|250000000|2180sqlite>sqlite> select * from rating limit 10;movie_id|num_critic_for_reviews|num_user_for_reviews|num_voted_users|movie_facebook_likes|cast_total_facebook_likes|imdb_1|723|3054|886204|33000|4834|7.92|302|1238|471220|0|48350|7.13|602|994|275868|85000|11700|6.84|813|2701|1144337|164000|106759|8.56|462|738|212204|24000|1873|6.67|392|1902|383056|0|46055|6.28|324|387|294810|29000|2036|7.89|635|1117|462669|118000|92000|7.510|375|973|321795|10000|58753|7.511|673|3018|371639|197000|24450|6.9sqlite>sqlite> .mode columnsqlite>sqlite> select * from director limit 10;id name facebook_likes ...

April 27, 2022 · 17 min · jiezi

关于sqlite:SQLite简介

SQLite简介常见的关系型数据库有SQLite,MySQL,SQL Server等,通常学习关系型数据库时不会应用SQLite,然而SQLite 是世界上应用最宽泛的数据库引擎。SQLite 内置于所有手机和大多数计算机中,并捆绑在人们每天应用的有数其余应用程序中。SQLite 是一个由C语音开发的嵌入式库,具备小型、 疾速、 自蕴含、 高牢靠、 功能齐全等特点。 文章以3.32.2版本为例讲述SQLite的一些基本知识 SQLite常用命令和示例在命令行中应用SQLite命令,须要先执行sqlite3命令,进入SQLite提示符。 创立或关上数据库有两种办法能够创立或关上数据库,一个是在sqlite3命令前面加上数据库门路,另一个是应用点命令.open sqlite3 /.../xxx.db法通过执行这个命令进入SQLite提示符时,如果数据库文件曾经存在,则间接关上对应数据库,否则不会立刻在对应门路创立xxx.db文件。要等到执行了增加数据表,视图等数据库对象的命令之后。 示例 先执行如下命令,此时没有创立出comms_ease.db文件 sqlite3 comms_ease.db 再执行如下命令创立一张表,在当前目录呈现comms_ease.db文件 .open /.../xxx.db法应用.open是一个点命令,应用它须要先执行sqlite3命令进入SQLite提示符。.open命令的应用形式也是在命令前面追加数据库门路,不过和sqlite3 /.../xxx.db法不同的是,执行.open命令后,数据库文件会被间接创立进去,不须要再创立数据库对象。 创立表SQLite的创立语句为CREATE TABLE,残缺的创立表语句内容丰盛,除了创立一般表外,还能具备判断表是否曾经存在,创立长期表等能力。常见的创立一般表的句式为。 CREATE TABLE 表名 ( 列1名称 列类型 以空格隔开的一个或多个列束缚, 列2名称 列类型 以空格隔开的一个或多个列束缚, ...);默认状况下,一张表的最大列数为2000,每一行能存下的最大字节数为十亿,能满足绝大多数的需要,创立一般表的示例如下 CREATE TABLE table_comms_ease ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, value VARCHAR);例子中,创立了一个名为table_comms_ease的数据表,蕴含两列,第一列是id,类型是整形,不能为空,而且是表的主键,并能够主动生成; 第二列是value,类型为字符串 在表名曾经存在的状况下,调用CREATE TABLE 表名语句会报错,要防止,能够应用CREATE TABLE IF NOT EXISTS 表名语句。如果不存在,则创立表,如果存在,则什么都不做。示例如下 CREATE TABLE IF NOT EXISTS table_comms_ease ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, value VARCHAR);如果心愿长期存储一些数据,而且只对以后连贯无效,能够试试长期表。长期表的创立语句为CREATE TEMP TABLE。长期表只对以后数据库连贯无效,从新建设连贯或者同时存在的其余连贯都无法访问到。示例如下 ...

March 25, 2022 · 3 min · jiezi

关于sqlite:SQLite-3380-现已正式发布

本月 22 日,SQLite 3.38.0正式公布。 SQLite 是一个小型、疾速、自蕴含、高可靠性、全功能的嵌入式 SQL 数据库引擎,是世界上使用量最大的数据库引擎。 次要更新内容增加了->和->\> 运算符以便于解决 JSON。新的运算符与 MySQL 和 PostgreSQL 兼容。JSON 函数当初是内置的。不再须要应用-DSQLITE\_ENABLE\_JSON1编译时选项来启用 JSON 反对。默认状况下启用 JSON。应用新的-DSQLITE\_OMIT\_JSON编译时选项禁用 JSON接口。 日期和工夫性能的加强:  增加了unixepoch() 函数。增加了auto 修饰符和julianday 修饰符。将printf() SQL 函数重命名为format()以取得更好的兼容性。保留原始printf() 名称作为别名以实现向后兼容性。 增加了sqlite3\_error\_offset() 接口,该接口有时能够帮忙将 SQL error 本地化为输出 SQL 文本中的特定字符,以便应用程序能够提供更好的谬误音讯。加强了virtual tables的接口如下:  增加了sqlite3\_vtab\_distinct() 接口。 增加了sqlite3\_vtab\_rhs_value() 接口。 增加了新的 operator 类型SQLITE\_INDEX\_CONSTRAINT_LIMIT和SQLITE\_INDEX\_CONSTRAINT_OFFSET。 增加了sqlite3\_vtab\_in()接口(及相干接口)以使虚构表可能一次解决所有 IN operator 束缚,而不是独自解决 IN operator 右侧的每个值。CLI 加强性能:  Columnar output modes失去加强,能够正确处理嵌入在文本中的制表符和换行符。向Columnar output modes增加了“--wrap N”、“--wordwrap on”和“--quote”等选项。 增加了.mode qbox别名。.import 命令主动打消列名的歧义。 应用新的sqlite3\_error\_offset()接口提供更好的谬误音讯。 Query planner 加强性能: 应用 Bloom filter 来减速大型剖析查问。 应用 balanced merge tree 来评估具备 ORDER BY 子句的 UNION 或 UNION ALL 复合 SELECT 语句。 扭转了ALTER TABLE语句,当PRAGMA writable_schema=ON时,静默疏忽sqlite_schema table中没有解析的条目。

February 24, 2022 · 1 min · jiezi

关于sqlite:SQLite原理与运用

SQLite是一个开源的关系型数据库,实现自容纳、零配置、反对事务的SQL数据库引擎。其特点是高度便携、使用方便、结构紧凑、高效、牢靠。并且SQLite是在世界上最宽泛部署的 SQL 数据库引擎。SQLite 源代码不受版权限度。 本篇文章书要是记述了SQLite的根本架构以及SQLite的几种操作形式,其中比拟重要的就是ADB Shell命令操作与SQL语句,另外在开发中还是Litepal这款开源ORM框架用的比拟多一些,应用起来的确十分不便。 <!-- more --> SQLite根本构造 接口由SQLite C API组成,程序与SQLite交互的根底就是用C语言编写的API,JDBC也只是JNI调用而已。 在编译器中,词法分析器与语法分析器把SQL翻译为语法树,Code Generator依据语法树生产SQLite的汇编代码,交给虚拟机执行。 虚拟机,与Java虚拟机执行class中的指令相似,SQLite的汇编代码由SQLite虚拟机来执行,由虚拟机负责SQL到数据存取的交互,对于虚拟机的更多内容能够查看官网《The Virtual Database Engine of SQLite》 更多对于SQLite架构的内容能够查看官网《 Architecture of SQLite 》, 外面介绍的比拟具体。 SQLite数据类型 类型类型阐明NULL这个值为空值VARCHAR(n)长度不固定且其最大长度为 n 的字串,n不能超过 4000CHAR(n)长度固定为n的字串,n不能超过 254INTEGER值被标识为整数,根据值的大小能够顺次被存储为1,2,3,4,5,6,7,8REAL所有值都是浮动的数值,被存储为8字节的IEEE浮动标记序号TEXT值为文本字符串,应用数据库编码存储(TUTF-8, UTF-16BE or UTF-16-LE)BLOB值是BLOB数据块,以输出的数据格式进行存储。如何输出就如何存储,不扭转格局DATA蕴含了 年份、月份、日期TIME蕴含了 小时、分钟、秒Android中操作SQLiteSQLite的SQL语句其实和一般SQL没什么特地的不同,Windows下可视化操作SQLite能够应用SQLite Expert Personal 4 - 这款工具,下载地址如下:http://www.sqliteexpert.com/v4/SQLiteExpertPersSetup64.exe。关上后即可通过图形化界面的形式操作SQLite,同样也能够通过SQL语句来操作: # 建表create table stu_info ( id integer primary key autoincrement, name varchar(30) not null, age integer , gender varchar(2) not null)# 插入数据insert into stu_info(name, age, gender) values ('Mike', 24, '女');insert into stu_info(name, age, gender) values ('Jone', 26, '男');insert into stu_info(name, age, gender) values ('Tom', 28, '女');# 查问数据select * from stu_info;# 删除数据delete from stu_info where id = 13;# 批改数据update stu_info set name = 'Jack' where id = 10;# 按条件查问select * from stu_info where age = 24; 当初次要还是看看在Android平台如何应用吧!SQLiteOpenHelper:Android平台里一个数据库辅助类,用于创立或关上数据库,并且对数据库的创立和版本进行治理。 ...

December 5, 2020 · 7 min · jiezi

关于sqlite:Android-原生-SQLite-数据库的一次封装实践

本文首发于 vivo互联网技术 微信公众号  链接: https://mp.weixin.qq.com/s/CL4MsQEsrWS8n7lhXCOQ_g 作者:Li Bingyan本文次要讲述原生SQLite数据库的一次ORM封装实际,给应用原生数据库操作的业务场景(如:自身是一个SDK)带来一些启发和参考意义,以及追随框架的实现思路对数据库操作、APT、泛型等概念更深一层的了解。 实现思路:通过动静代理获取申请接口参数进行SQL拼凑,并以接口返回值(泛型)类型的RawType和ActualType来适配调用形式和执行后果,以此将理论SQL操作封装在其外部来简化数据库操作的目标。 一、背景 毫无疑问,对于Android数据库当初曾经有很多风行好用的ORM框架了,比方:Room、GreenDao、DBFlow等都提供了简洁、易用的API,尤其是谷歌开源的Room是目前最支流的框架。 既然曾经有了这么多数据库框架了,为什么还要入手封装所谓本人的数据库框架呢?对于一般 APP 的开发的确齐全不须要,这些框架中总有一款能够齐全满足你日常需要;但如果你是一个SDK开发者,而且业务是一个比拟依赖数据库操作的场景,如果限度不能依赖第三方SDK(次要考量维护性、问题排查、稳定性、体积大小),那就不得不本人去写原生SQLite操作了,这将是一个既繁琐又容易出错的过程(数据库降级/降级/关上/敞开、多线程状况、拼凑SQL语句、ContentValues插数据、游标遍历/敞开、Entity转换等)。 为了在SDK的开发场景中防止上述繁琐且容易出错的问题,于是就有了接下来的一系列思考和革新。 二、预期目标能简化原生的增删改查简短操作,不要再去写容易出错的两头逻辑步骤主动生成数据库的建表、降级/降级逻辑易用的调用接口(反对同步/异步、线程切换)稳固牢靠,无性能问题三、计划调研察看咱们日常业务代码能够发现:一次数据库查问与一次网络申请在流程上是极为类似的,都是通过结构申请、发动申请、两头步骤、获取后果、处理结果等几个步骤。因而感觉能够将数据库操作以网络申请的形式进行形象和封装,其具体比照如下表所示: 通过上述相似性的比照并综合现有ORM框架来思考切入口,首先想到的是应用注解: 支流Room应用的是编译时注解(更有利于性能),但在具体编码实现Processor过程中发现增删改查操作的出参和入参解决有点过于繁琐(参考Room实现),不太实用于自身就是一个SDK的场景,最终pass掉了。 运行时注解解决绝对更简略一些(接口和参数较容易适配、解决流程也能够间接写咱们相熟的安卓原生代码),而且后面曾经有了赫赫有名的网络申请库Retrofit应用运行时注解实现网络申请的典型范例,因而能够依葫芦画瓢尝试实现一下数据库增删改查操作,也是本次革新最终的实现计划。 置信大部分安卓客户端开发同学都用过Retrofit(网络申请罕用库),其大略原理是:应用动静代理获取接口对应的Method对象为入口,并通过该Method对象的各种参数(注解润饰)结构出Request对象抛给okhttp做理论申请,返回值则通过Conveter和Adapter适配申请后果(bean对象)和调用形式,如:Call<List<Bean>>、Observable<List<Bean>>等。 它以这种形式将网络申请的外部细节封装起来,极大简化了网络申请过程。依据其相似性,数据库操作(增删改查)也能够应用这个机制来进一步封装。 对于数据库的建表、降级、降级等这些容易出错的步骤,最好是不要让使用者本人去手动写这部分逻辑,计划应用编译时注解来实现(Entitiy类和字段属性、版本号通过注解对应起来),在编译期间主动生成SQLiteOpenHelper的实现类。 综合以上两局部根本实现了所有痛点操作不再须要调用者去关注(只需关注传参和返回后果),于是将其独立成一个数据库模块,取名Sponsor( [spnsr] ),寓意一种散发器或调度器计划,目前已在团队外部应用。 四、Sponsor调用示例1、Entity定义://Queryable:示意一个可查问的对象,有办法bool convert(Cursor cursor),将cursor转换为Entitiy//Insertable:示意一个可插入的对象,有办法ContentValues convert(),将Entitiy转换为ContentValuespublic class FooEntity implements Queryable, Insertable { /** * 数据库自增id */ private int id; /** * entitiy id */ private String fooId; /** * entity内容 */ private String data; //其余属性 //getter()/setter()}2、接口定义,申明增删改查接口:/** * 插入 * @return 最初一个row Id */@Insert(tableName = FooEntity.TABLE)Call<Integer> insertEntities(List<FooEntity> entities);/** * 查问 * @return 获取的entitiy列表 */@Query("SELECT * FROM " + FooEntity.TABLE + " WHERE " + FooEntity.CREATE_TIME + " > " + Parameter1.NAME + " AND " + FooEntity.CREATE_TIME + " < " + Parameter2.NAME + " ORDER BY " + FooEntity.CREATE_TIME + " ASC LIMIT " + Parameter3.NAME)Call<List<FooEntity>> queryEntitiesByRange(@Parameter1 long start, @Parameter2 long end, @Parameter3 int limit);/** * 删除 * @return 删除记录的条数 */@Delete(tableName = FooEntity.TABLE, whereClause = FooEntity.ID + " >= " + Parameter1.NAME + " AND " + FooEntity.ID + " <= " + Parameter2.NAME)Call<Integer> deleteByIdRange(@Parameter1 int startId, @Parameter2 int endId);3、创立FooService实例:Sponsor sponsor = new Sponsor.Builder(this) .allowMainThreadQueries() //是否运行在主线程操作,默认不容许 //.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //rxjava //.addCallAdapterFactory(Java8CallAdapterFactory.create()) //java8 //.addCallAdapterFactory(LiveDataCallAdapterFactory.create()) //livedata .logger(new SponsorLogger()) //日志输入 .build();//调用create()办法创立FooService实例,实际上是返回了FooService的动静代理对象FooService mFooService = sponsor.create(FooService.class);4、插入Entitiy数据://结构Entity列表List<FooEntity> entities = new ArrayList<>();//add entities//同步形式//rowId为最终的自增id(同原生insert操作返回值)//final int rowId = mFooService.insertEntities(entities).execute();//异步形式mFooService.insertEntities(entities).enqueue(new Callback<Integer>() { @Override public void onResponse(Call<Integer> call, Integer rowId) { //success } @Override public void onFailure(Call<Integer> call, Throwable t) { //failed }});5、查问参数指定数据库记录,并转换为Entitiy对象列表:List<FooEntity> entities;//entities为查问后果汇合entities = mFooService.queryEntitiesByRange(1, 200, 100).execute();6、删除参数指定数据库记录,返回总共删除的记录条数://cout为删除的条数int count = mFooService.deleteByIdRange(0, 100).execute();注: ...

August 10, 2020 · 4 min · jiezi

superset 性能优化1-已经使用中的superset更改默认数据源sqlite到mysql

已经使用中的superset更改默认数据源sqlite到mysql1.提取sqlite db(由于当前使用 docker 需要到宿主)docker cp CONTAINER_NAME:/var/lib/superset/superset.db /opt2.数据导出然后使用 navicat 数据库工具打开sqlite文件选择导出向导csv数据保存到一个文件夹3.创建mysql数据库与账号4.安装superset python mysql支持 修改superset 数据库config(这个网上很多配置)5.然后使用 superset db upgrade 初始化mysql数据表结构(如果直接导入sqlite导出的csv字段默认都是varchar255,这个会有问题)6.更改数据表,删除全部数据表外键(删除外键是我这边的做法,具体自己可以优化,这里建议暂停superset来操作会快些)7.更改部分报错类型Incorrect datetime value: ‘0000-00-00 00:53:00’ for column ’last_login’ at row 1[ERR] INSERT INTO new_superset.ab_user删除 ab_user.cvs last_login created_on changed_on 数据同时ab_user表更改last_login,created_on,changed_on 字段类型为timestamp(6)1406 - Data too long for column ‘sql’ at row 1[ERR] INSERT INTO new_superset.queryquery表更改sql,’executed_sql’ 字段类型longtext 1062 - Duplicate entry ‘[impala].[dw]’ for key ’name’[ERR] INSERT INTO new_superset.`ab_view_menu删除唯一索引dbs.password 如果乱码需要删除,不然打开数据源页面会报错8.使用navicat导入向导导入csv数据到mysql

January 28, 2019 · 1 min · jiezi

Android开发音视频应用之构建媒体浏览器服务

构建媒体浏览器服务您的应用必须MediaBrowserService在其清单中声明带有intent-filter。您可以选择自己的服务名称; 在以下示例中,它是“MediaPlaybackService”。<service android:name=".MediaPlaybackService"> <intent-filter> <action android:name=“android.media.browse.MediaBrowserService” /> </intent-filter></service>注意:推荐的实现MediaBrowserService 是MediaBrowserServiceCompat。这是在media-compat支持库中定义的 。在整个页面中,术语“MediaBrowserService”指的是of的一个实例MediaBrowserServiceCompat。初始化媒体会话当服务收到onCreate()生命周期回调方法时,它应该执行以下步骤:创建并初始化媒体会话设置媒体会话回调设置媒体会话令牌onCreate()下面的代码演示了以下步骤:public class MediaPlaybackService extends MediaBrowserServiceCompat { private static final String MY_MEDIA_ROOT_ID = “media_root_id”; private static final String MY_EMPTY_MEDIA_ROOT_ID = “empty_root_id”; private MediaSessionCompat mMediaSession; private PlaybackStateCompat.Builder mStateBuilder; @Override public void onCreate() { super.onCreate(); // Create a MediaSessionCompat mMediaSession = new MediaSessionCompat(context, LOG_TAG); // Enable callbacks from MediaButtons and TransportControls mMediaSession.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player mStateBuilder = new PlaybackStateCompat.Builder() .setActions( PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE); mMediaSession.setPlaybackState(mStateBuilder.build()); // MySessionCallback() has methods that handle callbacks from a media controller mMediaSession.setCallback(new MySessionCallback()); // Set the session’s token so that client activities can communicate with it. setSessionToken(mMediaSession.getSessionToken()); }}管理客户连接MediaBrowserService有两种处理客户端连接的方法: onGetRoot()控制对服务的访问,并 onLoadChildren() 为客户端提供构建和显示MediaBrowserService内容层次结构菜单的能力。使用控制客户端连接 onGetRoot()该onGetRoot()方法返回内容层次结构的根节点。如果方法返回null,则拒绝连接。要允许客户端连接到您的服务并浏览其媒体内容,onGetRoot()必须返回一个非空的BrowserRoot,它是一个表示您的内容层次结构的根ID。要允许客户端在不浏览的情况下连接到MediaSession,onGetRoot()仍必须返回非null的BrowserRoot,但根ID应表示空的内容层次结构。典型的实现onGetRoot()可能如下所示:@Overridepublic BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { // (Optional) Control the level of access for the specified package name. // You’ll need to write your own logic to do this. if (allowBrowsing(clientPackageName, clientUid)) { // Returns a root ID that clients can use with onLoadChildren() to retrieve // the content hierarchy. return new BrowserRoot(MY_MEDIA_ROOT_ID, null); } else { // Clients can connect, but this BrowserRoot is an empty hierachy // so onLoadChildren returns nothing. This disables the ability to browse for content. return new BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null); }}在某些情况下,您可能希望实施白/黑名单方案来控制连接。有关白名单的示例,请参阅通用Android音乐播放器示例应用程序中的PackageValidator类。注意:您应该考虑提供不同的内容层次结构,具体取决于进行查询的客户端类型。特别是,Android Auto会限制用户与音频应用的互动方式。有关更多信息,请参阅为自动播放音频。您可以查看clientPackageName连接时间以确定客户端类型,并BrowserRoot根据客户端(或者rootHints 如果有)返回不同的客户端类型。与内容沟通内容 onLoadChildren()在客户端连接之后,它可以通过重复调用MediaBrowserCompat.subscribe()来构建UI的本地表示来遍历内容层次结构。该subscribe()方法将回调发送onLoadChildren()到服务,该服务返回MediaBrowser.MediaItem对象列表。每个MediaItem都有一个唯一的ID字符串,它是一个不透明的标记。当客户想要打开子菜单或播放项目时,它会传递ID。您的服务负责将ID与相应的菜单节点或内容项相关联。一个简单的实现onLoadChildren()可能如下所示:@Overridepublic void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) { // Browsing not allowed if (TextUtils.equals(MY_EMPTY_MEDIA_ROOT_ID, parentMediaId)) { result.sendResult(null); return; } // Assume for example that the music catalog is already loaded/cached. List<MediaItem> mediaItems = new ArrayList<>(); // Check if this is the root menu: if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) { // Build the MediaItem objects for the top level, // and put them in the mediaItems list… } else { // Examine the passed parentMediaId to see which submenu we’re at, // and put the children of that menu in the mediaItems list… } result.sendResult(mediaItems);}注意:MediaItem MediaBrowserService传递的对象不应包含图标位图。使用Uri的,而不是调用 setIconUri() 在生成MediaDescription的每个项目有关如何实施的示例onLoadChildren(),请参阅MediaBrowserService和Universal Android Music Player示例应用程序。媒体浏览器服务生命周期Android 服务的行为取决于它是启动还是绑定到一个或多个客户端。创建服务后,可以启动,绑定或同时启用它。在所有这些状态中,它功能齐全,可以执行其设计的工作。不同之处在于服务存在多长时间。绑定的服务在其所有绑定的客户端解除绑定之前不会被销毁。可以显式停止和销毁已启动的服务(假设它不再绑定到任何客户端)。当MediaBrowser另一个活动中的运行连接到a时MediaBrowserService,它会将活动绑定到服务,从而使服务绑定(但不启动)。此默认行为内置于MediaBrowserServiceCompat类中。只有绑定(并且未启动)的服务在其所有客户端解除绑定时销毁。如果此时UI活动断开连接,则服务将被销毁。如果您还没有播放任何音乐,这不是问题。但是,当播放开始时,用户可能希望即使在切换应用后也能继续收听。当您取消绑定UI以使用其他应用程序时,您不希望销毁播放器。因此,您需要确保在通过调用开始播放服务时启动该服务startService()。无论是否绑定,必须明确停止已启动的服务。这可确保即使控制UI活动解除绑定,您的播放器也会继续执行。要停止已启动的服务,请致电Context.stopService()或stopSelf()。系统会尽快停止并销毁服务。但是,如果一个或多个客户端仍然绑定到该服务,则停止该服务的调用将延迟,直到其所有客户端解除绑定。它的生命周期MediaBrowserService由创建方式,绑定到它的客户端数量以及从媒体会话回调接收的调用控制。总结一下:该服务在响应媒体按钮或活动绑定到它(通过其连接后MediaBrowser)启动时创建。媒体会话onPlay()回调应包括调用的代码startService()。这可确保服务启动并继续运行,即使MediaBrowser绑定到它的所有UI 活动都解除绑定。该onStop()回调应该调用stopSelf()。如果服务已启动,则会停止该服务。此外,如果没有绑定的活动,服务将被销毁。否则,服务将保持绑定,直到其所有活动解除绑定。(如果startService()在销毁服务之前收到后续呼叫,则取消挂起停止。)以下流程图演示了如何管理服务的生命周期。变量计数器跟踪绑定客户端的数量:将MediaStyle通知与前台服务一起使用当服务正在播放时,它应该在前台运行。这使系统知道服务正在执行有用的功能,如果系统内存不足,则不应该被杀死。前台服务必须显示通知,以便用户知道它并可以选择控制它。该onPlay()回调应该把服务的前景。(请注意,这是“前景”的特殊含义。虽然Android在前台考虑服务以进行流程管理,但是对于用户,播放器正在后台播放,而其他应用程序在“前景”中可见屏幕。)当服务在前台运行时,它必须显示通知,理想情况下是一个或多个传输控件。通知还应包括会话元数据中的有用信息。在播放器开始播放时构建并显示通知。这样做的最佳位置是MediaSessionCompat.Callback.onPlay()方法内部。以下示例使用 NotificationCompat.MediaStyle专为媒体应用设计的。它显示了如何构建显示元数据和传输控件的通知。便捷方法 getController() 允许您直接从媒体会话创建媒体控制器。// Given a media session and its context (usually the component containing the session)// Create a NotificationCompat.Builder// Get the session’s metadataMediaControllerCompat controller = mediaSession.getController();MediaMetadataCompat mediaMetadata = controller.getMetadata();MediaDescriptionCompat description = mediaMetadata.getDescription();NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId);builder // Add the metadata for the currently playing track .setContentTitle(description.getTitle()) .setContentText(description.getSubtitle()) .setSubText(description.getDescription()) .setLargeIcon(description.getIconBitmap()) // Enable launching the player by clicking the notification .setContentIntent(controller.getSessionActivity()) // Stop the service when the notification is swiped away .setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP)) // Make the transport controls visible on the lockscreen .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) // Add an app icon and set its accent color // Be careful about the color .setSmallIcon(R.drawable.notification_icon) .setColor(ContextCompat.getColor(context, R.color.primaryDark)) // Add a pause button .addAction(new NotificationCompat.Action( R.drawable.pause, getString(R.string.pause), MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_PLAY_PAUSE))) // Take advantage of MediaStyle features .setStyle(new MediaStyle() .setMediaSession(mediaSession.getSessionToken()) .setShowActionsInCompactView(0) // Add a cancel button .setShowCancelButton(true) .setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP)));// Display the notification and place the service in the foregroundstartForeground(id, builder.build());使用MediaStyle通知时,请注意这些NotificationCompat设置的行为:使用时 setContentIntent(),您的服务会在单击通知时自动启动,这是一个方便的功能。在像锁屏这样的“不受信任”情况下,通知内容的默认可见性是 VISIBILITY_PRIVATE 。您可能希望在锁屏上看到传输控件,这样 VISIBILITY_PUBLIC 就可以了。设置背景颜色时要小心。在Android 5.0或更高版本的普通通知中,颜色仅应用于小应用程序图标的背景。但对于Android 7.0之前的MediaStyle通知,颜色用于整个通知背景。测试你的背景颜色。温柔的眼睛,避免极其明亮或荧光的颜色。用于 setMediaSession() 将通知与您的会话相关联。这允许第三方应用和配套设备访问和控制会话。用于 setMediaSession() 将通知与您的会话相关联。这允许第三方应用和配套设备访问和控制会话。用于 setShowActionsInCompactView() 在通知的标准大小的contentView中添加最多3个操作。(此处指定了暂停按钮。)在Android 5.0(API级别21)及更高版本中,一旦服务不再在前台运行,您可以滑动通知以停止播放器。您不能在早期版本中执行此操作。要允许用户在Android 5.0(API级别21)之前删除通知并停止播放,您可以通过调用 setShowCancelButton(true) 和在通知的右上角添加取消按钮 setCancelButtonIntent() 。添加暂停和取消按钮时,您需要PendingIntent附加到播放操作。该方法 MediaButtonReceiver.buildMediaButtonPendingIntent() 执行将PlaybackState操作转换为PendingIntent的工作。总结写的一般,欢迎留言、私信指出问题与不足之处!如回复不及时可加入Android技术交流群:150923287 一起学习探讨Android开发技术! ...

January 9, 2019 · 3 min · jiezi

Python进阶:自定义对象实现切片功能

Python进阶:自定义对象实现切片功能切片是 Python 中最迷人最强大最 Amazing 的语言特性(几乎没有之一),在《Python进阶:切片的误区与高级用法》中,我介绍了切片的基础用法、高级用法以及一些使用误区。这些内容都是基于原生的序列类型(如字符串、列表、元组……),那么,我们是否可以定义自己的序列类型并让它支持切片语法呢?更进一步,我们是否可以自定义其它对象(如字典)并让它支持切片呢?1、魔术方法:getitem()想要使自定义对象支持切片语法并不难,只需要在定义类的时候给它实现魔术方法 getitem() 即可。所以,这里就先介绍一下这个方法。语法: object.getitem(self, key)官方文档释义:Called to implement evaluation of self[key]. For sequence types, the accepted keys should be integers and slice objects. Note that the special interpretation of negative indexes (if the class wishes to emulate a sequence type) is up to the getitem() method. If key is of an inappropriate type, TypeError may be raised; if of a value outside the set of indexes for the sequence (after any special interpretation of negative values), IndexError should be raised. For mapping types, if key is missing (not in the container), KeyError should be raised.概括翻译一下:getitem() 方法用于返回参数 key 所对应的值,这个 key 可以是整型数值和切片对象,并且支持负数索引;如果 key 不是以上两种类型,就会抛 TypeError;如果索引越界,会抛 IndexError ;如果定义的是映射类型,当 key 参数不是其对象的键值时,则会抛 KeyError 。2、自定义序列实现切片功能接下来,我们定义一个简单的 MyList ,并给它加上切片功能。(PS:仅作演示,不保证其它功能的完备性)。class MyList(): def init(self): self.data = [] def append(self, item): self.data.append(item) def getitem(self, key): print(“key is : " + str(key)) return self.data[key]l = MyList()l.append(“My”)l.append(“name”)l.append(“is”)l.append(“Python猫”)print(l[3])print(l[:2])print(l[‘hi’])### 输出结果:key is : 3Python猫key is : slice(None, 2, None)[‘My’, ’name’]key is : hiTraceback (most recent call last):…TypeError: list indices must be integers or slices, not str从输出结果来看,自定义的 MyList 既支持按索引查找,也支持切片操作,这正是我们的目的。特别需要说明的是,此例中的 getitem() 方法会根据不同的参数类型而实现不同的功能(取索引位值或切片值),也会妥当地处理异常,所以并不需要我们再去写繁琐的处理逻辑。网上有不少学习资料完全是在误人子弟,它们会教你区分参数的不同类型,然后写一大段代码来实现索引查找和切片语法,简直是画蛇添足。下面的就是一个代表性的错误示例:###略去其它代码####def getitem(self, index): cls = type(self) if isinstance(index, slice): # 如果index是个切片类型,则构造新实例 return cls(self.components[index]) elif isinstance(index, numbers.Integral): # 如果index是个数,则直接返回 return self.components[index] else: msg = “{cls.name} indices must be integers” raise TypeError(msg.format(cls=cls))3、自定义字典实现切片功能切片是序列类型的特性,所以在上例中,我们不需要写切片的具体实现逻辑。但是,对于其它非序列类型的自定义对象,就得自己实现切片逻辑。以自定义字典为例(PS:仅作演示,不保证其它功能的完备性):class MyDict(): def init(self): self.data = {} def len(self): return len(self.data) def append(self, item): self.data[len(self)] = item def getitem(self, key): if isinstance(key, int): return self.data[key] if isinstance(key, slice): slicedkeys = list(self.data.keys())[key] return {k: self.data[k] for k in slicedkeys} else: raise TypeErrord = MyDict()d.append(“My”)d.append(“name”)d.append(“is”)d.append(“Python猫”)print(d[2])print(d[:2])print(d[-4:-2])print(d[‘hi’])### 输出结果:is{0: ‘My’, 1: ’name’}{0: ‘My’, 1: ’name’}Traceback (most recent call last):…TypeError上例的关键点在于将字典的键值取出,并对键值的列表做切片处理,其妙处在于,不用担心索引越界和负数索引,将字典切片转换成了字典键值的切片,最终实现目的。4、小结最后小结一下:本文介绍了__getitem() 魔术方法,并用于实现自定义对象(以列表类型和字典类型为例)的切片功能,希望对你有所帮助。参考阅读: Python进阶:切片的误区与高级用法官方文档getitem用法:http://t.cn/EbzoZypPython切片赋值源码分析:http://t.cn/EbzSaoZPS:本公众号(Python猫)已开通读者交流群,详情请通过菜单栏中的“交流群”来了解。—————–本文原创并首发于微信公众号【Python猫】,后台回复“爱学习”,免费获得20+本精选电子书。 ...

December 27, 2018 · 2 min · jiezi

Android 数据库框架 DBFlow 的使用

DBFlow 是一个基于注解处理器开发的使用方便的 ORM Android 数据库,该库简化了很多多余的代码,并且提供了好用的 API 来处理与数据库的交互,让开发者专注 App 的开发。下面将从以下几个方面来学习 DBFlow 数据库框架的使用,具体如下:DBFlow 的优势配置 DBFlow创建数据库创建表插入数据删除数据更新数据查询数据案例DBFlow 的优势DBFlow 借鉴了一些其他优秀数据库框架的特性,下面是 DBFlow 的优势,具体如下:Extensibility(扩展性):对表类的继承类没有限制,可以是一个普通的 JavaBean,使用时为了方便推荐表类继承 BaseModel 类你可以扩展不同包中的非模型类,并将它们用作数据库表,此外,你可以将其他表的子类加入到 @Column 中,并且它们可以位于不同的包中。Speed(速度):该库基于 Java 的注解处理器生成,使用它对运行时性能几乎没有任何影响(反射仅仅用于生数据库模块的生成),可以节省生成样板代码的时间,支持模型缓存(多主键模型),在可能的情况下比原生的 SQLite 速度要快,支持懒加载、@ForeignKey、@OneToMany等使得查询更有效率。SQLite Query Flow(SQLite查询流):DBFlow 的查询尽可能的贴近原生 SQLite 查询,如:select(name, screenSize).from(Android.class).where(name.is(“Nexus 5x”)).and(version.is(6.0)).querySingle()Open Source(开源):DBFlow 时开源的,开源地址:GithubRobust(健壮性):支持 Trigger, ModelView, Index, Migration 以及内置的管理数据库的方式, 此外,还支持 SQLCipher, RXJava 等Multiple Databases, Multiple Modules(多数据库、多模型):无缝支持多数据库文件以及使用 DBFlow 的其他依赖中的数据库模型Built On SQLite(基于 SQLite):SQLite 是世界上使用最广泛的数据库引擎,它不仅限于某个平台。配置 DBFlow因为 DBFlow 任然不是官方发布的,你需要在项目的 build.gradle 文件中进行如下配置,具体如下:allprojects { repositories { jcenter() maven { url “https://jitpack.io” } }}然后,在 Module 对应的 build.gradle 文件中添加依赖,具体如下://为了方便可使用 def 关键字定义版本号def dbFlow_version = “4.2.4"dependencies { //… annotationProcessor “com.github.Raizlabs.DBFlow:dbflow-processor:${dbFlow_version}” compile “com.github.Raizlabs.DBFlow:dbflow-core:${dbFlow_version}” compile “com.github.Raizlabs.DBFlow:dbflow:${dbFlow_version}"}上面代码中的依赖只是针对于 Java,如果你要使用 Kotlin、RxJava 等要配置相对应的依赖即可。注意:升级新版本的 DBFlow 时,一定要删除旧版本的依赖,因为新旧版本的注解处理器可能不同,如果未移除旧版本,将会报如下错误,具体如下:java.lang.NoSuchMethodError: com.raizlabs.android.dbflow.annotation.Table.tableName()Ljava/lang/String然后,自定义 Application ,在相应的 onCreate() 方法中初始化 DBFlow,具体如下:/** * 自定义Application * @author jzman * create at 2018/4/16 0016 17:28 /public class MyApplication extends Application{ @Override public void onCreate() { super.onCreate(); //初始化DBFlow FlowManager.init(new FlowConfig.Builder(this).build()); //设置日志显示 FlowLog.setMinimumLoggingLevel(FlowLog.Level.V); }}最后,在 AndroidManifest.xml 文件中使用自定义的 Application,具体如下:<application android:name=".app.MyApplication” // …</application>此时,DBFlow 就引入当前项目中咯。创建数据库创建一个类并使用 @Database 注解来定义自己的数据库,该类应该要定义数据库的名称和数据库的版本,具体如下:/* * MyDatabase * @author jzman * create at 2018/4/17 0017 9:08 /@Database(name = MyDatabase.NAME, version = MyDatabase.VERSION)public class MyDatabase { //数据库名称 public static final String NAME = “MyDatabase”; //数据库版本号 public static final int VERSION = 1;}注意:如果以后要修改任意表的结构,为避免与旧版本数据库冲突一定要修改版本号,且保证版本号只升不降。创建表在已经创建好数据库的前提下就可以创建表了,表的模型类一般需要继承 BaseModel,并为模型类中的每个字段添加 @Column 注解,该注解将映射模型类的字段到对应表中的列,定义一张表具体如下:/* * NoteTable.java * @author jzman * create at 2018/4/17 0017 9:54 /@Table(database = MyDatabase.class)public class NoteTable extends BaseModel { @Column @PrimaryKey int id; @Column private String title; @Column private String date; @Column private String content; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getContent() { return content; } public void setContent(String content) { this.content = content; }}注意:在一张表中至少必须定义一个字段作为主键(primary key),如果模型类中某个字段是私有的,一定要定义相应的 getter、setter 方法,否则会在创建表的环节失败,表的命名要使用驼峰命名法,否则可能会出现如此下问题:java.lang.IllegalArgumentException: expected type but was null插入数据使用 DBFlow 插入数据常用的有二种方式,具体如下:model.insert()SQLite.insert()前者用于单个模型类对象的的插入,创建完具体的对象后,调用 model.insert() 即可插入该对象所对应的记录;后者使用 SQLite Wrapper Language 来插入数据,类似于原生的 insert 语句,支持多列数据的插入,使用起来比较方便,具体参考如下:/* * 插入数据 * @param model /public void inseartData(NoteBean model){ //1.model,insert() model.setTitle(“title”); model.setDate(“2018-04-17”); model.setContent(“content”); model.insert(); //2.SQLite.insert() SQLite.insert(NoteBean.class) .columns(NoteBean_Table.title,NoteBean_Table.date,NoteBean_Table.content) .values(“title”,“2018-04-17”,“content”) .execute();}删除数据使用 DBFlow 删除数据常用的有二种方式,具体如下:model.delete():删除某条记录SQLite.delete():根据条件删除前者用于单个模型类对象的的删除,创建完具体的对象后,调用 model.delete() 即可删除该对象所对应的记录;后者使用 SQLite Wrapper Language 来条件删除数据,类似于原生的 delete 语句,使用起来比较方便,具体参考如下:/* * 删除数据 * @param model /public void deleteData(NoteBean model){ //1.model.delete() model.delete(); //2.SQLite.delete() SQLite.delete(NoteBean.class) .where(NoteBean_Table.title.is(“title”)) .and(NoteBean_Table.id.is(10)) .async() .execute(); //删除整张表 Delete.table(NoteBean.class); //删除多张表 Delete.table(NoteBean.class,NoteBean1.class);}更新数据使用 DBFlow 删除数据常用的有二种方式,具体如下:model.update():更新某条记录SQLite.update():根据条件更新某条记录前者用于单个模型类对象的的更新,创建完具体的对象后,调用 model.update() 即可更新该对象所对应的记录;后者使用 SQLite Wrapper Language 来条件删除数据,类似于原生的 update 语句,使用起来比较方便,具体参考如下:/* * 更新数据 * @param model /public void updateData(NoteBean model) { //1.model.update() model.update(); //2.SQLite.update() SQLite.update(NoteBean.class) .set(NoteBean_Table.title.eq(“title”), NoteBean_Table.content.eq(“content”)) .where(NoteBean_Table.id.is(10)) .async() .execute();}查询数据查询使用 SQLite.select() 方法,查询还有许多可以作为条件的关键字,这里就不在赘述了,下面是一个开发者做常用的查询,具体参考如下:/* * 查询数据 */public List<NoteBean> queryData(){ //根据条件查询 List<NoteBean> noteBeans = SQLite.select() .from(NoteBean.class) .where(NoteBean_Table.title.is(“title”)) .queryList(); return noteBeans;}注意:对于插入、更新操作可以使用 model.save() 方法。案例上文中介绍了 DBFlow 的配置以及增删改查等基本操作,DbFlow 还有其他比较高级的用法,比如使用 Transactions 来进行数据的安全操作等,下面写一个简单的案例来结束对 DBFlow 的学习,具体效果如下:更多关于 DBFlow 的知识请参考 DBFlow 的 GitBook 。可以关注微信公众号:jzman-blog,一起交流学习。 ...

December 19, 2018 · 2 min · jiezi

如何做好SQLite 使用质量检测,让事故消灭在摇篮里

本文由云+社区发表SQLite 在移动端开发中广泛使用,其使用质量直接影响到产品的体验。常见的 SQLite 质量监控一般都是依赖上线后反馈的机制,比如耗时监控或者用户反馈。这种方式问题是:事后发现,负面影响已经发生。关注的只是没这么差。eg. 监控阈值为 500ms ,那么一条可优化为 20ms 而平均耗时只有 490ms 的 sql 就被忽略了。能否在上线前就进行SQLite使用质量的监控?于是我们尝试开发了一个工具: SQLiteLint 。虽然名带 “lint ” ,但并不是代码的静态检查,而是在 APP 运行时对 sql 语句、执行序列、表信息等进行分析检测。而和 “lint” 有点类似的是:在开发阶段就介入,并运用一些最佳实践的规则来检测,从而发现潜在的、可疑的 SQLite 使用问题。本文会介绍 SQLiteLint 的思路,也算是 SQLite 使用经验的分享,希望对大家有所帮助。简述SQLiteLint 在 APP 运行时进行检测,而且大部分检测算法与数据量无关即不依赖线上的数据状态。只要你触发了某条 sql 语句的执行,SQLiteLint 就会帮助你 review 这条语句是否写得有问题。而这在开发、测试或者灰度阶段就可以进行。检测流程十分简单:1. 收集 APP 运行时的 sql 执行信息 包括执行语句、创建的表信息等。其中表相关信息可以通过 pragma 命令得到。对于执行语句,有两种情况: a)DB 框架提供了回调接口。比如微信使用的是 WCDB ,很容易就可以通过MMDataBase.setSQLiteTrace 注册回调拿到这些信息。 b) 若使用 Android 默认的 DB 框架,SQLiteLint 提供了一种无侵入的获取到执行的sql语句及耗时等信息的方式。通过hook的技巧,向 SQLite3 C 层的 api sqlite3_profile 方法注册回调,也能拿到分析所需的信息,从而无需开发者额外的打点统计代码。2. 预处理 包括生成对应的 sql 语法树,生成不带实参的 sql ,判断是否 select* 语句等,为后面的分析做准备。预处理和后面的算法调度都在一个单独的处理线程。3. 调度具体检测算法执行 checker 就是各种检测算法,也支持扩展。并且检测算法都是以 C++ 实现,方便支持多平台。而调度的时机包括:最近未分析 sql 语句调度,抽样调度,初始化调度,每条 sql 语句调度。4. 发布问题 上报问题或者弹框提示。可以看到重点在第 3 步,下面具体讨论下 SQLiteLint 目前所关注的质量问题检测。检测问题简介一、检测索引使用问题索引的使用问题是数据库最常见的问题,也是最直接影响性能的问题。SQLiteLint 的分析主要基于 SQLite3 的 “explain query plan” ,即 sql 的查询计划。先简单说下查询计划的最常见的几个关键字:SCAN TABLE: 全表扫描,遍历数据表查找结果集,复杂度 O(n) SEARCH TABLE: 利用索引查找,一般除了 without rowid 表或覆盖索引等,会对索引树先一次 Binary Search 找到 rowid ,然后根据得到 rowid 去数据表做一次 Binary Search 得到目标结果集,复杂度为 O(logn) USE TEMP B-TREE: 对结果集临时建树排序,额外需要空间和时间。比如有 Order By 关键字,就有可能出现这样查询计划通过分析查询计划,SQLiteLint 目前主要检查以下几个索引问题:1. 未建索引导致的全表扫描(对应查询计划的 SCAN TABLE… )虽然建立索引是最基本优化技巧,但实际开发中,很多同学因为意识不够或者需求太紧急,而疏漏了建立合适的索引,SQLiteLint 帮助提醒这种疏漏。问题虽小,解决也简单,但最普遍存在。 这里也顺带讨论下一般不适合建立索引的情况:写多读少以及表行数很小。但对于客户端而言,写多读少的表应该不常见。而表行数很小的情况,建索引是有可能导致查询更慢的(因为索引的载入需要的时间可能大过全表扫描了),但是这个差别是微乎其微的。所以这里认为一般情况下,客户端的查询还是尽量使用索引优化,如果确定预估表数量很小或者写多读少,也可以将这个表加到不检测的白名单。解决这类问题,当然是建立对应的索引。2. 索引未生效导致的全表扫描(对应查询计划的 SCAN TABLE… )有些情况即便建立了索引,但依然可能不生效,而这种情况有时候是可以通过优化 sql 语句去用上索引的。举个例子:以上看到,即便已建立了索引,但实际没有使用索引来查询。 如对于这个 case ,可以把 like 变成不等式的比较:这里看到已经是使用索引来 SEARCH TABLE ,避免了全表扫描。但值得注意的是并不是所有 like 的情况都可以这样优化,如 like ‘%lo’ 或 like ‘%lo%’ ,不等式就做不到了。再看个位操作导致索引不生效的例子:位操作是最常见的导致索引不生效的语句之一。但有些时候也是有些技巧的利用上索引的,假如这个 case 里 flag 的业务取值只有 0x1,0x2,0x4,0x8 ,那么这条语句就可以通过穷举值的方式等效:以上看到,把位操作转成 in 穷举就能利用索引了。解决这类索引未生效导致的全表扫描 的问题,需要结合实际业务好好优化sql语句,甚至使用一些比较trick的技巧。也有可能没办法优化,这时需要添加到白名单。3. 不必要的临时建树排序(对应查询计划的 USE TEMP B-TREE… )。比如sql语句中 order by 、distinct 、group by 等就有可能引起对结果集临时额外建树排序,当然很多情况都是可以通过建立恰当的索引去优化的。举个例子:以上看到,即便id和mark都分别建立了索引,即便只需要一行结果,依然会引起重新建树排序( USE TEMP B-TREE FOR ORDER BY )。当然这个case非常简单,不过如果对 SQLite 的索引不熟悉或者开发时松懈了,确实很容易发生这样的问题。同样这个问题也很容易优化:这样就避免了重新建树排序,这对于数据量大的表查询,优化效果是立竿见影的好。解决这类问题,一般就是建立合适的索引。4. 不足够的索引组合这个主要指已经建立了索引,但索引组合的列并没有覆盖足够 where 子句的条件式中的列。SQLiteLint 检测出这种问题,建议先关注该 sql 语句是否有性能问题,再决定是否建立一个更长的索引。举个例子:以上看到,确实是利用了索引 genderIndex 来查询,但看到where子句里还有一个 mark=60 的条件,所以还有一次遍历判断操作才能得到最终需要的结果集。尤其对于这个 case,gender 也就是性别,那么最多 3 种情况,这个时候单独的 gender 索引的优化效果的已经不明显了。而同样,优化也是很容易的:解决这类问题,一般就是建立一个更大的组合索引。5. 怎么降低误报现在看到 SQLiteLint 主要根据查询计划的某些关键字去发现这些问题,但SQLite支持的查询语法是非常复杂的,而对应的查询计划也是无穷变化的。所以对查询计划自动且正确的分析,不是一件容易的事。SQLiteLint 很大的功夫也在这件事情上所以对查询计划自动且正确的分析,不是一件容易的事。SQLiteLint 很大的功夫也在这件事情上。SQLiteLint 这里主要对输出的查询计划重新构建了一棵有一定的特点的分析树,并结合sql语句的语法树,依据一定的算法及规则进行分析检测。建分析树的过程会使用到每条查询计划前面如 “0|1|0” 的数字,这里不具体展开了。 举个例子:是不是所有带有 “SCAN TABLE” 前缀的查询计划,都认为是需要优化的呢?明显不是。具体看个 case :这是一个联表查询,在 SQLite 的实现里一般就是嵌套循环。在这个语句中里, t3.id 列建了索引,并且在第二层循环中用上了,但第一层循环的 SCAN TABLE是无法优化的。比如尝试给t4的id列也建立索引:可以看出,依然无法避免 SCAN TABLE 。对于这种 SCAN TABLE 无法优化的情况,SQLiteLint 不应该误报。前面提到,会对查询计划组织成树的结构。比如对于这个 case ,最后构建的查询计划分析树为:分析树,有个主要的特点:叶子节点有兄弟节点的是联表查询,其循环顺序对应从左往右,而无兄弟节点是单表查询。而最后的分析会落地到叶子节点的分析。遍历叶子节点时,有一条规则(不完整描述)是:叶子节点有兄弟节点的,且是最左节点即第一层循环,且 where 子句中不含有相关常量条件表达式时,SCAN TABLE 不认为是质量问题。这里有两个条件必须同时满足,SCAN TABLE 才不报问题:第一层循环 & 无相关常量表达式。第一层循环前面已经描述,这里再解释下后面一个条件。由上看到,当select子句中出现常量条件表达式 “t4.id=666” , 若 t3.id,t4.id 都建了索引,是可以优化成没有 SCAN TABLE 。而把 t4.id 的索引删除后,又出现了 SCAN TABLE 。而这种 SCAN TABLE 的情况,不满足规则里的的第二个条件,SQLiteLint 就会报出可以使用索引优化了。这里介绍了一个较简单语句的查询计划的分析,当然还有更复杂的语句,还有子查询、组合等等,这里不展开讨论了。巨大的复杂性,无疑对准确率有很大的挑战,需要对分析规则不断地迭代完善。当前 SQLiteLint 的分析算法依然不足够严谨,还有很大的优化空间。 这里还有另一个思路去应对准确性的问题:对所有上报的问题,结合耗时、是否主线程、问题等级等信息,进行优先级排序。这个“曲线救国”来降低误报的策略也适用本文介绍的所有检测问题。二、检测冗余索引问题SQLiteLint 会在应用启动后对所有的表检测一次是否存在冗余索引,并建议保留最大那个索引组合。先定义什么是冗余索引:如对于某个表,如果索引组合 index1,index2 是另一个索引组合 index3 的前缀,那么一般情况下 index3 可以替代掉 index1 和 index2 的作用,所以 index1,index2 就冗余了。而多余的索引就会有多余的插入消耗和空间消耗,一般就建议只保留索引 index3 。 看个例子:以上看到,如果已经有一个 length 和 type 的组合索引,就已经满足了单 length 列条件式的查询,没必要再为 length 再建一个索引。三、检测 select * 问题SQLiteLint这里通过扫描 sql 语法树,若发现 select 子句,就会报问题,建议尽量避免使用 select ,而是按需 select 对应的列。select * 是SQLite最常用的语句之一,也非常方便,为什么还认为是问题的呢?这里有必要辩驳一下:对于 select ,SQLite 底层依然存在一步把 展开成表的全部列。select * 也减少了可以使用覆盖索引的机会。覆盖索引指索引包含的列已经覆盖了 select 所需要的列,而使用上覆盖索引就可以减少一次数据表的查询。对于 Android 平台而言,select * 就会投射所有的列,那么每行结果占据的内存就会相对更大,那么 CursorWindow(缓冲区)的容纳条数就变少,那么 SQLiteQuery.fillWindow 的次数就可能变多,这也有一定的性能影响。基于以上原因,出于 SQLiteLint 目标最佳实践的原则,这里依然报问题。四、检测 Autoincrement 问题SQLiteLint 在应用启动后会检测一次所有表的创建语句,发现 AUTOINCREMENT 关键字,就会报问题,建议避免使用 Autoincrement 。这里看下为什么要检测这个问题,下面引用 SQLite 的官方文档:The AUTOINCREMENT keyword imposes extra CPU, memory, disk space, and disk I/O overhead and should be avoided if not strictly needed. It is usually not needed.可以看出 Auto Increment 确实不是个好东西。 ps. 我这里补充说明一下 strictly needed 是什么是意思,也就是为什么它不必要。通常 AUTOINCREMENT 用于修饰 INTEGER PRIMARY KEY 列,后简称IPK 列。而 IPK 列等同于 rowid 别名,本身也具有自增属性,但会复用删除的 rowid 号。比如当前有 4 行,最大的rowid是 4,这时把第 4 行删掉,再插入一行,新插入行的 rowid 取值是比当前最大的 rowid 加 1,也就 3+1=4 ,所以复用了 rowid 号 4 。而如果加以 AUTOINCREMENT 修饰就是阻止了复用,在这个情况,rowid 号是 5 。也就是说,AUTOINCREMENT 可以保证了历史自增的唯一性,但对于客户端应用有多少这样的场景呢?五、检测建议使用 prepared statementSQLiteLint 会以抽样的时机去检测这个问题,比如每 50 条执行语句,分析一次执行序列,如果发现连续执行次数超过一定阈值的相同的(当然实参可以不同)而未使用 prepared statement 的 sql 语句,就报问题,建议使用 prepared statement 优化。 如阈值是 3 ,那么连续执行下面的语句,就会报问题:使用 prepared statement 优化的好处有两个:对于相同(实参不同)的 sql 语句多次执行,会有性能提升如果参数是不可信或不可控输入,还防止了注入问题六、检测建议使用 without rowid 特性SQLiteLint 会在应用启动后检测一次所有表的创建语句,发现未使用 without rowid 技巧且根据表信息判断适合使用 without rowid 优化的表,就报问题,建议使用 without rowid 优化。 这是 SQLiteLint 的另一个思路,就是发现是否可以应用上一些 SQLite 的高级特性。without rowid 在某些情况下可以同时带来空间以及时间上将近一半的优化。简单说下原理,如:对于这个含有 rowid 的表( rowid 是自动生成的),这时这里涉及到两次查询,一次在 name 的索引树上找到对应的 rowid ,一次是用这个 rowid 在数据树上查询到 mark 列。 而使用 without rowid 来建表:数据树构建是以 name 为 key ,mark 为 data 的,并且是以普通 B-tree 的方式存储。这样对于刚刚同样的查询,就需要只有一次数据树的查询就得到了 mark 列,所以算法复杂度上已经省了一个 O(logn)。另外又少维护了一个 name 的索引树,插入消耗和空间上也有了节省。当然 withou rowid 不是处处适用的,不然肯定是默认属性了。SQLiteLint 判断如果同时满足以下两个条件,就建议使用 without rowid :表含有 non-integer or composite (multi-column) PRIMARY KEY表每行数据大小不大,一个比较好的标准是行数据大小小于二十分之一的page size 。ps.默认 page size SQLite 版本3.12.0以后(对应 Android O 以上)是 4096 bytes ,以前是 1024 。而由于行数据大小业务相关,为了降低误报,SQLiteLint 使用更严格的判定标准:表不含有 BLOB 列且不含有非 PRIMARY KEY TEXT 列。简单说下原因: 对于1,假如没有 PRIMARY KEY ,无法使用 without rowid 特性;假如有 INTEGER PRIMARY KEY ,前面也说过,这时也已经等同于 rowid 。 对于 2,小于 20 分之一 pagesize 是官方给出的建议。 这里说下我理解的原因。page 是 SQLite 一般的读写单位(实际上磁盘的读写 block 更关键,而磁盘的消耗更多在定位上,更多的page就有可能需要更多的定位)。without rowid 的表是以普通 B-Tree 存储的,而这时数据也存储在所有树结点上,那么假如数据比较大,一个 page 存储的结点变少,那么查找的过程就需要读更多的 page ,从而查找的消耗更大。当然这是相对 rowid 表 B*-Tree 的存储来说的,因为这时数据都在叶子结点,搜索路径上的结点只有 KEY ,那么一个page能存的结点就多了很多,查找磁盘消耗变小。这里注意的是,不要以纯内存的算法复杂度去考量这个问题。以上是推论不一定正确,欢迎指教。引申一下,这也就是为什么 SQLite 的索引树以 B-Tree 组织,而 rowid 表树以 B-Tree 组织,因为索引树每个结点的存主要是索引列和 rowid ,往往没这么大,相对 B-Tree 优势就在于不用一直查找到叶子结点就能结束查找。与 without rowid 同样的限制,不建议用大 String 作为索引列,这当然也可以加入到 SQLiteLint 的检测。小结这里介绍了一个在开发、测试或者灰度阶段进行 SQLite 使用质量检测的工具,这个思路的好处是:上线前发现问题关注最佳实践本文的较大篇幅其实是对 SQLite 最佳实践的讨论,因为 SQLiteLint 的思路就是对最佳实践的自动化检测。当然检查可以覆盖更广的范围,准确性也是挑战,这里还有很大的空间。 此文已由作者授权腾讯云+社区发布搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包! ...

December 18, 2018 · 3 min · jiezi