在本系列的第一篇文章中,我讨论了RDBMS作为Java™对象存储解决方案的失败。 正如我所解释的那样,在当今的面向对象的世界中,像db4o这样的对象数据库可以为面向对象的开发人员提供更多的功能,而不仅仅是关系数据库。

关于本系列

大约十年来,信息存储和检索几乎已经成为RDBMS的同义词,但是最近情况已经开始改变。 尤其是Java开发人员对所谓的对象关系阻抗不匹配感到沮丧,并且对尝试解决它的解决方案不耐烦。 这以及可行的替代方案的出现,引起了对对象持久性和检索兴趣的复兴。 本系列是db4o的工作介绍,db4o是一个开放源码数据库,它利用了当今的面向对象的语言,系统和思维方式。

在本文和以后的文章中,我将继续介绍对象数据库。 我将使用示例来展示存储系统的强大功能,该存储系统针对您在选择的面向对象编程语言(在本例中为Java语言)中使用的相同“形状”实体进行了优化。 特别是,我将介绍各种可用于将对象检索,修改和还原回db4o的机制。 正如您将学到的,一旦您摆脱了SQL的约束,您可以做的事实际上令人惊讶。

如果尚未这样做,则可能要立即下载db4o 。 您需要它来编译示例。

实例查询

示例查询(QBE)是一种数据库查询语言,它允许您通过设计要进行比较的“模板”来创建查询,而不是使用谓词条件的语言(如SQL)。 我上次演示了使用db4o的QBE引擎进行数据检索,但是在这里我将快速回顾一下。 首先看一下我公认的原始数据库。 它由一种类型组成,其定义如清单1所示:

清单1. Person类
package com.tedneward.model;public class Person
{public Person(){ }public Person(String firstName, String lastName, int age){this.firstName = firstName;this.lastName = lastName;this.age = age;}public String getFirstName() { return firstName; }public void setFirstName(String value) { firstName = value; }public String getLastName() { return lastName; }public void setLastName(String value) { lastName = value; }public int getAge() { return age; }public void setAge(int value) { age = value; }public String toString(){return "[Person: " +"firstName = " + firstName + " " +"lastName = " + lastName + " " +"age = " + age + "]";}public boolean equals(Object rhs){if (rhs == this)return true;if (!(rhs instanceof Person))return false;Person other = (Person)rhs;return (this.firstName.equals(other.firstName) &&this.lastName.equals(other.lastName) &&this.age == other.age);}private String firstName;private String lastName;private int age;
}

随着POJO的发展, Person几乎不是一个复杂的野兽。 它由三个字段和一些支持类似于POJO的活动的基本方法组成,即toString()equals() 。 (约书亚·布洛赫(Joshua Bloch)的《 有效Java》的精明读者会注意到,我省略了hashCode()实现,这明显违反了规则8。在经典的作者看来,我将hashCode()留为“对读者的一种练习, “这通常意味着作者要么不想打扰它,要么认为手头的例子不必要。我也把它留给读者作为练习,以决定在这里是哪种情况。”

在清单2中,我创建了六个对象,将它们放入文件中,然后使用QBE调用名字与模式“ Brian”匹配的两个对象。 这种查询风格使用原型对象(传递给get()调用的原型对象)来确定数据库中的对象是否匹配,并返回那些符合条件的对象的ObjectSet (本质上是一个集合)。

清单2.通过示例查询
import com.db4o.*;import com.tedneward.model.*;public class Hellodb4o
{public static void main(String[] args)throws Exception{ObjectContainer db = null;try{db = Db4o.openFile("persons.data");Person brian = new Person("Brian", "Goetz", 39);Person jason = new Person("Jason", "Hunter", 35);Person brians = new Person("Brian", "Sletten", 38);Person david = new Person("David", "Geary", 55);Person glenn = new Person("Glenn", "Vanderberg", 40);Person neal = new Person("Neal", "Ford", 39);db.set(brian);db.set(jason);db.set(brians);db.set(david);db.set(glenn);db.set(neal);db.commit();// Find all the BriansObjectSet brians = db.get(new Person("Brian", null, 0));while (brians.hasNext())System.out.println(brians.next());}finally{if (db != null)db.close();}}
}

查询规则

由于QBE使用原型对象作为其模板来搜索数据,因此有一些关于其用法的简单规则。 当db4o正在为给定目标搜索“ Person类型的所有对象时(发生的事情过于简化,但从概念上讲是准确的),以确定数据存储中的特定对象是否符合条件,将字段值一一比较。 如果原型中的字段为“ null”,则该值与数据存储区中的任何值匹配;否则为0。 否则,值必须完全匹配。 对于基本类型,由于基本类型不能真正保存“ null”值,因此零用作通配符值。 (这也指出了QBE方法的局限性-不能有效地使用零作为搜索值。)如果指定了多个字段值,则数据库中的对象必须满足候选人的所有字段值对象符合查询条件; 从本质上讲,这意味着将字段“与”在一起以形成查询谓词。

在前面的示例中,查询正在查找firstName字段等于“ Brian”且有效忽略了lastNameage字段的所有Person类型。 在表中,此调用将大致对应于SELECT * FROM Person WHERE firstName = "Brian"SQL查询。 (但是,要谨慎尝试将OODBMS查询映射到SQL:这种类比并不完美,并且可能导致对特定查询的性质和性能的误解。)

查询返回的对象是ObjectSet ,它与JDBC ResultSet相似,因为它是对象的简单容器。 使用ObjectSet实现的Iterator接口,对结果进行ObjectSet是一个简单的练习。 使用Person的特定方法将需要对next()返回的对象进行向下转换。

更新和身份

尽管简单的数据显示本身很有趣,但是大多数对象也需要进行修改并将其还原回数据库。 这可能是使用OODBMS的最棘手的部分,因为对象数据库使用的标识概念与关系数据库使用的标识概念不同。 实际上,这意味着在使用对象数据库时,必须更加注意内存中的对象与存储中的对象。

清单3中的简单示例演示了身份的不同概念:

清单3.三个布莱恩斯
import com.db4o.*;import com.tedneward.model.*;public class Hellodb4o
{public static void main(String[] args)throws Exception{ObjectContainer db = null;try{db = Db4o.openFile("persons.data");Person brian = new Person("Brian", "Goetz", 39);Person jason = new Person("Jason", "Hunter", 35);Person brians = new Person("Brian", "Sletten", 38);Person david = new Person("David", "Geary", 55);Person glenn = new Person("Glenn", "Vanderberg", 40);Person neal = new Person("Neal", "Ford", 39);db.set(brian);db.set(jason);db.set(brians);db.set(david);db.set(glenn);db.set(neal);db.commit();// Find all the BriansObjectSet brians = db.get(new Person("Brian", null, 0));while (brians.hasNext())System.out.println(brians.next());Person brian2 = new Person("Brian", "Goetz", 39);db.set(brian2);db.commit();// Find all the BriansObjectSet brians = db.get(new Person("Brian", null, 0));while (brians.hasNext())System.out.println(brians.next());}finally{if (db != null)db.close();}}
}

当您运行清单3中的查询时,数据库报告三个Brian ,其中两个Brian Goetz。 (如果person.data文件已经存在于当前目录中,则会产生类似的效果-创建的所有Person都将存储在person.data文件中,并且存储在其中的所有Brian都将由查询返回。 )

扩展界面

db4o开发团队偶尔会发现某些API的使用频率较低,或者代表该团队不确定该API是否应作为核心ObjectContainer API的一部分的“实验”。 在这些情况下,方法是在ext()方法返回的ExtObjectContainer实例上提供的。 引入,删除或移到核心ObjectContainer类本身后,该类上可用的方法因版本而异。 已知该列表包含测试内存中对象的方法,以查看它们是否与db4o容器实例相关联,获取容器已知的所有类的列表或设置/释放并发信号量的方法。 与往常一样,请参见db4o文档以获取ExtObjectContainer类的完整详细信息。

显然,关于主键的旧规则在这里并没有生效; 那么对象数据库如何处理唯一性概念?

拥抱OID

将对象存储到对象数据库中时,将创建一个唯一键,称为对象标识符或OID (与最后一个回避音节的发音类似),它唯一地标识该对象。 除非明确请求,否则OID就像C#和Java编程中的this指针/引用一样,是无声的。 在db4o中,可以通过调用db.ext().getID()找到给定对象的OID。 (您还可以使用db.ext().getByID()方法通过OID检索对象。调用此方法的含义有些复杂,因此此处db.ext().getByID()讨论,但仍然可以选择。)

实际上,所有这些意味着开发人员可以通过先前在容器中查询该对象来确定对象是否存在于系统中,如清单4所示:

清单4.插入之前的查询
// ... as beforeObjectContainer db = null;try{db = Db4o.openFile("persons.data");...// We want to add Brian Goetz to the database; is he already there?if (db.get(new Person("Brian", "Goetz", 0).hasNext() == false){// Nope, no Brian Goetz here, go ahead and add himdb.set(new Person("Brian", "Goetz", 39));db.commit();}}

在这种特殊情况下,我们假设一个Person在系统中的唯一性是其名字-姓氏组合。 因此,在数据库中搜索Brian时,只需在Person实例上查找那些属性。 (也许布莱恩是在几年前加入的,那时他才39岁。)

如果要修改数据库中的对象,可以很简单地从容器中检索对象,以某种方式对其进行修改,然后再将其存储回去,如清单5所示:

清单5.更新对象
// ... as beforeObjectContainer db = null;try{db = Db4o.openFile("persons.data");...// Happy Birthday, David Geary!if ((ObjectSet set = db.get(new Person("David", "Geary", 0))).hasNext()){Person davidG = (Person)set.next();davidG.setAge(davidG.getAge() + 1);db.set(davidG);db.commit();}elsethrow new MissingPersonsException("David Geary doesn't seem to be in the database");}

db4o容器在这里不会遇到身份问题,因为相关对象已被识别为来自数据库的对象,这意味着其OID已存储在db4o簿记基础结构中。 因此,当您调用set ,db4o知道更新现有对象而不是插入新对象。

搜索实用程序方法

尽管不是QBE固有的,但特定于应用程序的主键的概念还是值得保留的。 您需要一种实用程序方法来简化基于身份的搜索。 本节向您展示了一种基于解决方案的解决方案,该解决方案使用了Reflection API在正确的字段中戳入正确的值,并提出了针对各种喜好和美感来调整解决方案的方法。

让我们从一个基本前提开始:我有一个db4o数据库,其中有一个我要基于其中具有某些值的字段集进行查询的类型( Person )。 在此方法中,我使用Class上的Reflection API来创建该类型的新实例(调用其默认构造函数)。 然后,我遍历其中包含字段的字符串数组,以获取Class中的每个Field对象。 之后,我遍历与每个字段的值相对应的对象数组,然后调用Field.set()将那个值Field.set()我的模板对象中。

完成这些操作后,我在db4o数据库上调用get()并检查返回的ObjectSet是否包含任何对象。 这给了我一个基本的方法概述,如清单6所示:

清单6.用于执行QBE身份搜索的实用程序方法
import java.lang.reflect.*;
import com.db4o.*;public class Util
{public static boolean identitySearch(ObjectContainer db, Class type,String[] fields, Object[] values)throws InstantiationException, IllegalAccessException,NoSuchFieldException{// Create an instance of our typeObject template = type.newInstance();// Populate its fields with the passed-in template valuesfor (int i=0; i<fields.length; i++){Field f = type.getDeclaredField(fields[i]);if (f == null)throw new IllegalArgumentException("Field " + fields[i] + " not found on type " + type);if (Modifier.isStatic(f.getModifiers()))throw new IllegalArgumentException("Field " + fields[i] + " is a static field and cannot be used in a QBE query");f.setAccessible(true);f.set(template, values[i]);}// Do the queryObjectSet set = db.get(template);if (set.hasNext())return true;elsereturn false;}
}

显然,可以做很多工作来调整此方法的味道,例如捕获所有异常类型并将它们作为运行时异常重新抛出,或者返回ObjectSet本身而不是true / false ,甚至返回包含以下内容的对象数组: ObjectSet内容(这将使检查返回数组的长度变得容易)。 但是,从清单7中可以明显看出,它的用法比已经显示的基本QBE版本简单得多:

清单7.工作中的实用程序方法
// Is Brian already in the database?
if (Util.identitySearch(db, Person.class, {"firstName", "lastName"}, {"Brian", "Goetz"}) == false)
{db.set(new Person("Brian", "Goetz", 39));db.commit();
}

实际上,当将实用程序方法的许多实用程序放置在存储的类本身上时,它们就会变得很明显,如清单8所示:

清单8.在Person内部使用Utility方法
public class Person
{// ... as beforepublic static boolean exists(ObjectContainer db, Person instance){return (Util.identitySearch(db, Person.class,{"firstName", "lastName"},{instance.getFirstName(), instance.getLastName()});}
}

或者,再次,您可以调整方法以返回找到的实例,以使Person实例具有适当关联的OID,依此类推。 要记住的关键是,您可以在db4o基础结构之上构建便捷方法,以使其易于使用。

请注意,使用db4o SODA查询API可以对存储在磁盘上的基础对象执行这种查询样式,这是一种更有效的方法,但是它在本文的讨论范围之外,因此我将其留待以后的讨论。

高级查询

到目前为止,您已经了解了如何查询单个对象或满足特定条件的对象。 尽管这使发出查询的方法相当容易,但也使选择受到了限制。 例如,如果你需要什么样的检索所有Person (胡)的名字开始与G,或所有Person的年龄大于21号第? 对于这些类型的查询,QBE方法将严重失败,因为QBE进行相等匹配而不是比较。

从历史上看,即使是比较复杂的比较也一直是OODBMS的弱点,也是关系模型和SQL的强项。 在SQL中发出比较查询很简单,但是要在OODBMS中执行相同的查询,则需要采用几种不引人注意的方法之一:

  • 获取所有对象并自己进行相对比较。
  • 扩展QBE API以包括谓词。
  • 创建查询语言以将其转换为针对您的对象模型的查询。

比较弱

显然,上面的第一个选项仅对最普通的数据库才可行,因为它给您可以实际使用的数据库大小增加了明显的上限。 即使最坚硬的硬件也无法轻易摆脱百万个对象的负担,尤其是在通过网络连接的情况下。 (顺便说一下,这并不是对OODBMS的起诉-通过RDBMS服务器的能力可以通过网络连接获取一百万行,但仍会破坏其所在的网络。)

第二种选择污染了QBE方法的简单性,并导致了清单9中所示的怪异现象:

清单9.带有谓词的QBE调用
Query q = new Query();
q.setClass(Person.class);
q.setPredicate(new Predicate(new And(new Equals(new Field("firstName"), "David"),new GreaterThan(new Field("age"), 21))));
q.Execute();

很容易看出使用这种技术,任何中等复杂的查询将如何Swift变得不可行,特别是与诸如SQL之类的查询语言相比时。

第三种选择是创建一种查询语言,然后该查询语言可用于查询数据库的对象模型。 过去,OODBMS员工创建了标准查询语言,对象查询语言或OQL,其外观类似于清单10:

清单10. OQL的片段
SELECT p FROM Person
WHERE p.firstName = "David" AND p.age > 21

从表面上看,OQL看起来与SQL非常相似,因此据说同样强大且易于使用。 OQL的缺点是它想返回……什么? 类似于SQL的语言似乎像SQL一样想要返回列集(元组),但是对象数据库不能那样工作-它想要返回对象,而不是任意集。 特别是在像C#或Java编程这样的强类型语言中,这些对象类型必须先验地知道 ,这与基于集合SQL概念不同。

db4o中的本机查询

db4o并没有强迫开发人员使用复杂的查询API或引入新的“ something-QL”,而是提供了一种称为本机查询的功能 ,该功能既强大又非常易于使用,如清单11所示。对于db4o的API 是 SODA查询,这是主要用于细粒度查询控制的形式提供。正如你在第二个看不过来,SODA通常只用于手动优化查询必要的。)

清单11. db4o本机查询
// ... as beforeObjectContainer db = null;try{db = Db4o.openFile("persons.data");...// Who wants to get a beer?List<Person> drinkers = db.query(new Predicate<Person>() {public boolean match(Person candidate) {return person.getAge() > 21;}}for (Person drinker : drinkers)System.out.println("Here's your beer, " + person.getFirstName());}

查询的“本机”部分是这样的事实:它是用编程语言本身(在这种情况下是Java语言)编写的,而不是用某种任意的语言编写的,然后必须将其翻译成其他某种语言。 (Predicate API的非泛型版本可用于Java 5之前的版本,尽管使用起来不太容易。)

片刻考虑一下,您可能会开始怀疑这种特定方法是如何实施的。 或者必须使用源预处理器将其中包含查询的源文件转换为数据库引擎可以理解的内容( 例如 SQL / J或其他嵌入式预处理器),或者数据库会将所有Person对象发送回给对整个集合执行谓词的客户端(换句话说,就是之前拒绝的方法)。

事实证明,db4o均不执行任何操作。 相反,db4o背后的负责人选择对本地查询采用一种有趣且创新的方法。 松散地说,db4o系统将一个谓词发送到数据库,在数据库中它在运行时对match()方法的字节码执行字节码分析。 如果字节码足够容易理解,db4o会将该查询转换为SODA查询以提高效率,在这种情况下,无需实例化所有对象以传递给match()方法。 这样,程序员可以继续使用自己喜欢的语言编写查询,但是查询本身可以转换为数据库可以理解和有效执行的内容。 (如果愿意的话,可以选择“ JQL” - Java查询语言。但是,请不要对db4o开发人员重复该名称;否则会给我带来麻烦。)

确保包括BLOAT!

db4o Java发行版包含几个jar文件,包括针对每个JDK 1.1,JDK 1.2和Java 5发行版的核心db4o实现。 该发行版中还包括一个名为BLOAT的jar文件。 尽管是它的名字,但它是普渡大学开发的Java字节码优化器,必须与db4o-5.0-nqopt.jar一起出现在运行时类路径中,才能进行本机查询。 不包含这些库将不会产生任何类型的错误,而只会导致每个本机查询未优化。 (开发人员可以使用本节中描述的侦听器,但只能以被动方式发现这一点。)

让db4o告诉您...

本机查询方法并不完美。 例如,完全有可能编写一个足以打败字节码分析器的本机查询,从而需要执行最坏情况的执行模型。 在这种最坏的情况下,db4o必须实例化数据库中查询类型的每个对象,并将每个对象都通过match()实现。 可以预计,这会降低查询性能,但是您可以通过在需要的地方安装侦听器来解决此问题。

直觉并不总是足以预见优化失败,因为原因可能与代码审查所暗示的完全不同。 例如,在控制台代码中包含控制台打印语句(Java代码中为System.out.println或C#中包含System.Console.WriteLine )会导致优化器在db4o的.NET版本中失败,而Java版本会优化该语句。 您无法真正预料到这种类型的变化(尽管您可以通过经验来了解它们),所以让系统告诉您总是一个好主意,就像他们在极限编程中所说的那样。

只需在ObjectContainer本身上注册一个侦听器( Db4oQueryExecutionListener ),以通知您是否无法优化本机查询,如清单12所示:

清单12. DiagnosticListener
// ... as beforeObjectContainer db = null;try{db = Db4o.openFile("persons.data");db.ext().configure().diagnostic().addListener(new DiagnosticListener() {public void onDiagnostic(Diagnostic d) {if (d instanceof NativeQueryNotOptimized){// could display information here, but for simplicity// let's just fail loudlythrow new RuntimeException("Native query failed optimization!");}}});}

显然,这仅在开发过程中是合乎需要的-在运行时,最好将此故障记录到log4j错误流或对用户分心的东西。

结论

在《繁忙的Java开发人员db4o指南》的第二篇文章中,我将OODBMS身份概念用作解释db4o如何存储和检索对象以及引入其本机查询功能的起点。

QBE是用于简单查询情况的首​​选机制,因为它是一种更易于使用的API,但是它确实要求您的域对象允许将包含数据的任何或所有字段设置为null,这可能会违反您的某些域规则。 例如,能够为Person对象强制使用名字和姓氏将是很好的。 但是,在QBE查询中仅使用Person作为姓氏,就要求将名字允许为空,这实际上意味着我们必须选择域约束或查询功能,而这两者都不是完全可以接受的。

本机查询提供了执行复杂查询的强大方法,而无需学习新的查询语言或诉诸复杂的对象结构来对谓词建模。 对于db4o的本机查询工具无法满足需求的情况,SODA API(最初显示为任何对象系统的独立查询系统,并且仍然存在于SourceForge上)使您可以将查询调优至最细微的细节,以简单为代价。

这种多方面的数据库查询方法可能会让您感到复杂和混乱,并且与RDBMS的工作方式完全不同。 实际上,情况并非如此:大多数大型数据库将SQL文本转换为字节码格式,然后对其进行分析和优化,然后针对存储在磁盘上的数据执行,组合回文本并返回。 db4o本机查询方法将编译后的字节码重新交到Java(或C#)编译器手中,从而允许类型安全和更早地检测到错误的查询语法。 (顺便说一下,遗憾的是,JDBC访问SQL的方法中缺少类型安全性,因为它是一个调用级接口,因此仅限于只能在运行时检查的字符串。这对任何CLI都是如此,而不仅仅是JDBC; ODBC和.NET的ADO.NET受到相同的限制。)优化仍在数据库内部完成,但不是将文本返回,而是将真实对象发回,以备使用。 这与SQL / Hibernate或其他ORM方法形成鲜明对比,Esther Dyson著名地描述了以下方法:

使用桌子存放物品就像开车开车然后将其分解以放入车库一样。 它可以在早上再次组装,但最终有人问这是否是最有效的停车方式。

确实。 下次见。


翻译自: https://www.ibm.com/developerworks/java/library/j-db4o2/index.html

用sql查询姓名和身份证_查询,更新和身份相关推荐

  1. mysql查询男女平均年龄_查询计算机系学生的姓名、性别和年龄

    [填空题]用 insert命令向学生表student里插入一条新记录:学号为18007,姓名为:张飞驰,男,20岁,师范系 () into student(sno,sname,sex,sdept) v ...

  2. 如何查询以太信道接口_查询区块

    --- 概述: 用Go查询以太坊区块. --- # 查询区块 正如我们所见,您可以有两种方式查询区块信息. #### 区块头 您可以调用客户端的`HeadByNumber`来返回有关一个区块的头信息. ...

  3. mysql查询学生成绩语句_查询每个学生的各科成绩sql语句

    展开全部 1.查询每个学生的各科成绩sql语句: select a.studentid,a.name,a.sex,v1.score as '语文',v2.score as '数学', v3.score ...

  4. sql查询mysql参数配置_查询参数配置

    示例 请求示例 http(s)://rds.aliyuncs.com/?Action=DescribeParameters &DBInstanceId=rm-uf6wjk5xxxxxxx &a ...

  5. 在mysql查询库和表_查询mysql 库和表占的大小

    use information_schema; select concat(round(sum(data_length/1024/1024),2),'MB') as data from tables; ...

  6. 苹果笔记本怎么查看计算机基本信息,如何查询苹果电脑型号_查询苹果电脑型号的方法...

    新买入苹果电脑后无法确认是什么型号?虽然可以通过电脑外箱和机身标签识别,但此不够内容不够详细.那么还有什么办法查询苹果电脑型号呢?Mac的机型信息并不是直接可以在电脑的系统信息中查询到,而是根据Mac ...

  7. mysql查询不及格的学生_查询“数据库”不及格的学生的学号和成绩。

    [单选题]在下列传输介质中,错误率最低的是() [单选题]The followings are some of the potential risks generally existing in ja ...

  8. 查询mysql版本好_查询mysql版本(select查mysql版本)

    查询mysql版本(select查mysql版本) 2020-07-24 11:32:47 共10个回答 1.通过mysql的-V参数查询版本信息mysql-V2.登录mysql时可以查询版本信息my ...

  9. java 中查询余额怎么写_查询余额示例代码

    package api.binstd.sms; import api.util.HttpUtil; import net.sf.json.JSONArray; import net.sf.json.J ...

最新文章

  1. Roger Ver:BCH也可成为价值储备,前提是它被用起来
  2. [问题解决]NotImplementedError 错误原因:子类没有实现父类要求一定要实现的接口
  3. Eclipse-Java代码规范和质量检查插件-SonarLint
  4. linux mentohust dhcp,MentoHUST的使用教程详解
  5. Heritrix 3.1.0 源码解析(八)
  6. 算法 | 数据结构与算法(代码版)
  7. composer切换源_Composer具体安装方法
  8. 使用Spring Integration Java DSL与Rabbit MQ集成
  9. emlog博客主题价值358元lu1.3模板
  10. php 指定域名的cookie,php如何设置cookie对整个域名有效?
  11. 使用kermit通过串口升级uboot
  12. GBA编程和汉化常用软件汇总
  13. 关于ini读取错误问题?
  14. Adb 微信APP降级工具使用教程
  15. 大整数除一相对较小的数
  16. centos7:configure: error: perl >= 5.7.3 with Encode and Data::Dumper required by Texinfo.
  17. 如何免费生成资讯类App
  18. 批量注册163邮箱的代码
  19. UNR2 黎明前的巧克力
  20. 学习笔记-Matlab之多项式详解

热门文章

  1. [fyne] build constraints exclude all Go files in
  2. python解一元二次方程虚根_Python编程实现数学运算求一元二次方程的实根算法示例...
  3. 新手零基础:飞桨代码中关于图片路径读取和资源解压报错
  4. 计算机表格常用根式,常用平方根表.doc
  5. WAP 构建 Java 应用 和 WAP经验总结
  6. hellojs使用 推特登录/api nuxt vue
  7. 深入理解Java虚拟机——运行时栈帧结构(局部变量表)
  8. iOS-常见三种加密(MD5、非对称加密,对称加密)
  9. 步进电机(四相五线为例子)步进角度和工作原理介绍
  10. linux su命令卡顿,linux su特别慢问题排查