Android数据库框架LitePal详解
LitPal
1.简介
LitePal是开源的Android数据库框架,采用了对象关系映射(ORM)的模式,并将我们平时开发最常用到的一些数据库功能进行了封装,使得不用编写一行SQL语句就可以完成建表和增删改查。
2.配置LitePal
2.1 把litepal引入到项目中
dependencies{
compile 'org.litepal.android:core:1.6.1'
}
2.2配置litepal.xml文件
右击app/src/main目录--new--Directory,创建一个assets目录,然后在assets目录下再创建一个litpal.xml文件,内容如下
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<dbname value="CourseManager" />
<version value="1" />
<list>
。。。
</list>
</litepal>
其中,<dbname>标签用于指定数据库名,<version>用于指定数据库版本号,<list>用于指定所有的映射模型。
2.3修改AndroidManifest.xml文件
<application
android:mane="org.;itepal.LitePalApplication"
...
</application>
是litePal的所有功能都可以正常工作。扩展看十三章
3.创建数据库
将面向对象的语言和面向关系的数据库之间建立一种映射关系。
3.1创建一个实体类例如 AbnormalCrew.class
public class AbnormalCrew {
private String picture;
private String crewName;
private String crewCode;
private String gender;
private String reason;
private String crewId;
private String country;
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getCrewId() {
return crewId;
}
public void setCrewId(String crewId) {
this.crewId = crewId;
}
public String getPicture() {
return picture;
}
public void setPicture(String picture) {
this.picture = picture;
}
public String getCrewName() {
return crewName;
}
public void setCrewName(String crewName) {
this.crewName = crewName;
}
public String getCrewCode() {
return crewCode;
}
public void setCrewCode(String crewCode) {
this.crewCode = crewCode;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
3.2 将 AbnormalCrew类添加到映射模型立标中
修改litepal.xm文件:放在<list>标签下
<dbname value="Crew"></dbname>
<list>
<mapping class = "com.itiis.whut.certificateaudit.beans.AbnormalCrew"/>
......
</list>
3.3 在XXXActivity中添加代码:
获取数据库 ,必须使用否则报空错误 ,放在application或第一个启动的activity中都可以。
SQLiteDatabase db = LitePal.getDatabase();
调用getDatabase就可以创建数据库和对一个的表。
4.升级数据库
表加列,新建表。。。。。
4.1 比如在AbnormalCrew表中添加一个age列:
public class AbnormalCrew {
....
private String sex;
...getAge(){
...
}
...setAge(){
...
}
}
新建一个表Crew:
新建过程省略,上面有。。。
4.2 修改litpal.xml文件
?xml version="1.0" encoding="utf-8"?>
<litepal>
<dbname value="CourseManager" />
<version value="2" />
<list>
<mapping class = "com.itiis.whut.certificateaudit.beans.AbnormalCrew"/>
<mapping class = "com.itiis.whut.certificateaudit.beans.Crew"/>
</list>
</litepal>
记得版本号加1.
5.是用litepal添加数据
例如向AbnormalCrew添加数据。
5.1让 AbnormalCrew继承DataSupport
因为要进行CRUD操作,必须继承DataSupport类。
public class AbnormalCrew extends DataSupport{
...
}
5.2添加数据
创建出模型类的实例,用set方法设置数据,最后调用save()方法。
在xxxActivity中修改代码:
AbnormalCrew abnormalCrew = new AbnormalCrew();
abnormalCrew.setCrewName("jmq");
........
abnormalCrew.save();
save()方法是DataSupport类里面的。
6.更新数据
6.1方法一:
对已存储对象重新设值,然后重新调用save()方法即可。
什么是已存储对象(经过持久化)?怎么判断?
两种情况:
第一种:已经调用过model.save()方法去添加数据的,此时model会被认为是已存储的对象。
第二种:model对象是通过LitePal提供的查询API查出来的,由于是从数据库中查找对象。因此也会被认为是已存储的对象。
判断:
通过model.isSaved()方法,返回true则已存储,false表示未存储。
AbnormalCrew abnormalCrew = new AbnormalCrew();
abnormalCrew.setCrewName("jmq");
........
abnormalCrew.save();
abnormalCrew.setCrewName("小明");
abnormalCrew.save();
这样可以对CrewName的值进行修改。
6.2方法二:
是用updata()方法
public static int update( long id)
这个静态的update()方法接收参数是一个指定的id,表示我们要修改哪一行数据。
AbnormalCrew abnormalCrew = new AbnormalCrew();
abnormalCrew.setCrewName("小明");
abnormalCrew.update(2);
在指定修改某一条id记录的时候只需要传入这个id即可,语法更简练。
6.3方法三
也许我想修改的是某一个条件下的所有数据,而不是仅仅修改某个id的数据,那该怎么办呢?LitePal还提供了另外一个简便的方法,
AbnormalCrew abnormalCrew = new AbnormalCrew();
abnormalCrew.setCrewName("小明");
abnormalCrew.updateAll("gender = ? and country > ?", "男", "中国");
7.使用LitePal删除数据
7.1是用delete()方法
就是直接调用已存储对象的delete()就可以了。
7.2调用deleteAll()方法
LitePal删除数据的API和修改数据是比较类似的,但是更加的简单一些,我们先来看一下DataSupport类中的方法定义,如下所示:
public static int delete(Class<?> modelClass, long id)
delete()方法接收两个参数,第一个参数是Class,传入我们要删除的那个类的Class就好,第二个参数是一个指定的id,表示我们要删除哪一行数据。
那么比如说我们想删除news表中id为2的记录,就可以这样写:
DataSupport.delete(News.class, 2);
需要注意的是,这不仅仅会将news表中id为2的记录删除,同时还会将其它表中以news id为2的这条记录作为外键的数据一起删除掉,因为外键既然不存在了,那么这么数据也就没有保留的意义了。
说起来可能有点拗口,我们还是举例看一下。比如news表中目前有两条数据,如下图所示:
然后comment表中也有两条数据,如下图所示:
其中comment表中两条数据的外键都是2,指向的news表中id为2的这条记录。那么下面我们执行如下删除语句:
int deleteCount = DataSupport.delete(News.class, 2);
Log.d("TAG", "delete count is " + deleteCount);
其中delete()方法的返回值表示被删除的记录数,打印结果如下所示:
可以看到,有三条记录被删除了,那我们再到news表中查询一下:
OK,只剩下一条记录了,id为2的那条记录确实被删除了。那么再到comment表中看一下呢,如下图所示:
数据全没了!为什么呢?因为comment表中的两条数据都是以news表中id为2的数据作为外键的,现在外键不存在了,那么这两条数据自然也没有存在的意义了,因此被删除的记录数一共是3条。这样是不是就好理解了很多呢?
除了删除指定id的数据之外,DataSupport中也提供了一个通过where语句来批量删除数据的方法,先看一下方法定义:
public static int deleteAll(Class<?> modelClass, String... conditions)
看起来很眼熟吧?非常简单,deleteAll()方法接收两个参数,第一个参数是Class,传入我们要删除的那个类的Class就好,第二个参数是一个conditions数组,用于指定删除哪些行的约束条件,返回值表示此次删除了多少行数据,用法和updateAll()方法是基本相同的。
那么比如说我们想把news表中标题为“今日iPhone6发布”且评论数等于0的所有新闻都删除掉,就可以这样写:
DataSupport.deleteAll(News.class, "title = ? and commentcount = ?", "今日iPhone6发布", "0");
而如果我们想把news表中所有的数据全部删除掉,就可以这样写:
DataSupport.deleteAll(News.class);
在不指定约束条件的情况下,deleteAll()方法就会删除表中所有的数据了。
除了DataSupport类中提供的静态删除方法之外,还有一个删除方法是作用于对象上的,即任何一个继承自DataSupport类的实例都可以通过调用delete()这个实例方法来删除数据。但前提是这个对象一定是要持久化之后的,一个非持久化的对象如果调用了delete()方法则不会产生任何效果。
比如说下面这种写法:
News news = new News();
news.delete();
这里new出了一个News对象,这个对象明显是没有持久化的,那么此时调用delete()方法则不会删除任何数据。
但如果我们之前将这个对象持久化过了,那么再调用delete()方法就会把这个对象对应的数据删除掉了,比如:
News news = new News();
news.setTitle("这是一条新闻标题");
news.setContent("这是一条新闻内容");
news.save();
...
news.delete();
一个对象如果save过了之后,那就是持久化的了。除了调用save()方法之外,通过DataSupport中提供的查询方法从数据库中查出来的对象也是经过持久化的,查询的功能我们会在下篇博客中讲解。
另外还有一个简单的办法可以帮助我们判断一个对象是否是持久化之后的,DataSupport类中提供了一个isSaved()方法,这个方法返回true就表示该对象是经过持久化的,返回false则表示该对象未经过持久化。那么删除一个对象对应的数据也就可以这样写了:
News news;
...
if (news.isSaved()) {
news.delete();
}
好了,这样我们就把LitePal中提供的修改和删除数据操作的用法基本都学习完了,那么今天的文章就到这里,
使用LitePal查询数据
LitePal在查询方面提供了非常丰富的API,功能多种多样,基本上已经能够满足我们平时所有的查询需求了。不仅如此,LitePal在查询API的设计方面也是非常用心,摒弃了原生query()方法中繁琐的参数列表,而是改用了一种更为灵巧的方式——连缀查询。除此之外,LitePal查询的结果也不再返回Cursor对象,然后再由开发者自己去逐个取出,而是直接返回封装好的对象。这些改变都使得查询数据变得更加简单,也更加合理,那么下面我们就来完整地学习一下LitePal中查询数据的所有用法。
简单查询
比如说现在我们想实现一个最简单的功能,查询news表中id为1的这条记录,使用LitePal就可以这样写:
News news = DataSupport.find(News.class, 1);
天呐!有没有觉得太轻松了?仅仅一行代码,就可以把news表中id为1的记录查出来了,而且结果还是自动封装到News对象里的,也不需要我们手动再从Cursor中去解析。如果是用原生的SQL语句,或者query()方法来写,至少要20行左右的代码才能完成同样的功能!
那我们先冷静一下,来分析分析这个find()方法。可以看到,它的参数列表也比较简单,只接收两个参数,第一个参数是一个泛型类,也就是说我们在这里指定什么类,返回的对象就是什么类,所以这里传入News.class,那么返回的对象也就是News了。第二个参数就更简单了,就是一个id值,如果想要查询id为1的记录就传1,想查id为2的记录就传2,以此类推。
本来一个还算颇为复杂的功能,通过LitePal之后就变得这么简单了!那么你可能已经迫不及待地想要学习更多LitePal中更多的查询用法了,别着急,我们一个个来看。
你也许遇到过以下场景,在某些情况下,你需要取出表中的第一条数据,那么传统的做法是怎么样的呢?在SQL语句中指定一个limit值,然后获取返回结果的第一条记录。但是在LitePal中不用这么麻烦,比如我们想要获取news表中的第一条数据,只需要这样写:
News firstNews = DataSupport.findFirst(News.class);
OK,语义性非常强吧,让人一眼就看懂是什么意思了,只需调用findFirst()方法,然后传入News类,得到的就是news表中的第一条数据了。
那我们举一翻三,如果是想要获取News表中的最后一条数据该怎么写呢?同样简单,如下所示:
News lastNews = DataSupport.findLast(News.class);
因为获取表中第一条或者是最后一条数据的场景比较常见,所以LitePal特意提供了这两个方法来方便我们的操作。
那么我们看到这里,目前都只是查询单条数据的功能,如果想要查询多条数据该怎么办呢?比如说,我们想把news表中id为1、3、5、7的数据都查出来,该怎么写呢?也许有的朋友会比较聪明,立马就想到可以一个个去查,就调用四次find()方法嘛,然后把1、3、5、7这四个id分别传进去不就可以了。没错,这样做完全是可以的,而且效率也并不低,但是LitePal给我们提供了一个更简便的方法——findAll()。这个方法的用法和find()方法是非常类似的,只不过它可以指定多个id,并且返回值也不再是一个泛型类对象,而是一个泛型类集合,如下所示:
List<News> newsList = DataSupport.findAll(News.class, 1, 3, 5, 7);
可以看到,首先我们是调用的findAll()方法,然后这个方法的第一个参数仍然是指定的泛型类,但是后面的参数就很随意了,你可以传入任意个id进去,findAll()方法会把所有传入的id所对应的数据全部查出来,然后一起返回到List<News>这个泛型集合当中。
虽说这个语法设计算是相当人性化,但是在有些场景或许不太适用,因为可能要你要查询的多个id已经封装到一个数组里了。那么没关系,findAll()方法也是接收数组参数的,所以说同样的功能你也可以这样写:
long[] ids = new long[] { 1, 3, 5, 7 };
List<News> newsList = DataSupport.findAll(News.class, ids);
看到这里,那有的朋友可能会奇怪了,说findAll()方法不应该是查询所有数据的意思吗?怎么总是查询几个id所对应数据呢?哈!这个问题问得好,因为findAll()方法也是可以查询所有数据的,而且查询所有数据的写法更简单,只需要这样写:
List<News> allNews = DataSupport.findAll(News.class);
看到没有,我们只需要把后面的参数都去掉,在不指定具体id的情况下,findAll()方法查询出的就是news表中的所有数据了,是不是语义性非常强?
而且大家不要以为刚才这些都只是findAll()的几个方法重载而已,实际上刚才我们的这几种用法都是调用的同一个findAll()方法!一个方法却能够实现多种不同的查询效果,并且语义性也很强,让人一看就能理解,这就是LitePal的查询艺术!
连缀查询
当然了,LitePal给我们提供的查询功能还远远不只这些,好戏还在后头。相信大家现在也已经发现了,我们目前的查询功能都是基于id来进行查询的,并不能随意地指定查询条件。那么怎样才能指定查询条件呢?让我们回想一下传统情况应该怎么做,query()方法中接收七个参数,其中第三和第四个参数就是用于指定查询条件的,然后其它几个参数都填null就可以了。但是呢,前面我们已经痛批过了这种写法,因为冗长的参数列表太过繁琐,那么LitePal又是怎么解决这个问题的呢?我们现在就来学习一下。
为了避免冗长的参数列表,LitePal采用了一种非常巧妙的解决方案,叫作连缀查询,这种查询很灵活,可以根据我们实际的查询需求来动态配置查询参数。 那这里举个简单的例子,比如我们想查询news表中所有评论数大于零的新闻,就可以这样写:
List<News> newsList = DataSupport.where("commentcount > ?", "0").find(News.class);
可以看到,首先是调用了DataSupport的where()方法,在这里指定了查询条件。where()方法接收任意个字符串参数,其中第一个参数用于进行条件约束,从第二个参数开始,都是用于替换第一个参数中的占位符的。那这个where()方法就对应了一条SQL语句中的where部分。
接着我们在where()方法之后直接连缀了一个find()方法,然后在这里指定一个泛型类,表示用于查询哪张表。那么上面的一段代码,查询出的结果和如下SQL语句是相同的:
select * from users where commentcount > 0;
但是这样会将news表中所有的列都查询出来,也许你并不需要那么多的数据,而是只要title和content这两列数据。那么也很简单,我们只要再增加一个连缀就行了,如下所示:
List<News> newsList = DataSupport.select("title", "content")
.where("commentcount > ?", "0").find(News.class);
可以看到,这里我们新增了一个select()方法,这个方法接收任意个字符串参数,每个参数要求对应一个列名,这样就只会把相应列的数据查询出来了,因此select()方法对应了一条SQL语句中的select部分。
那么上面的一段代码,查询出的结果和如下SQL语句是相同的:
select title,content from users where commentcount > 0;
很好玩吧?不过这还不算完呢,我们还可以继续连缀更多的东西。比如说,我希望将查询出的新闻按照发布的时间倒序排列,即最新发布的新闻放在最前面,那就可以这样写:
List<News> newsList = DataSupport.select("title", "content")
.where("commentcount > ?", "0")
.order("publishdate desc").find(News.class);
order()方法中接收一个字符串参数,用于指定查询出的结果按照哪一列进行排序,asc表示正序排序,desc表示倒序排序,因此order()方法对应了一条SQL语句中的order by部分。
那么上面的一段代码,查询出的结果和如下SQL语句是相同的:
select title,content from users where commentcount > 0 order by publishdate desc;
然后呢,也许你并不希望将所有条件匹配的结果一次性全部查询出来,因为这样数据量可能会有点太大了,而是希望只查询出前10条数据,那么使用连缀同样可以轻松解决这个问题,代码如下所示:
List<News> newsList = DataSupport.select("title", "content")
.where("commentcount > ?", "0")
.order("publishdate desc").limit(10).find(News.class);
这里我们又连缀了一个limit()方法,这个方法接收一个整型参数,用于指定查询前几条数据,这里指定成10,意思就是查询所有匹配结果中的前10条数据。
那么上面的一段代码,查询出的结果和如下SQL语句是相同的:
select title,content from users where commentcount > 0 order by publishdate desc limit 10;
刚才我们查询到的是所有匹配条件的前10条新闻,那么现在我想对新闻进行分页展示,翻到第二页时,展示第11到第20条新闻,这又该怎么实现呢?没关系,在LitePal的帮助下,这些功能都是十分简单的,只需要再连缀一个偏移量就可以了,如下所示:
List<News> newsList = DataSupport.select("title", "content")
.where("commentcount > ?", "0")
.order("publishdate desc").limit(10).offset(10)
.find(News.class);
可以看到,这里我们又添加了一个offset()方法,用于指定查询结果的偏移量,这里指定成10,就表示偏移十个位置,那么原来是查询前10条新闻的,偏移了十个位置之后,就变成了查询第11到第20条新闻了,如果偏移量是20,那就表示查询第21到第30条新闻,以此类推。因此,limit()方法和offset()方法共同对应了一条SQL语句中的limit部分。
那么上面的一段代码,查询出的结果和如下SQL语句是相同的:
select title,content from users where commentcount > 0 order by publishdate desc limit 10,10;
这大概就是LitePal中连缀查询的所有用法了。看出区别了吧?这种查询的好处就在于,我们可以随意地组合各种查询参数,需要用到的时候就把它们连缀到一起,不需要用到的时候不用指定就可以了。对比一下query()方法中那冗长的参数列表,即使我们用不到那些参数,也必须要传null,是不是明显感觉LitePal中的查询更加人性化?
激进查询
不过,上述我们的所有用法中,都只能是查询到指定表中的数据而已,关联表中数据是无法查到的,因为LitePal默认的模式就是懒查询,当然这也是推荐的查询方式。那么,如果你真的非常想要一次性将关联表中的数据也一起查询出来,当然也是可以的,LitePal中也支持激进查询的方式,下面我们就来一起看一下。
不知道你有没有发现,刚才我们所学的每一个类型的find()方法,都对应了一个带有isEager参数的方法重载,这个参数相信大家一看就明白是什么意思了,设置成true就表示激进查询,这样就会把关联表中的数据一起查询出来了。
比如说,我们想要查询news表中id为1的新闻,并且把这条新闻所对应的评论也一起查询出来,就可以这样写:
News news = DataSupport.find(News.class, 1, true);
List<Comment> commentList = news.getCommentList();
可以看到,这里并没有什么复杂的用法,也就是在find()方法的最后多加了一个true参数,就表示使用激进查询了。这会将和news表关联的所有表中的数据也一起查出来,那么comment表和news表是多对一的关联,所以使用激进查询一条新闻的时候,那么该新闻所对应的评论也就一起被查询出来了。
激进查询的用法非常简单,就只有这么多,其它find()方法也都是同样的用法,就不再重复介绍了。但是这种查询方式LitePal并不推荐,因为如果一旦关联表中的数据很多,查询速度可能就会非常慢。而且激进查询只能查询出指定表的关联表数据,但是没法继续迭代查询关联表的关联表数据。因此,这里我建议大家还是使用默认的懒加载更加合适,至于如何查询出关联表中的数据,其实只需要在模型类中做一点小修改就可以了。修改News类中的代码,如下所示:
public class News extends DataSupport{
...
public List<Comment> getComments() {
return DataSupport.where("news_id = ?", String.valueOf(id)).find(Comment.class);
}
}
可以看到,我们在News类中添加了一个getComments()方法,而这个方法的内部就是使用了一句连缀查询,查出了当前这条新闻对应的所有评论。改成这种写法之后,我们就可以将关联表数据的查询延迟,当我们需要去获取新闻所对应的评论时,再去调用News的getComments()方法,这时才会去查询关联数据。这种写法会比激进查询更加高效也更加合理。
原生查询
相信你已经体会到,LitePal在查询方面提供的API已经相当丰富了。但是,也许你总会遇到一些千奇百怪的需求,可能使用LitePal提供的查询API无法完成这些需求。没有关系,因为即使使用了LitePal,你仍然可以使用原生的查询方式(SQL语句)来去查询数据。DataSuppport类中还提供了一个findBySQL()方法,使用这个方法就能通过原生的SQL语句方式来查询数据了,如下所示:
Cursor cursor = DataSupport.findBySQL("select * from news where commentcount>?", "0");
findBySQL()方法接收任意个字符串参数,其中第一个参数就是SQL语句,后面的参数都是用于替换SQL语句中的占位符的,用法非常简单。另外,findBySQL()方法返回的是一个Cursor对象,这和原生SQL语句的用法返回的结果也是相同的。
好了,这样我们就把LitePal中提供的查询数据的方法全部都学完了,那么今天的文章就到这里
Android数据库框架LitePal详解相关推荐
- android多媒体框架学习 详解
原址 一:多媒体框架概述 jellybean 的多媒体跟以前的版本,通过对比没啥变化,最大的变化是google终于舍得给multimedia建个独立的git了(framework/av),等你好久了! ...
- android多媒体框架学习 详解 最新版本
一:多媒体框架概述 jellybean 的多媒体跟以前的版本,通过对比没啥变化,最大的变化是google终于舍得给multimedia建个独立的git了(framework/av),等你好久了!也体现 ...
- Android自动化测试框架uiautomator2详解
1 uiautomator2 简介 uiautomator2 是 一种 Android 自动化测试框架,提供了点击.长按.输入文本.滑动.拖拽.截屏等方法,能够模拟用户的各种动作.用户可以通过控件的 ...
- android应用程序框架图,Android系统框架图详解
Android系统从下至上分为4层:Linux内核.Android程序库及Android运行时.Android应用程序框架,以及应用程序等. 图1-1 Android系统框架 1. Linux内核(L ...
- Android音频框架之一 详解audioPolicy流程及HAL驱动加载与配置
前言 此音频架构梳理笔记.主要是因工作上需要在 Android8.1 以上版本中,增加 snd-aloop 虚拟声卡做前期准备工作, 本篇文章提纲挈领的把音频框架主线梳理清晰,通过这篇文章能够清晰如下 ...
- Android 进阶——Framework 核心之Android Storage Access Framework(SAF)存储访问框架机制详解(二)
文章大纲 引言 一.DirectFragment 1.当选中DirectoryFragment中RecyclerView的Item时 2.选中DirectoryFragment中RecyclerVie ...
- Android客户端开发—开源数据库框架LitePal
LitePal是一款开源的Android数据库框架,采用对象关系映射(ORM)模式,将常用的数据库功能进行封装,不使用SQL语句就可以完成创建表以及表单的CRUD操作,并且很轻量级,几乎零配置.(它将 ...
- android 开源 数据库,Android开源数据库框架-LitePal的使用
简介 LitePal是一款开源的Android数据库框架,采用了关系映射的模式.LitePal在github上的项目地址:github.com/LitePalFram- 配置LitePal depen ...
- Android UI 测试框架Espresso详解
Android UI 测试框架Espresso详解 1. Espresso测试框架 2.提供Intents Espresso 2.1.安装 2.2.为Espresso配置Gradle构建文件 2.3. ...
- Android 进阶——Framework 核心之Android Storage Access Framework(SAF)存储访问框架机制详解(一)
文章大纲 引言 一.Android Storage Access Framework 二.Storage Access Framework 的主要角色成员 1.Document Provider 文件 ...
最新文章
- WKWebView 那些坑
- 后端必备:15000 字的 SQL 语句大全
- 安卓给string对象赋值_String 面试题!看完让你恍然大悟!
- 《阿里巴巴 Java 开发手册》读书笔记
- 2021Java就业笔试题总结
- 什么叫pin脚的pad_为什么特小尺寸液晶屏价格比较高
- ML.NET机器学习、API容器化与Azure DevOps实践(一):简介
- linux浏览器不能播放音频文件夹,在html中插入音频文件在浏览器中播放音频文件的兼容性问题...
- Neo4j:动态添加属性/设置动态属性
- IDEA安装“Alibaba Java Coding Guidelines”插件
- 【Android图像处理】图像处理之-素描效果
- Adobe Illustrator【印前角线X2.0】脚本源码
- CCF-CSP真题《202209-4—吉祥物投票》思路+python题解
- EDK2源码下载及环境搭建
- 一键构建云上高可用蛋白质结构预测平台
- 【Python web 开发】 bottle 简单教程 (四)
- 经典数学问题——三门问题(数据分析面试题)
- 关于EOSUNION 的商业创业大赛的一点心得
- Parameter-Efficient Conformers via Sharing Sparsely-Gated Experts for End-to-End Speech Recognition
- 基于ArcGIS Pro城市用地适宜性评价