代码来源于高淇JAVA教学视频 谢谢高淇老师的教学。

因为自己在学习的过程中发现了很多困难点,总结下希望对自己接下来学框架提升。给像我一样得初学者方便。

SORM框架是一个简单的ORM,关系对象映射,可以通过这个框架方便的更改和操作一些数据库的东西,在框架运行的时候也会根据数据库中的表生成相应的Javabean在po包下。通过直接对po包下的操作在运用query中的一些操作就可以实现对数据库的操作。思路会在代码中注释。

QueryFactory直接生产出Query给用的人调用,Query作为一个接口是更好的让多种数据库提供各自的操作。实现mySQLQuery或者OracleQuery,conventor是个转换不同语言数据类型的工具

TableContext就是核心的程序连接数据库表的类。DBManager钟放的是一些配置资源文件和一些加载处理。

明确主要思路,就是通过factory制作一个query,然后直接set方法然后将对象传入query的方法实现功能。查询的话也是通过queryrows方法传入select语句进行查询,因为查询语句的多样性简单的框架没有进一步封装。

先从最底层的tablecontext和DBManage说起:

解释会在代码中标识

public classDBManager {private staticConfiguration conf;               //这是在bean包下定义的一个类用来把从配置文件提取出来方便用get setprivate static List pool=new ArrayList<>();      //这是连接池的定义,我在老师的基础上做了些更改,我把连接池全部放到了这个类里static{                                      //静态代码块初始化资源文件中的数据, 注意静态块是和类一起加载的,大量用静态影响内存

Properties pro=newProperties();                      //这个类是用来从资源文件中提取数据的类try{

pro.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("database.properties"));

}catch(IOException e) {//TODO Auto-generated catch block

e.printStackTrace();

}

conf=newConfiguration();

conf.setDriver(pro.getProperty("driver"));

conf.setPoPackage(pro.getProperty("poPackage"));

conf.setPwd(pro.getProperty("pwd"));

conf.setScrPath(pro.getProperty("srcPath"));

conf.setUser(pro.getProperty("user"));

conf.setUrl(pro.getProperty("url"));

conf.setUsingDB(pro.getProperty("usingDB"));

conf.setQueryClass(pro.getProperty("queryClass"));

conf.setMAX_POOL(Integer.parseInt(pro.getProperty("max_pool")));

conf.setMIN_POOL(Integer.parseInt(pro.getProperty("min_pool")));

System.out.println(TableContext.class);//加载类                          //这是用来加载表信息的,用于表连接也可以用反射来加载,但是要trycatch选择输出加载

initPool();                                              //加载连接池

}public static voidinitPool(){                          //连接池的初始化if(pool==null){

pool=new ArrayList();

}while(pool.size()

pool.add(DBManager.createConn());//初始化

}

}public staticConnection getConn(){ //连接池取连接int last_index=pool.size()-1;

Connection conn=pool.get(last_index);

pool.remove(last_index);returnconn;

}public static voidCloseConn(Connection conn){                          //连接池关闭if(pool.size()>conf.getMAX_POOL()){try{

conn.close();

}catch(SQLException e) {//TODO Auto-generated catch block

e.printStackTrace();

}

}else{

pool.add(conn);

}

}public staticConnection createConn(){try{                                                //真正的建立一个连接

Class.forName(conf.getDriver());returnDriverManager.getConnection(conf.getUrl(),conf.getUser(),conf.getPwd());//直接建立连接

} catch(Exception e) {//TODO: handle exception

e.printStackTrace();return null;

}

}

//get用于配置信息的哪个类,很好理解public staticConfiguration getConf(){returnconf;

}

//之后的关闭就不说了,主要是要记得把关闭改变成从线程池中假关闭public static voidclose(ResultSet rs,Statement ps,Connection conn){try{if(rs!=null){

rs.close();

}

}catch(Exception e) {//TODO Auto-generated catch block

e.printStackTrace();

}try{if(ps!=null){

ps.close();

}

}catch(Exception e) {//TODO Auto-generated catch block

e.printStackTrace();

}try{if(conn!=null){

CloseConn(conn);

}

}catch(Exception e) {//TODO Auto-generated catch block

e.printStackTrace();

}

}public static voidclose(Statement ps,Connection conn){try{if(ps!=null){

ps.close();

}

}catch(Exception e) {//TODO Auto-generated catch block

e.printStackTrace();

}try{if(conn!=null){

CloseConn(conn);

}

}catch(Exception e) {//TODO Auto-generated catch block

e.printStackTrace();

}

}

}

然后现在有配置文件和连接了

接下来是表连接tablecontect

/***

* 管理数据库所有表结构和类结构的关系,并可以根据表结构生成类结构

*@authorNodeRed

**/

public classTableContext { //最核心的 可以根据表生成类。/*** 表名为key,信息对对象为value*/

public static Map tables=new HashMap<>(); //这个表中的tableInfo也是定义的一个类大概意思就是能够记录下整张表,这里这个map就能记录整个database/*** 将po的class对象和表关联*/

public static Map poClassTableMap=new HashMap<>(); //这个表用来记录这个表后来会产生哪个类,这后面会说privateTableContext(){};

//无参构造很重要static{try{

Connection con=DBManager.getConn(); //这里就获得了和数据库的连接了

DatabaseMetaData dbmd=con.getMetaData(); //这里原视频中没有说,大概是 可以从连接中搜索获取一些元数据,%是类似所有

ResultSet tableRet=dbmd.getTables(null, "%", "%",new String[]{"TABLE"}); //TABLE那就是要查询的就是表了,但是这里出现了问题,意外的搜索出了创建表的时间

System.out.println(tableRet); //记录的那个表,捯饬了半天也没解决。 然后之后是放到了一个结果集里while(tableRet.next()){ //遍历查找出来的结果

String tableName=(String)tableRet.getObject("TABLE_NAME"); //获得表名

TableInfo ti=new TableInfo(tableName, new ArrayList(), // 然后把表名先把上说的可以记录下整个表的new出来,具体的后面再说new HashMap());

tables.put(tableName, ti); //放到记录整个数据库的那个表里

ResultSet set=dbmd.getColumns(null, "%", tableName ,"%"); //这里根据表名获取字段集,while(set.next()){

ColumnInFo ci=new ColumnInFo(set.getString("COLUMN_NAME"),set.getString("TYPE_NAME"),0);//可以看出这里获取了字段的名字和类型

ti.getColumns().put(set.getString("COLUMN_NAME"),ci); //这里是放到表映射,加载表的字段

}

ResultSet set2=dbmd.getPrimaryKeys(null, "%", tableName);while(set2.next()){   //这里加载主键

ColumnInFo ci2=(ColumnInFo)ti.getColumns().get(set2.getObject("COLUMN_NAME"));

ci2.setKeyType(1);

ti.getPriKeys().add(ci2);

}if(ti.getPriKeys().size()>0){

ti.setOnlyPrivate(ti.getPriKeys().get(0));

}

}                                  //如果这里没有懂得话,不要急,后面结合javabean

//这里就可以理解为,你在java中建立了一个表格,不是完全填好数据得表格,只是单纯得有每个数据类型在哪个表是不是主键得表示

有了这个标识以后可以在query中找出数据库要操作得是哪个,再从query中操作数据库。

}catch(Exception e) {//TODO: handle exception

}

updataJavaPOFile();                        //因为我们要再数据库中操作,程序事先是不知道你表中得每一项叫什么名字就没办法根据数据可定义好类,在用类来操作数据库

loadPOTables(); //这里我们就可以根据数据库中获取的表框架,获取表的名字,表中字段类型,生成这个数据库的java类

//然后是每次加载这个类的时候更新

}

//一下是实现方法public static voidupdataJavaPOFile(){

Map map=TableContext.tables; 这里就通过这个tables表然后用后面的一个java类实现了在项目中构造java类//TableInfo t=map.get("new_table");

for(TableInfo t:map.values()){

JavaFileUtils.createJavaPOFile(t,newMysqlTypeConventor());

}

}public static voidloadPOTables(){//Class c=Class.forName("com.cy.sorm.New_table");//poClassTableMap.put(c, TableInfo); //这里就是用反射把这个类和产生自哪个表放在了一个表里,在后面的操作有用

for(TableInfo tableInfo:tables.values()){try{

Class c=Class.forName(DBManager.getConf().getPoPackage()+

"."+StringUTils.firstChar2UpCase(tableInfo.getTname()));

poClassTableMap.put(c, tableInfo);

}catch(Exception e){

e.printStackTrace();

}

}

}//public static void main(String[] args) {//Map tables=TableContext.tables;//System.out.println(tables);//}

}

这里也是orm的核心了,从数据库中复制了一个映射表过来。但是老师说在工作中用的很少。接下来说一下字段映射和表映射吧,可以从以上的javabean包中可以看出,除了很容易理解的配置configuration,javasetget也就是在上面说的拼接我们自己产生的java类的简单结构。

privateString name;

privateString Datatype;

private int keyType;

字段就是在mysql建表时候的每一列

privateString tname;/*** key 字段名 value字段类*/

private Mapcolumns;privateColumnInFo onlyPrivate;private ListpriKeys;

这个表呢也就可以记录下整个表了 map中也就是整个表,记录下主键 很好理解这样我们只要有足够多的数据传入我们就可以在java中改了整张表,但是我们必去得做一个query类,用来我把这个数据类传入就可以在数据库中同步更改。

然后大概思路也就成型了,用户可以提供数据库 ,我自动给他生成他表中得类,他调用query中方法,对数据库进行更改。但是怎么通过我生成类对这个映射相联系,然后再映射到数据库呢,我们藏了一个map。

先说一点简单得把tables中获得得数据进行生成java类

这里实现了一个接口,可以处理不同数据库到java得翻译数据类型这里很好懂

public class MysqlTypeConventor implementsTypeConvertor{

@OverridepublicString databaseType2JavaType(String columnType) {if("varchar".equalsIgnoreCase(columnType)||"char".equalsIgnoreCase(columnType)){return "String";

}else if("int".equalsIgnoreCase(columnType)||"tinyint".equalsIgnoreCase(columnType)){return "Integer";

}else if("double".equalsIgnoreCase(columnType)){return "double";

}else if("timestamp".equalsIgnoreCase(columnType)){return "Timestamp";

}return null;

}

@OverridepublicString javaType2databaseType(String javaDataType) {//TODO Auto-generated method stub

return null;

}

}

然后我们把转换器和传进去的字段封装成一个java生成类的工具,就是上面代码提到的哪个工具类

public classJavaFileUtils {public staticJavaFieldGetSet createFieldGetSetSRC(ColumnInFo column,TypeConvertor convertor){

JavaFieldGetSet jfgs= newJavaFieldGetSet();

String javaFieldType=convertor.databaseType2JavaType(column.getDatatype());

jfgs.setFieldInfo("\tprivate "+javaFieldType+" "+column.getName());

StringBuilder getSrc=newStringBuilder();

getSrc.append("\tpublic "+javaFieldType+" get"+StringUTils.firstChar2UpCase(column.getName())+"(){\n");

getSrc.append("\t\treturn "+column.getName()+";\n");

getSrc.append("\t}\n");

jfgs.setGetInfo(getSrc.toString());

StringBuilder setSrc=newStringBuilder();

setSrc.append("\tpublic void set"+StringUTils.firstChar2UpCase(column.getName())+"(");

setSrc.append(javaFieldType+" "+column.getName()+"){\n");

setSrc.append("\t\tthis."+column.getName()+"="+column.getName()+";\n");

setSrc.append("\t}\n");

jfgs.setSetInfo(setSrc.toString());returnjfgs;

}public staticString createJavaSrc(TableInfo tableInfo,TypeConvertor convertor){

Map columns=tableInfo.getColumns();//表装入map

List javaFields=new ArrayList<>();for(ColumnInFo c:columns.values()){

javaFields.add(createFieldGetSetSRC(c, convertor));

}

StringBuilder src=newStringBuilder();

src.append("package "+DBManager.getConf().getPoPackage()+";\n\n");

src.append("import java.util.*;\n\n");

src.append("import java.sql.*;\n\n");

src.append("public class "+StringUTils.firstChar2UpCase(tableInfo.getTname())+"{\n\n");for(JavaFieldGetSet f:javaFields){

src.append(f.getFieldInfo()+";\n");

}

src.append("\n\n");for(JavaFieldGetSet f:javaFields){

src.append(f.getSetInfo());

}

src.append("\n");for(JavaFieldGetSet f:javaFields){

src.append(f.getGetInfo());

}

src.append("}\n");//System.out.println(src);

returnsrc.toString();

}public static voidcreateJavaPOFile(TableInfo tableInfo,TypeConvertor convertor){

String src=createJavaSrc(tableInfo, convertor);

BufferedWriter bw=null;

String srcPath=DBManager.getConf().getScrPath()+"\\";

String packagePath=DBManager.getConf().getPoPackage().replaceAll("\\.", "\\\\");

File f=new File(srcPath+packagePath.trim());

System.out.println(f);if(!f.exists()){

f.mkdirs();

}try{

bw=new BufferedWriter(new FileWriter(f.getAbsoluteFile()+"\\"+StringUTils.firstChar2UpCase(tableInfo.getTname())+".java"));

bw.write(src);

}catch(Exception e) {//TODO: handle exception

}finally{if(bw!=null){try{

bw.close();

}catch(IOException e) {//TODO Auto-generated catch block

e.printStackTrace();

}

}

}

}public static voidmain(String[] args) {//ColumnInFo ci=new ColumnInFo("username","varchar",0);//JavaFieldGetSet f=createFieldGetSetSRC(ci, new MysqlTypeConventor());//System.out.println(f);

Map map=TableContext.tables;//TableInfo t=map.get("new_table");

for(TableInfo t:map.values()){

JavaFileUtils.createJavaPOFile(t,newMysqlTypeConventor());

}

}

}

第一个方法就是把从字段类中获取的名字类型转换成一个String,这里没有表名所以拼接了的是getset,便于拼接是用stringbuilder拼接,

第二个方法有了表映射以后我们就可以拼接表名,然后从表映射中获取有那些字段,再拼接上字段的setget方法,最后呢就是通过配置文件中获得我们项目路径,然后建立java类

这里注意把“ .  ”换成"  \\ "的操作java中一个变两个

然后就是核心的实现query了,这里本来是想实现接口 然后方便不同的数据库连接不同的,然后后来因为方法重用的很多就改成了抽象类。

public abstract classQuery {publicList executeQueryTemplate(String sql, Object[] params,Class clazz,CallBack back){

Connection conn=DBManager.getConn();

PreparedStatement ps=null;

ResultSet rs=null;try{

ps=conn.prepareStatement(sql);

JDBCUtils.handleParams(ps, params); //这个是一个回调模板,通过callback,callback是一个简单的接口,很常用,实现的作用就是,我实现一个接口,接口中是我要实现的功能

rs=ps.executeQuery(); //然后我可以接着写下去,这成为了一个模板,只是中间这个方法的实现不一样,当我要使用这个模板的时候调用这个方法,然后匿名内部类实现backreturnback.doExecute(conn, ps, rs); //中的方法但是匿名内部类又涉及到用外部变量 final的问题,我用过的也很少希望在之后的学习有更深的理解。

}catch(Exception e) {//TODO Auto-generated catch block

e.printStackTrace();return null;

}finally{

DBManager.close(rs,ps,conn);

}

}public intexcuteDML(String sql,Object[] params){

Connection conn=DBManager.getConn();int count=0; //这是个执行sql语句的封装,object参数是传入要操作的数因为用的是preparestatement

PreparedStatement ps=null;try{

ps=conn.prepareStatement(sql);

JDBCUtils.handleParams(ps, params);

count=ps.executeUpdate();

}catch(SQLException e) {//TODO Auto-generated catch block

e.printStackTrace();

}finally{

DBManager.close(ps,conn);

}returncount;

}public voidinsert(Object obj){

Class c=obj.getClass();

List params=new ArrayList<>();//储存sql的参数对象

int countNotNull=0;//计算不为空的属性值

TableInfo tableInfo =TableContext.poClassTableMap.get(c);//这就是map记录了class和映射table的关系,然后返回映射表就可以获得表名拼接sql语句了

StringBuilder sql=new StringBuilder("insert into "+tableInfo.getTname()+" (");

Field[] fs=c.getDeclaredFields(); //利用反射将对象的的名字和操作值然后拼接字符串for(Field f:fs){

String fieldName=f.getName();

Object fieldValue=ReflectUtils.invokeGet(fieldName, obj); //这里是利用反射 获取get方法 get到我们设置的值 然后拼接到sql语句中,

//这样一来最开始的思路也解决了if(fieldValue!=null){

countNotNull++;

sql.append(fieldName+",");

params.add(fieldValue);

}

}

sql.setCharAt(sql.length()-1, ')');

sql.append(" value (");for(int i=0;i

sql.append("?,");

}

sql.setCharAt(sql.length()-1, ')');

excuteDML(sql.toString(), params.toArray());

}public voiddelete(Class clazz,Object id){/*** table.class->delete from New_table where id=2;*/TableInfo tableInfo=TableContext.poClassTableMap.get(clazz);

ColumnInFo onlyPriKey=tableInfo.getOnlyPrivate(); //mysql删除宇语句中我们只要获取表主键的编号就可以删除了

String sql="delete from "+tableInfo.getTname()+" where "+onlyPriKey.getName()+"=? ";

excuteDML(sql,newObject[]{id});

}public voiddelete(Object obj){

Class c=obj.getClass();

TableInfo tableInfo=TableContext.poClassTableMap.get(c); //同理反射获得get方法 然后用第一个delete方法删除

ColumnInFo onlyPriKey=tableInfo.getOnlyPrivate();

Object priKeyValue=ReflectUtils.invokeGet(onlyPriKey.getName(), obj);

delete(c,priKeyValue);

}public intupdate(Object obj,String[] fieldNames){//obj("uname",pwd)--->update 表名 set uname=?,pwd=? where id =?

Class c=obj.getClass();

List params=new ArrayList<>();

TableInfo tableInfo=TableContext.poClassTableMap.get(c);

ColumnInFo priKey=tableInfo.getOnlyPrivate();

StringBuilder sql=new StringBuilder("update "+tableInfo.getTname()+" set ");for(String fname:fieldNames){

Object fvalue=ReflectUtils.invokeGet(fname, obj);

params.add(fvalue);

sql.append(fname+"=?,");

//和insert很像加了遍历要更新的列 然后list记录问好处要更新的值 反射获取值

}

sql.setCharAt(sql.length()-1, ' ');

sql.append(" where ");

sql.append(priKey.getName()+"=? ");

params.add(ReflectUtils.invokeGet(priKey.getName(), obj));returnexcuteDML(sql.toString(), params.toArray());

}//返回多行

publicList queryRows(String sql, Class clazz, Object[] params){return executeQueryTemplate(sql,params,clazz,newCallBack(){ ///接上回调方法的实现publicList doExecute(Connection conn, PreparedStatement ps, ResultSet rs) {

List list=null;try{

ResultSetMetaData metaData=rs.getMetaData();while(rs.next()){if(list==null){

list=newArrayList();

}

Object rowObj=clazz.newInstance();//调用javabeen的无参构造器//select username,pwd,age from user where id>? and age<18

for(int i=0;i

String columnName=metaData.getColumnLabel(i+1);

Object columnValue=rs.getObject(i+1);

ReflectUtils.invokeSet(rowObj, columnName, columnValue);

}

list.add(rowObj);

}

}catch(Exception e) {//TODO Auto-generated catch block

e.printStackTrace();return null;

}returnlist;

}

});

}//返回一行

publicObject queryUniqueRows(String sql, Class clazz,Object[] params){

List list=queryRows(sql, clazz, params);return (list==null&&list.size()>0)? null:list.get(0);

}publicObject queryValue(String sql,Object[] params){

Connection conn=DBManager.getConn();

Object value=null;

PreparedStatement ps=null;

ResultSet rs=null;try{

ps=conn.prepareStatement(sql);

JDBCUtils.handleParams(ps, params);

rs=ps.executeQuery();//rs中为查到的行数

while(rs.next()){

value=rs.getObject(1); //rs结果集中不是以0开头的

}

}catch(Exception e) {//TODO Auto-generated catch block

e.printStackTrace();

}returnvalue;

}publicNumber queryNumber(String sql,Object[] params){return(Number)queryValue(sql, params);

}public abstract Object queryPagenate(int PageNum,int size); //抽象方法,不同子类不同实现

}

之后就可以继承query实现不同的数据库的不同方法然后重写和修改实现多态

然后实现了query后就可以工厂设计模式,用factory单例,然后生产query

public classQueryFactory {private static QueryFactory factory=newQueryFactory();private staticClass c;static{try{

c=Class.forName(DBManager.getConf().getQueryClass());

}catch(ClassNotFoundException e) {//TODO Auto-generated catch block

e.printStackTrace();

}

}privateQueryFactory() {//TODO Auto-generated constructor stub

}publicQuery creatFactory(){try{return(Query) c.newInstance();

}catch(InstantiationException e) {//TODO Auto-generated catch block

e.printStackTrace();return null;

}catch(IllegalAccessException e) {//TODO Auto-generated catch block

e.printStackTrace();return null;

}

}

}

高淇java_关于高淇JAVA中SORM总结学习笔记详细个人解释相关推荐

  1. java中volatile关键字---学习笔记

    volatile关键字的作用 在java内存模型中,线程之间共享堆内存(对应主内存),但又各自拥有自己的本地内存--栈内存,线程的栈内存中缓存有共享变量的副本,但如果是被volatile修饰的变量,线 ...

  2. Java中expecial,RxJava 学习笔记 (一)

    作者: 一字马胡 转载标志 [2017-12-13] 更新日志 日期 更新内容 备注 2017-12-13 RxJava学习笔记系列 系列笔记 (一) 2017-12-15 增加系列笔记(二) 201 ...

  3. java中servlet filter_lua学习笔记(二)仿java servlet中Filter功能

    2)代码 Filter.lua--[[ 过滤器接口 ]]-- local FilterChain = require("FilterChain") local Filter = { ...

  4. JAVA中JVM的重排序详细介绍(写得很明白)

    刚刚在研究volatile变量的时候,涉及到重排序的概念,于是发现了这篇很好的文章,写得很简短很明白.所以转载一下. 原文地址:JAVA中JVM的重排序详细介绍 原文贴出来: 重排序通常是编译器或运行 ...

  5. java中转义字符的学习---(多看几遍就会了系列)

    java中转义字符的学习-(多看几遍就会了系列) java中常见的转义字符– 符号 含义 \n 换行 \t 制表符 \r 回车 \b 退格 代码演示: public class a {public s ...

  6. JAVA基础与高级学习笔记

    JAVA基础与高级学习笔记 /记录java基础与高级,除了较简单的内容,没有必要记录的没有记录外,其余的都记录了/ java初学者看这一篇就够了,全文 6万+ 字. JAVA基础 java会出现内存溢 ...

  7. 《Java编程思想》学习笔记【一对象导论】

    重头学习Java,大一没怎么学,大二上课也没听.(流下不学无术的眼泪) 所有编程语言都提供抽象机制,我们所能解决的问题的复杂性直接取决于抽象的类型和质量. 汇编语言是对底层机器的轻微抽象," ...

  8. 零基础学习Java开发,这些学习笔记送给你

    因为Java具备很多特点,并且在企业中被广泛应用为此很多小伙伴选择学习Java基础开发,但是零基础学习Java技术开发需要我们制定Java学习路线图对于我们之后的学习会非常有帮助. 零基础学习Java ...

  9. Java 8 函数式编程学习笔记

    Java 8 函数式编程学习笔记 @(JAVASE)[java8, 函数式编程, lambda] Java 8 函数式编程学习笔记 参考内容 Java 8中重要的函数接口 扩展函数接口 常用的流操作 ...

  10. 【Java】函数式编程学习笔记——Stream流

    学习视频:https://www.bilibili.com/video/BV1Gh41187uR?p=1 (1)[Java]函数式编程学习笔记--Lambda表达式 (2)[Java]函数式编程学习笔 ...

最新文章

  1. 某程序员大牛放弃130万年薪,离开北京回老家事业单位!网友:太可惜!何不再忍两年?...
  2. 图像识别中距离变换的原理及作用详解,并附用OpenCV中的distanceTransform实现距离变换的代码
  3. 30分钟了解C 11新特性
  4. AVL Insertion(浙大pta)
  5. 从零开始搭二维激光SLAM --- Karto的后端优化与回环检测功能对比测试与分析
  6. windows下phpstorm的高效使用
  7. RSync实现文件同步备份配置详解
  8. 利用 Python分析北京雾霾天,发现这么秘密
  9. #边学边记 必修4 高项:对事的管理 第六章 项目质量管理 质量管理基础
  10. 基于VUE技术的超市购物系统设计答辩PPT模板
  11. Web功能测试(邮箱,手机号,验证码,身份证号测试用例)
  12. UniCode编码表,过滤不可见特殊字符
  13. elasticsearch插件之cerebro的安装
  14. 【MVC 4】4.MVC 基本工具(Visual Studio 的单元测试、使用Moq)
  15. android手机红外代码HAL,Android 红外遥控器适配
  16. String.prototype.matchAll 正则一些常用方法
  17. 程序员哥哥,你有一枚女朋友请查收。
  18. 机器视觉OpenCV库基础教程(一)
  19. 基于WIN32 API界面编程实现的华容道小游戏
  20. 成为你自己的新贵:如何开始低代码手机开发

热门文章

  1. css-自定义字体(LED)
  2. 项目实战-电商(网上书城)
  3. 区间多目标优化算法IP-MOEA
  4. jvm执行java大致采用过程_java练习题
  5. Spring框架浅谈及Spring框架学习小结
  6. 前端最新2022面试题(JS)
  7. R实现KMeans聚类算法教程
  8. Photoshop CS6版本安装及破解教程
  9. java修改yml文件
  10. 最小二乘法曲线拟合(代码注释)