首页 文章 精选 留言 我的

精选列表

搜索[学习],共10000篇文章
优秀的个人博客,低调大师

Android学习笔记--Scoket编程

MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal0 1.什么是Socket Socket英文意为"插座" 所谓Scoket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄 应用程序通常通过"套接字"向网络发出请求或者应答网络请求 2.Socket基本通信模型 3.使用基于TCP协议的Socket ServerSocketActivity代码例: 1.声明控件对象(略) 2.获得控件对象(略) 3.绑定事件(略) 4.创建监听器对象 PublicvoidonClick(Viewv){ NewServerThread().start(); } //TCPserver ClassServerThreadextendsThread{ Publicvoidrun(){ //声明一个serverSocket对象 ServerSocketserverSocket=null; Try{ //创建serverSocket对象并在4567端口监听 serverSocket=newServerSocket(4567); //调用serverSocket的accept方法,接受客户端所发送的请求 Socketsocket=serverSocket.accept();//阻塞函数 //从socket当中得到inputstream对象 InputStreaminputStream=socket.getInputStream(); Bytebuffer[]newbute[1024*4]; Inttemp=0; //从inputStream当中读取客户端所发送的数据 While((temp=inputStream.read(buffer))!=-1){ System.out,println(newString(buffer,0,temp)); } }catch(Execptione){ e.printStackTrace(); }finally{ Try{ serverSocket.close(); }catch(IOExecptione){ e.printStackTrace(); } } } } TCPClient代码例: Publicstaticvoidmain(String[]args){ Try{ //创建一个Socket对象,指定服务器端的ip地址和端口号 Socketsocket=newSocket("192.168.1.1",4567); //使用InputStream读取硬盘上的文件 InputStreaminputStream=newFileInputStream("F://file/words.txt"); //从Socket上得到outputStream outputStreamoutputStream=socket.getOutputStream(); Bytebuffer[]=newbyte[4*1024]; Inttemp=0; //将inputStream的数据取出并写入到outputStream While((temp=inputStream.read(buffer))!=-1){ outputStream.write(buffer,0,temp); } outpurStream.flush(); }catch(Execptione){ e.printStackTrace(); } } //UDPServer ClassServerThreadextendsThread{ Publicvoidrun(){ Try{ //创建一个DatagramSocket对象,并制定监听端口号 DatagramSocketsocket=newDatagramSocket(4567); Bytedata[]=newbyte[1024]; //创建一个空的DatagramPacket对象 DatagramPacketpacket=newDatagramPacket(data,data.length); //使用receive方法接受客户端所发送的数据 Socket.receive(packet);//阻塞方法 Stringresult=newString(packet.getData(),packet.getOffset(),packet.getLength());//设置数据偏移量,得到该次数据的长度 System.out.println("result-->"+result); }catch(Execptione){ e.printStackTrace(); } } } //UDPClient Publicstaticvoidmain(String[]args){ Try{ //创建一个DatagramSocket对象,并制定监听端口号 DatagramSocketsocket=newDatagramSocket(456 7); InetAddressserverAddress=InetAddress.getByName("192.168.1.1"); Stringstr="hello"; Bytedata[]=str.getBytes(); DatagramPacketpacket=newDatagramPacket(data,data.length,serverAddress,4567); Socket.send(packet); }catch(Execptione){ e.printStackTrace(); } } 4.使用基于UDP协议的Socket 本文转自My_King1 51CTO博客,原文链接:http://blog.51cto.com/apprentice/1360562,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android学习笔记--程序调试

MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal0 1.DDMS的使用 LogCat的VerboseDebugInfoWarningError的功能 增加过滤器createfilter=>system.out 通过fileexplorer功能实现文件的取出,放入 2.常见程序调试 A)多多查看DDMS的log和java中的方式相同 B)使用Log类输出各类信息 本文转自My_King1 51CTO博客,原文链接:http://blog.51cto.com/apprentice/1360570,如需转载请自行联系原作者

优秀的个人博客,低调大师

rpc协议之学习路线

RPC(Remote Procedure Call,远程过程调用)框架是分布式服务的基石,实现RPC框架需要考虑方方面面。其对业务隐藏了底层通信过程(TCP/UDP、打包/解包、序列化/反序列化),使上层专注于功能实现;框架层面,提供各类可选架构(多进程/多线程/协程);应对设备故障(高负载/死机)、网络故障(拥塞/网络分化),提供相应容灾措施。 RPC节点间为了协同工作、实现信息交换,需要协商一定的规则和约定,例如字节序、压缩或加密算法、各字段类型。通信协议的应用随处可见,例如我们对可选信息或字段经常使用TLV进行编码,HTTP、FTP等协议基于可读文本的 "Field: Value" 格式,各种系统也经常使用json、XML格式完成相互间通信。 不同的通信协议适用于不同的应用场景,比如内部系统的交互我们选择json,一来可读性较好,二来各种语言都提供了解析json的库、方便编码。Google Protocol Buffers是生成环境中常用的通信协议,除了可以设定Client/Server间通信格式,Protocol Buffers还对数据进行压缩,节省传输流量、加快传输速度。下面我们来了解Google Protocol Buffers。 RPC RPC框架实现 - 通信协议篇 http://www.cnblogs.com/bangerlee/p/4486429.html 本文转自heavenseahill 51CTO博客,原文链接:http://blog.51cto.com/shower/1974171,如需转载请自行联系原作者

优秀的个人博客,低调大师

Dockerfile学习And构建Hexo镜像

原文链接: http://yangbingdong.com/2017/note-of-dockefile-and-build-hexo-docker-image/ Preface 制作一个镜像可以使用docker commit和定制Dockerfile,但推荐的是写Dockerfile。 因为docker commit是一个暗箱操作,除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知,而且会加入一些没用的操作导致镜像臃肿。 此篇记录构建Hexo的镜像踩坑~ Build Images 首先在当前空目录创建一个Dockerfile: FROM ubuntu:latest ENV BLOG_PATH /root/blog ENV NODE_VERSION 6 MAINTAINER yangbingdong <yangbingdong1994@gmail.com> RUN \ apt-get update -y && \ apt-get install -y git curl libpng-dev && \ curl -sL https://deb.nodesource.com/setup_$NODE_VERSION.x | bash - && \ apt-get install -y nodejs && \ apt-get clean && \ apt-get autoclean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ npm install -g hexo-cli WORKDIR $BLOG_PATH VOLUME ["$BLOG_PATH", "/root/.ssh"] EXPOSE 4000 CMD ['/bin/bash'] 然后在当前目录打开终端: docker build -t <repo-name>/<image-name>:<tag> . 其中<repo-name>表示仓库名,与远程仓库(如docker hub)名字要一致,<tag>表示标签,不给默认latest,都是可选项,例如可以写成这样: docker build -t <image-name> . 看到Successfully built就表示构建成功了 注意docker build 命令最后有一个 .表示构建的上下文,镜像构建需要把上下文的东西上传到Docker引擎去构建。 Dockerfile 指令 From 指定基础镜像 所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。而 FROM 就是指定基础镜像,因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。 在 Docker Hub上有非常多的高质量的官方镜像, 有可以直接拿来使用的服务类的镜像,如 nginx、redis、mongo、mysql、httpd、php、tomcat 等; 也有一些方便开发、构建、运行各种语言应用的镜像,如 node、openjdk、python、ruby、golang 等。 可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。 如果没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操作系统镜像,如 ubuntu、debian、centos、fedora、alpine 等,这些操作系统的软件库为我们提供了更广阔的扩展空间。 除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。 RUN 执行命令 RUN 指令是用来执行命令行命令的。由于命令行的强大能力,RUN 指令在定制镜像时是最常用的指令之一。其格式有两种: shell 格式:RUN <命令>,就像直接在命令行中输入的命令一样。刚才写的 Dockrfile 中的 RUN 指令就是这种格式。 RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html exec 格式:RUN ["可执行文件", "参数1", "参数2"],这更像是函数调用中的格式。 注意: RUN命令尽量精简,也就是像上面一样一个RUN(使用$$ \),如果分开写很多个RUN会导致镜像铺了很多层从而臃肿。 RUN最后记住清理掉没用的垃圾,很多人初学 Docker 制作出了很臃肿的镜像的原因之一,就是忘记了每一层构建的最后一定要清理掉无关文件。 COPY 复制文件 格式: COPY <源路径>... <目标路径> COPY ["<源路径1>",... "<目标路径>"] 和 RUN 指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。 COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。比如: COPY package.json /usr/src/app/ ADD 更高级的复制文件 ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。 比如 <源路径> 可以是一个 URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600,如果这并不是想要的权限,那么还需要增加额外的一层 RUN进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN 指令进行解压缩。所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。 如果 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去。 在某些情况下,这个自动解压缩的功能非常有用,比如官方镜像 ubuntu 中: FROM scratch ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz / ... 但在某些情况下,如果我们真的是希望复制个压缩文件进去,而不解压缩,这时就不可以使用 ADD 命令了。 在 Docker 官方的最佳实践文档中要求,尽可能的使用 COPY,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合。 另外需要注意的是,ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。 因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。 CMD 容器启动命令 CMD 指令就是用于指定默认的容器主进程的启动命令的。 CMD 指令的格式和 RUN 相似,也是两种格式: shell 格式:CMD <命令> exec 格式:CMD ["可执行文件", "参数1", "参数2"...] 参数列表格式:CMD ["参数1", "参数2"...]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。 在运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如,ubuntu 镜像默认的 CMD 是 /bin/bash,如果我们直接 docker run -it ubuntu 的话,会直接进入 bash。我们也可以在运行时指定运行别的命令,如 docker run -it ubuntu cat /etc/os-release。这就是用 cat /etc/os-release 命令替换了默认的 /bin/bash 命令了,输出了系统版本信息。 在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号。 如果使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。比如: CMD echo $HOME 在实际执行中,会将其变更为: CMD [ "sh", "-c", "echo $HOME" ] 所以如果使用shell格式会导致容器莫名退出,因为实际上执行的事sh命令,而sh命令执行完时候容器也就没有存在的意义。 ENTRYPOINT 入口点 ENTRYPOINT 的格式和 RUN 指令格式一样,分为 exec 格式和 shell 格式。 ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint 来指定。 当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为: <ENTRYPOINT> "<CMD>" 这个指令非常有用,例如可以把命令后面的参数传进来或启动容器前准备一些环境然后执行启动命令(通过脚本exec "$@")。 ENV 设置环境变量 格式有两种: ENV <key> <value> ENV <key1>=<value1> <key2>=<value2>... 这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,如 RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。 ex: ENV NODE_VERSION 6 ... RUN curl -sL https://deb.nodesource.com/setup_$NODE_VERSION.x | bash - && \ ... ARG 构建参数 格式:ARG <参数名>[=<默认值>] 构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。 Dockerfile 中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build 中用 --build-arg <参数名>=<值> 来覆盖。 在 1.13 之前的版本,要求 --build-arg 中的参数名,必须在 Dockerfile 中用 ARG 定义过了,换句话说,就是 --build-arg 指定的参数,必须在 Dockerfile 中使用了。如果对应参数没有被使用,则会报错退出构建。从 1.13 开始,这种严格的限制被放开,不再报错退出,而是显示警告信息,并继续构建。这对于使用 CI 系统,用同样的构建流程构建不同的 Dockerfile 的时候比较有帮助,避免构建命令必须根据每个 Dockerfile 的内容修改。 VOLUME 定义匿名卷 格式为: VOLUME ["<路径1>", "<路径2>"...] VOLUME <路径> 之前我们说过,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,后面的章节我们会进一步介绍 Docker 卷的概念。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。 VOLUME /data 这里的 /data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如: docker run -d -v mydata:/data xxxx 在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。 EXPOSE 声明端口 格式为 EXPOSE <端口1> [<端口2>...]。EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P时,会自动随机映射 EXPOSE 的端口。 此外,在早期 Docker 版本中还有一个特殊的用处。以前所有容器都运行于默认桥接网络中,因此所有容器互相之间都可以直接访问,这样存在一定的安全性问题。于是有了一个 Docker 引擎参数 --icc=false,当指定该参数后,容器间将默认无法互访,除非互相间使用了 --links 参数的容器才可以互通,并且只有镜像中 EXPOSE 所声明的端口才可以被访问。这个 --icc=false 的用法,在引入了 docker network后已经基本不用了,通过自定义网络可以很轻松的实现容器间的互联与隔离。 要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。-p,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。 WORKDIR 指定工作目录 格式为 WORKDIR <工作目录路径>。 使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。 之前提到一些初学者常犯的错误是把 Dockerfile 等同于 Shell 脚本来书写,这种错误的理解还可能会导致出现下面这样的错误: RUN cd /app RUN echo "hello" > world.txt 如果将这个 Dockerfile 进行构建镜像运行后,会发现找不到 /app/world.txt 文件,或者其内容不是 hello。原因其实很简单,在 Shell 中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令;而在 Dockerfile 中,这两行 RUN 命令的执行环境根本不同,是两个完全不同的容器。这就是对 Dokerfile 构建分层存储的概念不了解所导致的错误。 之前说过每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。第一层 RUN cd /app 的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。 因此如果需要改变以后各层的工作目录的位置,那么应该使用 WORKDIR 指令。 USER 指定当前用户 格式:USER <用户名> USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER则是改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。 当然,和 WORKDIR 一样,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。 RUN groupadd -r redis && useradd -r -g redis redis USER redis RUN [ "redis-server" ] 踩坑 Dockerfile里也需要注意权限问题(nodejs7版本以上不能正常安装hexo,需要创建用户并制定权限去安装) 在docker容器里如果是root用户对挂载的文件进行了操作,那么实际上挂载文件的权限也变成了root的 使用attach进入容器,退出的时候容器也跟着退出了。。。囧 每一个RUN是一个新的shell su -之前在启动脚本加了-,导致环境变量以及工作目录都变了 Hexo-Docker 最后献上踩坑写的Hexo Dockerfile: # 使用Ubuntu官方镜像 FROM ubuntu:latest # 作者信息 MAINTAINER yangbingdong <yangbingdong1994@gmail.com> # 设置环境变量,使用${变量名}取值 ENV \ USER_NAME=hexo \ NODE_VERSION=8.5.0 \ NODE_DIR=/home/${USER_NAME}/nodejs # 需要执行的命令,使用 `$$ \` 分割多行多个命令 RUN \ # 安装基本的依赖以及工具 apt-get update -y && \ apt-get upgrade -y && \ apt-get install -y git && \ apt-get install -y curl && \ apt-get install -y libpng-dev && \ # 清理不必要的垃圾 apt-get clean && \ apt-get autoclean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ # 创建hexo用户去安装hexo useradd -m -U ${USER_NAME} && \ # 创建nodejs目录 mkdir ${NODE_DIR} && \ # 将nodejs下载解压到对应目录 curl -L https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz | tar xvzf - -C ${NODE_DIR} --strip-components=1 && \ # 把nodejs文件赋权给hexo用户 chown -R ${USER_NAME}.${USER_NAME} ${NODE_DIR} && \ # 把node相关命令软连接到/usr/local/bin目录下以便我们使用 ln -s ${NODE_DIR}/bin/node /usr/local/bin/node && \ ln -s ${NODE_DIR}/bin/npm /usr/local/bin/npm && \ # 以hexo用户身份安装hexo-cli su - ${USER_NAME} -c "npm install -g hexo-cli" && \ # 同样把hexo命令放到/usr/local/bin下 ln -s ${NODE_DIR}/bin/hexo /usr/local/bin/hexo && \ # 使用淘宝镜像 npm config set registry https://registry.npm.taobao.org/ # 切换到此目录 WORKDIR /home/${USER_NAME}/blog # 可以挂载进来的卷(文件夹) VOLUME ["/home/${USER_NAME}/blog", "/home/${USER_NAME}/.ssh"] # 暴露端口 EXPOSE 4000 # 把上下文中的docker-entrypoint.sh复制进来 COPY docker-entrypoint.sh /docker-entrypoint.sh # 执行脚本 ENTRYPOINT ["/docker-entrypoint.sh"] # 这个...鸡肋操作 CMD ['/bin/bash'] docker-entrypoint.sh : #!/bin/sh # 发生异常回滚 set -e # 设置git相关信息,不设置默认为博主的=.= GIT_USER_NAME=${GIT_USER_NAME:-yangbingdong} GIT_USER_MAIL=${GIT_USER_MAIL:-yangbingdong1994@gmail.com} # 你想要的用户名 NEW_USER_NAME=${NEW_USER_NAME:-ybd} # 由于每次启动容器都会执行这个脚本,但这个只需要执行一次,在此标志一下 if [ $(git config --system user.name)x = ${GIT_USER_NAME}x ] then su ${NEW_USER_NAME} else # 修改用户名 /usr/sbin/usermod -l ${NEW_USER_NAME} ${USER_NAME} /usr/sbin/usermod -c ${NEW_USER_NAME} ${NEW_USER_NAME} /usr/sbin/groupmod -n ${NEW_USER_NAME} ${USER_NAME} chown -R ${NEW_USER_NAME}.${NEW_USER_NAME} /home/${USER_NAME}/blog chmod -R 766 /home/${USER_NAME}/blog # 设置git全局信息 git config --system user.name $GIT_USER_NAME git config --system user.email $GIT_USER_MAIL su ${NEW_USER_NAME} fi # 执行脚本之后的命令 exec "$@" 源码:https://github.com/masteranthoneyd/docker-hexo Last 参考:Docker从入门到实践

优秀的个人博客,低调大师

Android Architecture Componets BasicSample学习

1.创建工程 所有模块依赖于Google,jencenter,maven仓库 allprojects { repositories { jcenter() maven { url 'https://maven.google.com' } mavenCentral() } } gradle版本:gradle-4.1-milestone-1-all gradle 的Android插件版本在3.0.0-beta7 distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-milestone-1-all.zip classpath 'com.android.tools.build:gradle:3.0.0-alpha7' 工程根目录的gradle设置一些变量,表示模块依赖的框架的版本号,方便以后改动,例如 ext { buildToolsVersion = "26.0.1" supportLibVersion = "26.0.2" runnerVersion = "1.0.1" rulesVersion = "1.0.1" espressoVersion = "3.0.1" archLifecycleVersion = "1.0.0-alpha9" archRoomVersion = "1.0.0-alpha9" constrantLayoutVersion="1.0.2" } Module的gradle里 compile 'com.android.support:design:' + rootProject.supportLibVersion compile 'com.android.support:cardview-v7:' + rootProject.supportLibVersion compile 'com.android.support:recyclerview-v7:' + rootProject.supportLibVersion compile 'com.android.support.constraint:constraint-layout:' + rootProject.constrantLayoutVersion 需求分析,效果展示 这个应用就两个界面,打开应用进入首页,刚开始会加载产品数据,数据是从本地数据库查询而来,然后点击任意一项可以进入产品的详情页,详情页包含对产品的评论 效果展示 主目录 src主目录 db db目录 converter 里面放着一个日期转换器,用了TypeConverter注解 /** * 时间转换 * @param timestamp * @return Date */ @TypeConverter public static Date toDate(Long timestamp){ return timestamp==null?null:new Date(timestamp); } /** * 时间转换 * @param date * @return long */ @TypeConverter public static Long toTimestamp(Date date) { return date == null ? null : date.getTime(); } model 模型,里面都是接口,包含的都是获取数据实体成员的方法,它们都是抽象的。 例:一个Comment的,一个Product的 public interface Comment { int getId();//获取ID int getProductId();//获取产品ID String getText();//获取评论内容 Date getPostedAt();//获取发布时间 } public interface Product { int getId(); String getName(); String getDescription(); int getPrice(); } entity 这个数据库就两张表,一个使产品表,一个使评论表,所以分别对应着各自的实体类 例如一个产品的实体类,它是实现了model里的抽象方法 //Entity注解,设置表名为products @Entity(tableName = "products") public class ProductEntity implements Product{//实现了模型的抽象方法 @PrimaryKey private int id;//id 为主键 private String name;//产品名 private String description;//产品描述 private int price;//产品价格 @Override public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } @Override public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } public ProductEntity() { } public ProductEntity(Product product){ this.id=product.getId(); this.name=product.getName(); this.description=product.getDescription(); this.price=product.getPrice(); } } @Entity(tableName = "comments" ,foreignKeys = { @ForeignKey(entity = ProductEntity.class, parentColumns = "id", childColumns = "productId", onDelete = ForeignKey.CASCADE) },indices = { @Index(value = "productId") }) public class CommentEntity implements Comment{ @PrimaryKey(autoGenerate = true) private int id; private int productId; private String text; private Date postedAt; @Override public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public int getProductId() { return productId; } public void setProductId(int productId) { this.productId = productId; } @Override public String getText() { return text; } public void setText(String text) { this.text = text; } @Override public Date getPostedAt() { return postedAt; } public void setPostedAt(Date postedAt) { this.postedAt = postedAt; } public CommentEntity() { } public CommentEntity(Comment comment) { this.id = comment.getId(); this.productId = comment.getProductId(); this.text = comment.getText(); this.postedAt = comment.getPostedAt(); } } dao 里面是各个表的操作接口,都是抽象的,很像Retrofit那个带注解的接口,只不过这里是操作数据库 @Dao public interface CommentDao { @Query("SELECT * FROM comments where productId = :productId") LiveData<List<CommentEntity>> loadComments(int productId); @Query("SELECT * FROM comments where productId = :productId") List<CommentEntity> loadCommentsSync(int productId); @Insert(onConflict = OnConflictStrategy.REPLACE) void insertAll(List<CommentEntity> products); } @Dao public interface ProductDao { @Query("SELECT * FROM products") LiveData<List<ProductEntity>> loadAllProducts(); @Insert(onConflict = OnConflictStrategy.REPLACE) void insertAll(List<ProductEntity> products); @Query("SELECT * FROM products WHERE id=:productId") LiveData<ProductEntity> loadProduct(int productId); @Query("SELECT * FROM products WHERE id =:productId") ProductEntity loadProductSync(int productId); } AppDatabase.java AppDatabase继承RoomDatabase,这个类的作用就是数据库类,通过这个对象可以获取各个表的Dao和数据库设置,@Database注解设置了实体类和数据库版本,@TypeConverters指定了类型转换,就是上面的日期转换,因为数据库没有Data这种类型,但可以用long表示 @Database(entities = {ProductEntity.class, CommentEntity.class},version = 1) @TypeConverters(DateConverter.class) public abstract class AppDataBase extends RoomDatabase { public static final String DATABASENAME="basesample-db";//数据库名 public abstract ProductDao productDao();//获取Dao public abstract CommentDao commentDao();//获取Dao } DatabaseCreator.java 这个是一个辅助类,也是一个单例,用于获取AppDatabase实例,因为很多地方都要用AppDatabase实例嘛,而且数据库还没创建啊,这个类设计到一些知识 实现单例 这里应该用的是双重检查锁,保证创建实例是也是在一个线程里创建 // For Singleton instantiation private static DatabaseCreator sInstance; private static final Object LOCK = new Object(); public synchronized static DatabaseCreator getInstance(Context context) { if (sInstance == null) { synchronized (LOCK) { if (sInstance == null) { sInstance = new DatabaseCreator(); } } } return sInstance; } 原子操作 这里用到了AtomicBoolean,原来看到这里我都很懵逼,后来查查资料,才知道这个叫原子操作,就是compareAndSet(boolean expect, boolean update)。 比较AtomicBoolean和expect的值,如果一致,执行方法内的语句。其实就是一个if语句 把AtomicBoolean的值设成update 这连个操作一气呵成,中间没有人能够阻止,这样的话可以进行多线程控制,比如这里的创建数据库,因为后期可能多个地方会用到creatDb方法,所以要保证数据库只能创建一次且只能在一个线程中创建,当然这里没用SharePreferences,所以这里的"数据库只能创建一次"是指在应用不被杀死的情况下。 private final MutableLiveData<Boolean> mIsDatabaseCreated = new MutableLiveData<>(); private AppDataBase mDb; private final AtomicBoolean mInitializing = new AtomicBoolean(true); public void createDb(Context context) { Log.d("DatabaseCreator", "Creating DB from " + Thread.currentThread().getName()); if (mInitializing.compareAndSet(false, true)) { return; // Already initializing 已经创建了数据库 } mIsDatabaseCreated.setValue(false);//开始创建数据库,观察这个数据可以显示loading new AsyncTask<Context,Void,Void>(){ @Override protected Void doInBackground(Context... contexts) { Log.d("DatabaseCreator", "Starting bg job " + Thread.currentThread().getName()); Context context = contexts[0].getApplicationContext(); // Reset the database to have new data on every run. context.deleteDatabase(DATABASENAME); AppDataBase db= Room.databaseBuilder(context.getApplicationContext(),AppDataBase.class, DATABASENAME).build(); addDelay(); // Add a delay to simulate a long-running operation模拟耗时操作 // Add some data to the database 加一些数据进去 DatabaseInitUtil.initializeDb(db); Log.d("DatabaseCreator", "DB was populated in thread " + Thread.currentThread().getName()); mDb=db; return null; } @Override protected void onPostExecute(Void aVoid) { mIsDatabaseCreated.setValue(true);//创建数据库完毕 Log.d(TAG, "onPostExecute:"); } }.execute(context.getApplicationContext()); } viewmodel viewmodel目录 viewmodel就是提供一个连接model和view的桥梁,只不过提供的是可被观察的数据对象,而且是liveData,可以判断观察者的状态进行通知.如下面的ProductListViewModel,mObservableProducts是一个LiveData,viewmodel获取它是通过database得到,但如果database未初始化的情况也要考虑,所以用了一个Transformations.switchMap(),就是在databaseCreator.isDatabaseCreated()这个liveData为FALSE时返回值为空的liveData,为True时返回database查询到的liveData,这里可能有点绕,但是慢慢想又觉得很妙,因为这个观察模式在一定的生命周期内一直生效,完全是响应式的。 public class ProductListViewModel extends AndroidViewModel{ private static final String TAG = "ProductListViewModel>>>"; private static final MutableLiveData ABSENT=new MutableLiveData(); { ABSENT.setValue(null); } private final LiveData<List<ProductEntity>> mObservableProducts; public ProductListViewModel(@Nullable Application application) { super(application); final DatabaseCreator databaseCreator=DatabaseCreator.getInstance(application); mObservableProducts= Transformations.switchMap(databaseCreator.isDatabaseCreated(), new Function<Boolean, LiveData<List<ProductEntity>>>() { @Override public LiveData<List<ProductEntity>> apply(Boolean input) { if (!Boolean.TRUE.equals(input)){ Log.d(TAG, "apply: 空数据!"); return ABSENT; }else { return databaseCreator.getDatabase().productDao().loadAllProducts(); } } }); databaseCreator.createDb(this.getApplication()); } public LiveData<List<ProductEntity>> getmObservableProducts() { return mObservableProducts; } }

优秀的个人博客,低调大师

MongoDB学习笔记(一)--基础

Insert MongoDB在执行插入时,首先会将插入的数据转换成BSON格式。然后MongoDB数据库会对BSON进行解剖,并检查是否存在_id建。 >doc = { "_id" : 1, "author" : "yyd", "title" : "MongoDB Test", "text" : "this is a test", "tags" : [ "love", "test" ], "comments" : [ { "author" : "yyd_guest", "comment" : "yes" }, { "author" : "yyd_admin", "comment" : "no" } ] } > db.yyd.insert(doc); Query 全部查找 返回除了 tags 字段外的所有字段 返回 tags = test 除了 comments 的所有列 返回 id=1 的 title 字段 <, <=, >, >= 大于 $gt、小于 $lt、大于等于 $gte、小于等于 $lte $all $all 操作类似$in 操作,但是不同的是,$all 操作要求数组里面的值全部被包含在返回的记录里面。 $exists $exists 操作检查一个字段是否存在。 $exists:true代表返回存在这个键的值。 $exists:false代表返回不存在这个键的值。 $mod > db.user.find("this._id%2==1"); > db.user.find({_id:{$mod:[2,1]}}); 两句话一样的效果。 $ne $ne 意思是 not equal,不等于。 $in $in 操作类似于传统关系数据库中的 IN。 $nin $nin 跟$in 操作相反。 $or $nor $nor 跟$or 相反。 $size $size 操作将会查询数组长度等于输入参数的数组。 skip 跳过前 2 条记录。 limit 每页返回 3 条记录 sort() sort()方法对返回记录集按照指定字段进行排序返回,1 表示升序,-1 表示降序。 count() count()方法返回查询记录的总数目。 Remove Update update() db.collection.update( criteria, objNew, upsert, multi ) 参数说明: Criteria:用于设置查询条件的对象 Objnew:用于设置更新内容的对象 Upsert:如果记录已经存在,更新它,否则新增一个记录 Multi:如果有多个符合条件的记录,全部更新 注意:默认情况下,只会更新第一个符合条件的记录 save() 如果存在更新它,如果不存在,新增记录。 $inc 增加1,对int等有效。 对一个_id=3 的 user 的年龄进行加 1,两种方法。 $set { $set : { field : value } } 把 field 的值设置成 value,当 field 不存在时,增加一个字段,类似 SQL 的 set 操作,value 支持所有类型。 $unset { $unset : { field : 1} } 删除给定的字段 field。 $push { $push : { field : value } } 如果 filed 是一个已经存在的数组,那么把 value 追加给 field; 如果 field 原来不存在,那么新增 field 字段,把 value 的值赋给 field; 如果 field 存在,但是不是一个数组,将会出错。 $pushAll { $pushAll : { field : value_array } } 功能同$push,只是这里的 value 是数组,相当于对数组里的每一个值进行$push操作。 $addToSet { $addToSet : { field : value } } 如果 filed 是一个已经存在的数组,并且 value 不在其中,那么把 value 加入到数组; 如果 filed 不存在,那么把 value 当成一个数组形式赋给 field;$pop 如果 field 是一个已经存在的非数组类型,那么将会报错。 $pop { $pop : { field : 1 } } 删除数组中最后一个元素 { $pop : { field : -1 } } 删除数组中第一个元素 $pull { $pull : { field : _value } } 如果 field 是一个数组,那么删除符合_value 检索条件的记录; 如果 field 是一个已经存在的非数组,那么会报错。 $pullAll { $pullAll : { field : value_array } } $rename { $rename : { old_field_name : new_field_name } 重命名指定的字段名称。 本文转自我爱物联网博客园博客,原文链接:http://www.cnblogs.com/yydcdut/p/3557414.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

深度学习的IR“之争”

熟悉编译器的同学应该对上图并不陌生。它就是大名鼎鼎的LLVM的logo。Google Tensorflow XLA (Accelerated Linear Algebra)就使用了LLVM IR(Intermediate Representation)。而它的“竞争对手”,刚刚发布的TVM/NNVM,则是“Tensor IR Stack for Deep Learning Systems”。IR是什么?为什么重要?我们一起来看看。 上周,我们看到这样的新闻“Facebook and Microsoft introduce new open ecosystem for interchangeable AI frameworks”。这也让Framework之争更加热闹。简单来说,ONNX也是为了解决目前多个Framework互操作的问题

优秀的个人博客,低调大师

Log stash学习笔记(一)

Logstash是一款开源的数据收集引擎,具备实时管道处理能力。简单来说,logstash作为数据源与数据存储分析工具之间的桥梁,结合 ElasticSearch以及Kibana,能够极大方便数据的处理与分析。通过200多个插件,logstash可以接受几乎各种各样的数据。包括日志、网络请求、关系型数据库、传感器或物联网等等。 安装 之前我有一篇文章ElasticSearch + Logstash + Kibana 搭建笔记记录了ELK环境的搭建过程,只是简单介绍了一种安装方式,本文记录更多的细节。 logstash 要求Java8的支持。Java9暂时不支持,本文使用的logstash版本为5.5.1。 在Linux环境中,我们可以通过包管理进行安装,例如Unbuntu中的apt、以及CentOS中的yum。也可以从这里下载对应环境的二进制版本。 体验Pipeline Logstatsh最基本的Pipeline模式如下图所示,其需要两个必需参数input、output,以及一个可选参数filter。 cd logstash-5.5.1 bin/logstash -e 'input { stdin { } } output { stdout {} }' [2017-08-09T15:40:03,303][INFO ][logstash.pipeline ] Pipeline main started hello world 2017-08-09T07:40:46.397Z iZ627x15h6pZ hello world -e参数允许我们在命令行中直接输入配置,而不同通过-f参数指定配置文件。看到Pipeline main started表示logstash已经启动成功,在命令行输入一些文字后,logstash会加上日期和主机名(IP)输出到终端。这就是Logstash最基本的工作模式,接受输入,然后将信息加工后放入到输出。 处理日志 Logstash收购了Filebeat加入到自己的生态系统中用来处理日志,Filebeat是一个轻量化的日志收集工具,还可以感知后台logstash的繁忙程度来决定日志发送的速度。 首先下载 Filebeat,解压后就可以使用。找到filebeat.yml修改如下: filebeat.prospectors: - input_type: log paths: - /path/to/file/logstash-tutorial.log output.logstash: hosts: ["localhost:5043"] 配置完成后等logstash启动后再启动Filebeat。 sudo ./filebeat -e -c filebeat.yml -d "publish" 接下来配置logstash,按照如下配置编写一个 filebeat.conf 文件 input { beats { port => "5043" } } output { stdout { codec => rubydebug } } 配置文件可以用下面的命令验证是否有问题 bin/logstash -f filebeat.conf --config.test_and_exit 如果验证没有问题,就可以用这个配置文件启动 logstash 了。 bin/logstash -f filebeat.conf 之后再启动Filebeat,就可以在Filebeat终端窗口看到日志信息的输出,同时在logstash这边也能看到有日志输出。这就说明此时logstash已经能够正常接收beats送来的文件了。 参考资料: 1、Logstash Reference 2、Filebeat

优秀的个人博客,低调大师

网络及Web学习笔记

IT人一提到说到网络,必定且首先就要提到的就是TCP/IP协议了。这是基本的网络通信层的协议。而这个协议,是网路平台七层网络协议中的一个点。网路协议定义了从用户数据和请求的处理打包/解包的TCP,UDP协议,到分包发送数据的IP协议,再到物理层的实现。一大堆的概念,一本本大砖头般的书,就是学这方面的人都受不了,更不用说只是想了解下网络过程的一般用户了。而更为人熟知的,该是伯纳斯李及其万维网了。万维网乃是现在最大的网络了,而一开始,网络是一点点连接起来的。 那么网络到底是什么呢?计算机刚兴起时,其主要工作就是用来做大规模计算的。其服务方式也是由大型服务机To客户机的形式。客户端进行数据请求,服务机响应处理然后再由客户机显示结果。后来,才有了机器与机器之间需要数据共享的问题。如果就那么几台机器,那么拷贝一份数据也没什么。但是当一个集群的机器需要共享数据,特别是涉及到实时数据时,显然,用拷贝的方式不能满足需求了。所以才有了计算机之间的通信。 而在一开始,不同硬件厂商——IBM, HP, SUN都定义了自己的通信协议和实现。同一个硬件厂商的硬件通信没问题,但是不同厂商的硬件却不能通信。所以才有了后来的标准化的通信协议。而万维网的兴起是在个人计算机兴起后,伯纳斯李的创造性发明。由此,计算机不再是一个个单独的存在,而是通过网络互通互联在一起的一个网络了。 首次接触到通信协议这种东西,往往茫然不知其所谓。记得我第一次了解到这些协议的时候就总是想不通,通过这些协议性的规定就可以实现数据的传输了吗?这是莫名其妙。后来才理解了,传输数据是由物理层的介质——光纤、无线网络——来实现的。而协议是通信双方约定好的,数据传输的格式。以约定好的格式来传输,双方都方便。数据发送方按照协议规定格式进行数据打包然后发送,而接收方就可以按照这个格式进行快速解包了。或者,可以这么去理解,协议就是一个双方都知道的其格式的空表格,发送方往表格空白处填充数据,而接收方,从表格中取出数据。由于双方都知道表格的格式,所以,要发什么数据,要取什么数据,直接去找所需的栏目就可以了。 现在的网络,是一个去中心化的网络。网络中的一个个节点通过IP地址定位。主要通信则发生在服务器与普通网络入口(pc机,手机)之间。所谓网络应用程序就是客户端(Client)软件与服务器端(Server)的通信了。这就是所谓的CS架构模型。而当下大热的Web,提倡用浏览器(Browser)代替客户端(Client)的新模型——BS架构模型。其中,最主要的通信就是浏览器与服务器程序的通信。也就是HTTP通信协议。这些整个过程中,HTTP协议负责对请求和响应的打包与解包。TCP协议负责的是机器与机器之间的通信连接,即用户端机器与服务器端机器的连接。而IP协议负责了用户机器与客户端机器见通信路径的选择,数据的分包发送等。TCP协议是建立在IP协议的基础上的。 作者:何妍 来源:51CTO

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册