Java实现简单KV数据库
用Java实现一个简单的KV数据库
开发思路:
用map存储数据,再用一个List记录操作日志,开一个新线程将List中的操作写入日志文件中,再开一个线程用于网络IO服务接收客户端的命令,再启动时检查日志,如果有数据就读入map中
关于redis:
- 存储结构:
- redis:
redis的数据保存其实比较复杂,使用一个哈希表保存所有键值对,一个哈希表就是一个数组,数组的每一个元素是一个哈希桶,哈希桶中保存的是key和value的指针目录,再通过,指针去找对应的key和value,当然对于value是List等数据结构还用到跳表,双向列表,压缩列表,整数数组等数据结构 - SimpleKVDB:
只用了Java的HashMap(偷懒~)
- redis:
- 线程:
- redis:
redis虽然成为单线程,但是redis的网络IO和键值对读写是由一个线程,但是另外的持久化,异步删除,集群数据同步等,都是额外线程 - SimpleKVDB:
数据读写网络IO一个线程,持久化一个线程(集群同步本来想做但是后来没有写,也是新开一条线程)
- redis:
- 网络IO:
- redis:
单线程多路复用高性能IO模式 - SimpleKVDB:
直接用Java标准库NIO,多路复用IO模式
- redis:
- 持久化:
- redis:
AOF操作日志,RDB快照,AOF用来记录每一次的操作(增删改)可以实时同步也可以每隔一个时间同步文件中,RDB全量数据快照但是需要开一条子进程开销比较大,redis4.0以后使用一种新的模式,RDB每隔一段时间全量快照内存数据,AOF记录每个RDB之间的操作记录,当下一次全量RDB以后清空AOF再重新记录操作日志 - SimpleKVDB
只记录AOF操作日志,开一个新线程,有新的操作就写入(后来我发现可以使用mmap内存映射的方法,这样更快效率更高)
- redis:
- 主从数据一致
- redis:
选一台主服务器用于写入,从服务器用于读取,主服务器有数据写入就同步从服务器,哨兵机制,用于监控所有服务器,如果主服务器崩溃,就选择一台从服务器作为主服务器(会根据是否下线,网络速度,读写速度等选择主服务器),然后通知其他从服务器连接到新的主服务器 - SimpleKVDB:
没写,设想:本来是想写一个配置文件,写入主服务器IP,其他从服务器IP,开一个线程在服务端中写一个客户端当作主服务器,读取配置文件,只有主服务器才能开这个线程,其他从服务器还是开启服务,用来接收主服务器的数据,同步从数据库的内存和操作日志里
- redis:
操作展示:
客户端:
服务端:
日志文件:
目录结构:
- SimpleKVDB
- SimpleKVDBClient(客户端)
- SimpleKVDBClient.java(客户端)
- SimpleKVDBService(服务端)
- AofAnnotation.java (注解)
- AofInterface.java(接口)
- DynamicAgent.java(动态代理)
- SimpleKVDBService.java(服务端)
- SimpleKVDBClient(客户端)
SimpleKVDBClient.java(客户端):
package SimpleKVDB.SimpleKVDBClient;import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.time.LocalDateTime;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class SimpleKVDBClient {public static void main(String[] args) throws Exception {SocketChannel socketChannel = SocketChannel.open();socketChannel.configureBlocking(false);Selector selector = Selector.open();socketChannel.register(selector, SelectionKey.OP_CONNECT);socketChannel.connect(new InetSocketAddress("127.0.0.1",5555));while (true){selector.select();//阻塞 等待事件发生Set<SelectionKey> selectionKeys = selector.selectedKeys();selectionKeys.forEach(key ->{try {if (key.isConnectable()){SocketChannel channel = (SocketChannel) key.channel();if (channel.isConnectionPending()){//是否正在连接channel.finishConnect(); //结束正在连接ByteBuffer writeBuffer = ByteBuffer.allocate(1024);writeBuffer.put((LocalDateTime.now() + " 连接成功").getBytes());writeBuffer.flip();channel.write(writeBuffer);//将buffer写入channelExecutorService service = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory());service.submit(()->{//线程,从键盘读入数据try {while (true){writeBuffer.clear();//清空bufferInputStreamReader input = new InputStreamReader(System.in);BufferedReader bufferedReader = new BufferedReader(input);String senderMessage = bufferedReader.readLine();writeBuffer.put(senderMessage.getBytes());writeBuffer.flip();channel.write(writeBuffer);}}catch (Exception e){e.printStackTrace();}});}channel.register(selector,SelectionKey.OP_READ);//注册事件}else if (key.isReadable()){//channel 有信息的输入SocketChannel channel = (SocketChannel) key.channel();//哪个channel 触发了 readByteBuffer readBuffer = ByteBuffer.allocate(1024);int count = channel.read(readBuffer);//server发来的if (count > 0){String receiveMessage = new String(readBuffer.array(),0,count);System.out.println("响应结果:"+receiveMessage);}}}catch (Exception e){e.printStackTrace();}finally {selectionKeys.clear();//移除已经发生的事件}});}}
}
AofAnnotation.java(注解):
package SimpleKVDB.SimpleKVDBService;import java.lang.annotation.*;// ----------- 自定义的注解,用于区分是什么操作(其实也可以不用,直接获取方法名区分也一样) -----------
// 自定义的注解
@Retention(RetentionPolicy.RUNTIME)//注解会在class中存在,运行时可通过反射获取
@Target(ElementType.METHOD)//目标是方法
@Documented
//文档生成时,该注解将被包含在javadoc中,可去掉
@interface AofAnnotation {String name() default "";
}
AofInterface.java(动态代理接口):
package SimpleKVDB.SimpleKVDBService;// ----------- 动态代理需要的接口,主要想实现切面效果在每一个操作后面加一个日志 -----------
// 动态代理需要的接口
// 只需要给增删改上加操作日志,保证数据一致性
interface AofInterface {
// @AofAnnotation(name="clear")
// int hashClear();@AofAnnotation(name="set")Object hashSet(String key, Object value);@AofAnnotation(name="remove")Object hashRemove(String key);
}
DynamicAgent.java(动态代理):
package SimpleKVDB.SimpleKVDBService;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;// ----------- 动态代理(实现切面效果的逻辑代码) -----------
// 动态代理
public class DynamicAgent<T> implements InvocationHandler {// 接口实现类实例,如果不使用泛型,这里可以直接用ObjectT rent;void setObject(T obj){this.rent = obj;}// aof内存List<String> listData;public void setListData(List<String> list){this.listData = list;}// 生成代码类public Object getProxy(){// 第一个参数是代理类的类加载器,第二个参数是代理类要实现的接口,第三个参数是处理接口方法的程序// 这里代理类是自己,所以直接this,getClass().getClassLoader()是获取加载器// getClass().getInterfaces() 是获取实现类的接口// 因为invoke()就是执行方法,所以第三个参数也是本身thisreturn Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this);}// 处理代理实例,并返回执行结果public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 动态代理本质就是通过反射实现,这里就是执行这个对象的方法Object result = method.invoke(rent, args);// 获取注解AofAnnotation say = method.getAnnotation(AofAnnotation.class);// 注解的name内容String name = say.name();System.out.println("name::"+name);// aof日志写入aofSetLog(name, args);return result;}// 给aof开辟一个内存public void aofSetLog(String name, Object[] args){Map<String, Object> dataMap = new HashMap<String, Object>();// 日志格式String aofData = "*|";if("set".equals(name)){dataMap.put(args[0].toString(), args[1]);aofData = aofData + name+"|"+args[0].toString()+"|"+dataMap.get(args[0].toString());}if("remove".equals(name)){if(null != dataMap && dataMap.size()>0){dataMap.remove(args[0].toString());}aofData = aofData + name+"|"+args[0].toString()+"|";}// 日志内存listData.add(aofData);
// System.out.println("listData:::"+listData);}// 返回日志数据public List<String> getAofDatas(){return listData;}
}
SimpleKVDBService.java(服务端):
package SimpleKVDB.SimpleKVDBService;import java.io.*;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;// ----------- KV数据库的服务端实现 -----------
public class SimpleKVDBService implements AofInterface {// 全局存储Map<String, Object> globalMap;public void setGlobalMap(Map<String, Object> map){this.globalMap = map;}// 动态代理对象AofInterface dl;public void setAofInterface(AofInterface i){this.dl = i;}// 写入修改操作public Object hashSet(String key, Object value){return globalMap.put(key, value);}// 读取操作public Object hashGet(String key){return globalMap.get(key);}// 删除操作public Object hashRemove(String key){return globalMap.remove(key);}// 获取长度操作public int hashSize(){return globalMap.size();}// 是否为空操作操作public boolean hashIsEmpty(){return globalMap.isEmpty();}// aof日志List<String> aofList;// 引用全局aof日志变量,用来存储aof操作日志public void setAofList(List<String> list){this.aofList = list;}// 创建aof文件public File createAofFile(){final String ROOT = '.' + File.separator;File newFolder = new File(ROOT+"simpleKVDB");if(newFolder.exists() && newFolder.isDirectory()){System.out.println("文件夹已经存在");}else {boolean isFolder = newFolder.mkdir();if(!isFolder){System.out.println("文件夹创建失败");}}// 创建一个文件File newFile = new File(newFolder.getPath(),"aofDatas.aof");if(newFile.exists() && newFile.isFile()){System.out.println("文件已经存在");}boolean isFile;try {isFile = newFile.createNewFile();if(!isFile){System.out.println("文件创建失败");}} catch (IOException e) {e.printStackTrace();}return newFile;}// 开一个线程,写aof写入文件public void aofFileThread() {new Thread(()->{System.out.println("aof日志写入线程:"+Thread.currentThread().getName());while (true){this.setAofFile(this.aofList);}}).start();}// aof写入日志文件逻辑,将aof操作日志写入文件中,持久化public void setAofFile(List<String> aofList){if(null != aofList && aofList.size()>0){// 休眠一秒再写入,不频繁使用IO写入try{Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 为什么文件夹和文件检测放这里每次都要检测是防止文件被误删除File newFile = this.createAofFile();// 使用try的话自动回收/关闭资源,会自动调用close方法,不需要手动关闭// 将需要关闭的资源放在try(xxx; yyy;zzz;)// 流的关闭是有顺序的,自己手动关闭很繁琐,自动关闭大大降低了难度,非常方便try(// 创建一个FileOutputStream,Output是写入,文件的byte数据传输流// FileOutputStream 第二参数是否追加FileOutputStream fos = new FileOutputStream(newFile, true);// FileOutputStream是通过byte字节流的,OutputStreamWriter是将字节流包装成想要的字符集的字符流写入OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);// 使用PrintWriter,可以方便的写入一行字符,第二个参数自动清空缓冲区PrintWriter pw = new PrintWriter(osw, true);){// 一边遍历一边删除aof操作日志Iterator<String> iterator = aofList.iterator();// 判断是否还有下一个元素while (iterator.hasNext()){// 获取下一个元素String str = iterator.next();// println是每段换行写入,print是不换行写入// 写入其实是一层一层走的,先是写入内容进入PrintWriter中,然后再OutputStreamWriter根据编码转成字节byte,然后再是FileOutputStream字节流写入文件pw.println(str);// 因为是引用传递,所以直接删除元素iterator.remove();}// 清空缓冲区,因为数据是先进入缓冲区再写入文件,需要在关闭前将缓冲区的数据全部写入文件才算完成,这样才能关闭整个流,缓存区的作用是,一个字节一个字节写入太费事儿,所以会等到一定量的字节再一起写入,所以会出现一种可能就是缓存区还有少量的字节因为没达到量没有写入,所以需要清空一下,将里面所有剩余的字节都写入// PrintWriter中设置了自动清空缓冲区
// pw.flush();}catch (IOException e){e.printStackTrace();}}}// socket服务,与客户端通讯public void socketServer(AofInterface dl){try {//创建ServerSocketChannel,-->> ServerSocket// 打开通道ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();// 打开 SocketChannel 并连接到端口InetSocketAddress inetSocketAddress = new InetSocketAddress(5555);serverSocketChannel.socket().bind(inetSocketAddress);// 配置通道为非阻塞模式serverSocketChannel.configureBlocking(false);//开启selector,并注册accept事件// 获取一个选择器实例Selector selector = Selector.open();// 将套接字通过到注册到选择器serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);while (true){// 阻塞,等待事件发生selector.select();// 返回已发生的注册事件Set<SelectionKey> selectionKeys = selector.selectedKeys();// 判断事件类型,进行相应操作selectionKeys.forEach(key ->{final SocketChannel client;try {// 根据key获得channelif (key.isAcceptable()){// 之所以转换ServerSocketChannel,因为前面注册的就是这个类ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();// 新的channel 和客户端建立了通道client = serverChannel.accept();// 非阻塞client.configureBlocking(false);// 将新的channel和selector,绑定client.register(selector,SelectionKey.OP_READ);//是否有数据可读}else if (key.isReadable()){client = (SocketChannel) key.channel();ByteBuffer readBuffer = ByteBuffer.allocate(1024);int count = client.read(readBuffer);if (count>0){readBuffer.flip();Charset charset = StandardCharsets.UTF_8;String receiveMassage = String.valueOf(charset.decode(readBuffer).array());// 显示哪个client发消息System.out.println(client +": "+receiveMassage);// 向客户端返回的信息String serverStr = "";// 根据客户端不同的命令,执行不同的方法if(Objects.equals(receiveMassage.split(" ")[0], "set")){dl.hashSet(receiveMassage.split(" ")[1], receiveMassage.split(" ")[2]);serverStr = "set OK";}if(Objects.equals(receiveMassage.split(" ")[0], "remove")){dl.hashRemove(receiveMassage.split(" ")[1]);serverStr = "remove OK";}if(Objects.equals(receiveMassage.split(" ")[0], "get")){serverStr = this.hashGet(receiveMassage.split(" ")[1]).toString();}if(Objects.equals(receiveMassage.split(" ")[0], "isempty")){serverStr = String.valueOf(this.hashIsEmpty());}if(Objects.equals(receiveMassage.split(" ")[0], "size")){serverStr = String.valueOf(this.hashSize());}if(receiveMassage.contains("连接成功")){serverStr = receiveMassage;}SocketChannel channel = (SocketChannel) key.channel();;ByteBuffer writeBuffer = ByteBuffer.allocate(1024);//返回客户端数据writeBuffer.put((serverStr).getBytes());writeBuffer.flip();channel.write(writeBuffer);}}// 处理完事件一定要移除//selectionKeys.clear();}catch (Exception e){e.printStackTrace();}finally {// 处理完事件一定要移除selectionKeys.clear();}});}}catch (IOException e){e.printStackTrace();}}// socket服务线程public void socketThread(){new Thread(()->{System.out.println("socketServer线程:"+Thread.currentThread().getName());this.socketServer(this.dl);}).start();}// 启动时检查持久化aof日志文件public void setAofToMap(){System.out.println("开始从AOF中恢复数据!");File readFile = this.createAofFile();// 使用try的话自动回收/关闭资源,会自动调用close方法,不需要手动关闭// 将需要关闭的资源放在try(xxx; yyy;zzz;)// 流的关闭是有顺序的,自己手动关闭很繁琐,自动关闭大大降低了难度,非常方便try(// 创建一个FileInputStream,Input是写入,文件的byte数据传输流FileInputStream fis = new FileInputStream(readFile);// FileInputStream是通过byte字节流的,InputStreamReader是将字节流包装成想要的字符集的字符流写入InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);// 使用BufferedReader,增加缓存,可以方便的写入一行字符BufferedReader reader = new BufferedReader(isr);){// reader.lines().map(String::trim).forEach(System.out::println); 这是一种lambda写法,效果和下面一样String str;// 为什么要放在while的条件里面赋值呢?是因为readLine()一行一行读取如果到文件结尾了会返回一个null,如果放在while的代码体里赋值,就需要多一步null的判断// 读取和写入正好相反,是先从文件读取内容到缓存区,然后从缓存区读出来while ((str = reader.readLine()) != null){String methodStr = str.split("\\|")[1];String keyStr = str.split("\\|")[2];// 根据不同指令操作不同方法if("set".equals(methodStr)){Object valueStr = str.split("\\|")[3];this.hashSet(keyStr, valueStr);}if("remove".equals(methodStr)){this.hashRemove(keyStr);}}System.out.println("AOF中恢复数据结束!");} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {System.out.println("主线程: "+Thread.currentThread().getName());// 全局内存Map<String, Object> maps = new HashMap<>();// 全局aof日志内存List<String> lists = new ArrayList<>();// 服务主体类SimpleKVDBService sKvService = new SimpleKVDBService();// 全局存储内存sKvService.setGlobalMap(maps);// 动态代理,主要是用于给操作添加日志DynamicAgent<AofInterface> nd = new DynamicAgent<AofInterface>();// 全局aof内存nd.setListData(lists);nd.setObject(sKvService);// 获取代理对象AofInterface dl = (AofInterface) nd.getProxy();// 启动时检查aof文件是否存在sKvService.setAofToMap();// 服务主体获取已经有日志信息的aof日志信息sKvService.setAofList(nd.getAofDatas());// 引用动态代理sKvService.setAofInterface(dl);// 子线程,写aof写入文件sKvService.aofFileThread();// 子线程,socket服务线程sKvService.socketThread(); System.out.println(sKvService.globalMap);
System.out.println("22222:"+nd.getAofDatas());
System.out.println("list:"+sKvService.aofList);
System.out.println("333333:"+sKvService.globalMap);}
}
相关文章:
Java实现简单KV数据库
用Java实现一个简单的KV数据库 开发思路: 用map存储数据,再用一个List记录操作日志,开一个新线程将List中的操作写入日志文件中,再开一个线程用于网络IO服务接收客户端的命令,再启动时检查日志,如果有数据就…...
【Spark分布式内存计算框架——Spark Streaming】5. DStream(上)
3. DStream SparkStreaming模块将流式数据封装的数据结构:DStream(Discretized Stream,离散化数据流,连续不断的数据流),代表持续性的数据流和经过各种Spark算子操作后的结果数据流。 3.1 DStream 是什么…...
Spring系列-9 Async注解使用与原理
背景: 本文作为Spring系列的第九篇,介绍Async注解的使用、注意事项和实现原理,原理部分会结合Spring框架代码进行。 本文可以和Spring系列-8 AOP原理进行比较阅读 1.使用方式 Async一般注解在方法上,用于实现方法的异步…...
Python自动化测试实战篇(6)用PO分层模式及思想,优化unittest+ddt+yaml+request登录接口自动化测试
这些是之前的文章,里面有一些基础的知识点在前面由于前面已经有写过,所以这一篇就不再详细对之前的内容进行描述 Python自动化测试实战篇(1)读取xlsx中账户密码,unittest框架实现通过requests接口post登录网站请求&…...
Linux 进程:父子进程
目录一、了解子进程二、创建子进程1.创建子进程2.区分父子进程三、理解子进程四、创建子进程的意义进程就是运行中的应用程序,如果一个程序较为庞大,我们可以给这个程序创建多个进程,每个进程负责一部分代码的运行。 A进程如果创建了B进程&am…...
Unity 之 实现读取代码写进Word文档功能实现 -- 软著脚本生成工具
Unity 之 实现读取代码写进Word文档功能前言一,实现步骤1.1 逻辑梳理1.2 用到工具二,实现读写文件2.1 读取目录相关2.2 读写文件三,编辑器拓展3.1 编辑器拓展介绍3.2 实现界面可视化四,源码分享4.1 工具目录4.2 完整代码前言 之所…...
Typora图床配置:Typora + PicGo + 阿里云OSS
文章目录一、前景提要二、相关链接三、搭建步骤1. 购买阿里云对象存储OSS2. 对象存储OSS:创建Bucket3. 阿里云:添加OSS访问用户及权限4. 安装Typora5. 配置PicGo方法一:使用PicGo-Core (Command line)方法二:使用PicGo(app)6. 最后…...
二进制搭建以太坊2.0节点-2023最新详细版文档
文章目录 一、配置 JWT 认证二、部署执行节点geth2.1 下载geth二进制文件2.2 geth节点启动三、部署共识节点Prysm3.1 下载Prysm脚本3.2 Prysm容器生成四、检查节点是否同步完成4.1 检查geth执行节点4.2 检查prysm共识节点4.3 geth常用命令五、节点同步详细说明5.1 启动时日志5.…...
如何简化跨网络安全域的文件发送流程,大幅降低IT人员工作量?
为什么要做安全域的隔离? 随着企业数字化转型的逐步深入,企业投入了大量资源进行信息系统建设,信息化程度日益提升。在这一过程中,企业也越来越重视核心数据资产的保护,数据资产的安全防护成为企业面临的重大挑战。 …...
带你深入了解c语言指针后续
前言 🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨ 🐻推荐专栏: 🍔🍟🌯 c语言进阶 🔑个人信条: 🌵知行合一 🍉本篇简介:>:介绍c语言中有关指针更深层的知识. 金句分享: ✨在该…...
借助Intune无感知开启Bitlocker
希望使用 Intune 部署 BitLocker,但不知道从哪里开始?这是人们最开始使用 Intune 时最常见的问题之一。在本博客中,你将了解有关使用 Intune 管理 BitLocker 的所有信息,包括建议的设置、BitLocker CSP 在客户端上的工作方式&…...
零基础该如何转行Python工程师?学习路线是什么?
最近1年的主要学习时间,都投资到了 python 数据分析和数据挖掘上面来了,虽然经验并不是十分丰富,但希望也能把自己的经验分享下,最近也好多朋友给我留言,和我聊天,问我python该如何学习,才能少走…...
Go项目(商品微服务-1)
文章目录简介建表protohandler商品小结简介 商品微服务主要在于表的设计,建哪些表?表之间的关系是怎样的? 主要代码就是 CURD表和字段的设计是一个比较有挑战性的工作,比较难说清楚,也需要经验的积累,这里…...
机器学习——集成学习
引言 集成学习:让机器学习效果更好,单个不行,群殴走起。 分类 1. Bagging:训练多个分类器取平均(m代表树的个数)。 2.Boosting(提升算法):从弱学习器开始加,通过加权来进行训练。…...
VS编译系统 实用调试技巧
目录什么是bug?调试是什么?有多重要?debug和release的介绍windows环境调试介绍、一些调试实例如何写出(易于调试)的代码编程常见的错误什么是bug?其实bug在英文翻译中有表示臭虫的含义,因为第一次被发现的导致计算机…...
【华为OD机试模拟题】用 C++ 实现 - GPU 调度(2023.Q1)
最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明GPU 调度题目输入输出示例一输入输出说明示例二输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。...
腾讯前端必会react面试题合集
React-Router的路由有几种模式? React-Router 支持使用 hash(对应 HashRouter)和 browser(对应 BrowserRouter) 两种路由规则, react-router-dom 提供了 BrowserRouter 和 HashRouter 两个组件来实现应用的…...
Linux搭建SVN服务器,并内网穿透实现公网远程访问
文章目录1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改authz文件3. 启动svn服务4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射本地端口5. 测试公网访问6. 配置固定公网TCP端口地址6.1 保留一个固定的公网TCP端口地址6.2 配置…...
C++STL之list的模拟实现
目录 一.list准备 二. iterator迭代器 1._list_iterator 2.begin()、end() 3.const_begin()、const_end() 4.!&& 5. && -- 6.operator* 7.operator-> 三.Modify(修改) 1.insert() 2.erase() 3.push_back() && push_front() 4.pop_bac…...
为什么硬件性能监控很重要
当今的混合网络环境平衡了分布式网络和现代技术的实施。但它们并不缺少一个核心组件:服务器。保持网络正常运行时间归结为监控和管理导致网络停机的因素。极有可能导致性能异常的此类因素之一是硬件。使用硬件监控器监控网络硬件已成为一项关键需求。 硬件监视器是…...
HTTP缓存
HTTP缓存HTTP缓存引发的一个问题HTTP缓存的作用HTTP缓存的分类强制缓存协商缓存(解决强缓存下资源不更新问题)缓存策略HTTP缓存引发的一个问题 有一次在开发移动端H5项目,UI提了几个UI问题,经过样式调试,android上没有…...
SPI设备树处理过程
SPI设备树处理过程 文章目录SPI设备树处理过程参考资料:一、 spi_device结构体二、 SPI设备树格式2.1 SPI Master2.2 SPI Device2.3 设备树示例三、设备树实例3.1 使用GPIO模拟的SPI控制器3.2 IMX6ULL SPI控制器四、 设备树处理过程致谢参考资料: 内核头…...
数据有哪些重要的作用?
我们正处在科技高速发展的时代,如今互联网已经与我们的生活息息相关,我们每天在互联网产生大量的数据,这些数据散落在网络中看似没有怎么作用,但是这些数据经过系统的处理整合起来确实非常有价值的。 一、 发展大数据技术可以提高…...
spring面试题总结
1、spring是什么? spring是一个轻量级IOC和AOP容器框架,是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用的开发,开发者只需要关注业务需求即可: core container 容器组件 spring context,…...
使用MUI与H5+构建移动端app
前言 通过mui构建APP 效果图: <!DOCTYPE html> <html> <head><meta charset...
第17篇:Java变量总结
目录 1.变量的概念 1.1 变量来源 1.2 计算机中的变量 1.3 变量如何在内存中存储 2.Java变量...
使用51单片机的GPIO输出占空比可调节的PWM波
一、前言 在一些单片机或微控制器中,通用GPIO可以被配置为产生PWM信号。PWM即脉冲宽度调制,是一种用于模拟输出的技术。它可以通过改变输出信号的脉冲宽度来控制电路中的电平,从而实现对电路的控制。 二、什么是PWM波? PWM波&a…...
从产品经理的角度如何提升项目的交付质量?
提高交付质量 ,对于每个IT公司都是永恒的话题。 交付质量其实包含2重意义, 一是交付的高质量(客户角度),即客户的满意度;二是高质量的交付(交付团队的角度),这里是指如何…...
JavaScript BOM【快速掌握知识点】
目录 Window对象的常用属性 语法: Window对象的常用方法 语法: open()和close()方法 History对象 常用属性和方法 示例 Location对象 常用属性 常用方法 Document对象的常用方法 定时函数 超时调用:setTimeout() 间歇调用&…...
【算法】哈希表
作者:指针不指南吗 专栏:算法篇 🐾或许会很慢,但是不可以停下来🐾 文章目录1.定义2.优点3.数字哈希3.1拉链法3.2开放寻址法3.3 例题4.字符串哈希1.定义 哈希表(Hash table),是根据键…...
微网站的图标怎么做/seo优化推广教程
一、介绍Oracle的同义词(synonyms)从字面上理解就是别名的意思,和视图的功能类似,就是一种映射关系。它可以节省大量的数据库空间,对不同用户的操作同一张表没有多少差别;它扩展了数据库的使用范围,能够在不同的数据库用户之间实现…...
网站建设公司 网络服务/最新app推广项目平台
2019年要看的书,看完做记录转载于:https://www.cnblogs.com/knuzy/p/10325517.html...
删除网站留言板功能/app优化推广
变量是保存存储值的内存位置。也就是说,当创建一个变量时,可以在内存中保留一些空间。 基于变量的数据类型,解释器分配内存并决定可以存储在保留的存储器中的内容。 因此,通过为变量分配不同的数据类型,可以在这些变量…...
南通网站建设知识/临沂森佳木业有限公司
重装MAC系统后,要恢复.ssh等文件夹内容,而其在“Finder”中又是默认隐藏的,这时我们可以先在“Finder”中使用“前往文件夹功能…”进入指定文件夹,然后再进入“Time Machine”进行恢复即可。 转载于:https://www.cnblogs.com/eri…...
网站设计与网页制作毕业论文/黄金网站app视频播放画质选择
现在数据库或是某个文件里有一些数据需要显示在界面上该怎么办呢?我们的目的就是将这些数据设法放到界面上去可以显示出来,这个将数据放到界面上去的过程可以叫住数据绑定。在Flex应用中程序中,数据绑定的实际是借助事件机制来完成的…...
知名外贸网站建设公司/网站快速上排名方法
一个电子商务网站,是依据某中盈利目的而建立。任何网站,建立后要做的第一件事情即是将网站推广出去,为人所知。通常采用的办法,一是开展线下推广,二是开展线上推广。 线下推广,一般是采取传统市场营销采用的…...