0%

前言

近段时间在准备毕业设计的前期准备,基本确定了前后端分离的架构,于是就需要用到了nginx。

在之前nginx是放在docker上,所以没有端口更改跟配置文件配置的烦恼。但是现在是直接放在服务器上,但是跟tomcat或者apollo的端口发生了冲突,于是就动了改端口以及配置文件位置的想法。

正文

nginx在linux上的安装

1
sudo apt-get install nginx

nginx安装完成后,文件目录会在/etc/nginx中,跟docker安装的有点类似。

nginx更换配置文件的配置

(其实也不算更换,算是增加,配置起来没那么麻烦)

  • 修改 /etc/nginx 中的nginx.conf
1
2
vim nginx.conf  --用vim打开文件
include /root/nginx/conf.d/*.conf; -- 放在http块中,路径自定义切换
  • 新建文件夹并重启nginx
1
nginx -s reload

nginx更换默认端口

  • 参照上文的更改配置文件的修改,增加一个语句
1
include /etc/nginx/sites-enabled/*;  -- 默认有的,但是最好检查一下
  • 修改 /etc/nginx/sites-enabled 中的 default 文件
1
2
3
vim default  -- 用vim打开文件
listen 8091 default_server; -- 修改监听端口
listen [::]:8091 default_server; -- 修改监听端口
  • 重启nginx
1
nginx -s reload

重新输入ip跟端口就能看到效果,但是如果像我一样搭在服务器上,记得开放服务器的端口(小小踩了坑的我)。

    本文首发于 cartoon的博客

    转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/nginx/更换nginx默认端口以及配置文件位置/

前言

这段时间在接触分布式的内容,由于本身比较熟悉rpc的原理,所以我顺其自然地选择了 dubbo 作为我学习的框架。
看了任务清单,这篇文章应该是在6天前出来的,但是因为实习等等的一些事情耽误了,今天立下决心动笔了。

准备

必需
JAVA 环境
注册中心(我选用的是 nacos )

非必需
maven / gradle(本文使用gradle构建)
docker
idea(这个应该是必需吧?当然也可以用记事本(滑稽.jpg))

正文

  1. 新建普通的 gradle 项目(不勾选任何选项)

  2. 新建三个module,分别命名为 Common,DubboProducer,DubboConsumer

  • 模块作用
1
2
3
Common -- 普通 gradle 项目,用于定义 proucer 以及 consumer 交互的接口以及规范
DubboProducer -- Spring Boot 本地项目,用于为 Common 中定义的服务接口创建实体类
DubboConsumer -- Spring Boot web 项目,接收用户请求,调用 producer 处理请求并返回结果
  • 模块 gradle 定义

    • 根项目 setting.gradle 新增
    1
    2
    3
    include 'Common'
    include 'DubboConsumer'
    include 'DubboProducer'
    • consumer 新增

      • build.gradle 新增
      1
      2
      3
      4
      5
      6
      // https://mvnrepository.com/artifact/org.apache.dubbo/dubbo
      compile group: 'org.apache.dubbo', name: 'dubbo', version: '2.7.8'
      // https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-registry-nacos
      compile group: 'org.apache.dubbo', name: 'dubbo-registry-nacos', version: '2.7.8'
      // https://mvnrepository.com/artifact/com.alibaba.nacos/nacos-client
      compile group: 'com.alibaba.nacos', name: 'nacos-client', version: '1.3.3'
      • setting.gradle 新增
      1
      includeFlat 'Common'
    • producer 新增

      • build.gradle 新增
      1
      2
      3
      4
      5
      6
      // https://mvnrepository.com/artifact/org.apache.dubbo/dubbo
      compile group: 'org.apache.dubbo', name: 'dubbo', version: '2.7.8'
      // https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-registry-nacos
      compile group: 'org.apache.dubbo', name: 'dubbo-registry-nacos', version: '2.7.8'
      // https://mvnrepository.com/artifact/com.alibaba.nacos/nacos-client
      compile group: 'com.alibaba.nacos', name: 'nacos-client', version: '1.3.3'
      • setting.gradle 新增
      1
      includeFlat 'Common'
  • 模块配置文件

    • consumer 配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    spring:
    application:
    name: dubbo-consumer
    cloud:
    nacos:
    discovery:
    server-addr: cartoon-ali.com
    dubbo:
    protocol:
    port: -1
    name: dubbo
    registry:
    address: nacos://cartoon-ali.com:8848
    cloud:
    subscribed-services: dubbo-spring-cloud-provider
    application:
    name: consumer
    • producer 配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    dubbo:
    registry:
    address: nacos://cartoon-ali.com:8848
    application:
    name: dubbo-producer
    protocol:
    port: -1
    name: dubbo
  • 启动类需同时使用 @EnableDubbo 修饰

    • producer
    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableDubbo
    public class DubboProducerApplication {
    public static void main(String[] args) {
    SpringApplication.run(DubboProducerApplication.class, args);
    }
    }
    • consumer
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @SpringBootApplication
    @EnableDubbo
    public class DubboConsumerApplication {

    public static void main(String[] args) {
    SpringApplication.run(DubboConsumerApplication.class, args);
    }

    }
  1. 示例搭建(Hello World)
  • 接口 DubboService构建
1
2
3
4
public interface DubboService {

String say();
}
  • 服务提供类实现
1
2
3
4
5
6
7
8
9
10
@org.apache.dubbo.config.annotation.DubboService
@Service
public class DubboServiceImpl implements DubboService {

@Override
public String say() {
return "dubbo producer";
}

}

注意:@Service 注解是 Srping 的注解,@org.apache.dubbo.config.annotation.Service 已在版本 2.7.7 被 @org.apache.dubbo.config.annotation.DubboService取代

  • 消费者实现
1
2
3
4
5
6
7
8
9
10
11
@RestController
public class TestController {

@DubboReference
private DubboService dubboService;

@RequestMapping("/test")
public String test(){
return dubboService.say();
}
}

dubbo 的服务消费应该在消费者中的 Service 层做整合消费后返回处理结果,这里仅为演示。

  1. 运行

先运行 provider 再运行 consumer,否则 dubbo 会因无法找到服务提供者自行关闭消费者。

  • nacos 的结果

  • 模拟调用

后记

虽然在网上已经有很多这方面的教程,但是大多是用 zookeeper 作为注册中心。
而个人喜欢接触新技术,nacos 在今年1月才由阿里开源出来。而且我比较喜欢 nacos 的界面风格,虽然 nacos 在功能上不如 zookeeper+dubboAdmin 强大,但是作为入门应该是足够的。

源码地址

本文所涉及代码都已上传到**github**

修改历史

  1. 2019 年 11 月 09 日
    1. 文章初始版本编写
  2. 2020 年 10 月 11 日
    1. 删减错误的描述
      1. 服务的消费方与生产方启动类都应添加 @EnableDubbo 注解,修改前为生产方启动类应添加 @EnableDubbo 注解
    2. 优化部分描述
      1. 第四点运行中的模拟调用不再使用 RestServices 进行调用,使用更为普遍的 postman 调用
    3. 优化 demo 的代码结构
      1. 使用 gradle 构建,修改前为 maven
      2. 使用多组件方式进行项目的构建,使 demo 不再局限于 demo,可成为更加常用的工具

本文首发于cartoon的博客

转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/dubbo/dubbo与springboot的结合/

前言

近段时间秋招上岸了,于是每天疯狂补各种分布式基础,每天都在痛苦与快乐中度过。
在学习 nginx 的时候,遇到配置上的问题:root 与 alias 的区别,卡了大概三个小时,记录下来警醒自己不要再犯了。

正文

在使用 “/” 进行配置时,两者没有区别,一样都是在 root 或者 alias 指定的路径寻找文件,所以以下的过程与结果都跟此无关。

  • 测试用例的构建

    1
    2
    3
    4
    5
    6
    7
    location /static2 {
    root /static;
    }

    location /static1 {
    alias /static;
    }
  • 图片示例及存放位置

2.jpeg : /static/

5.jpeg : /static/static2/

  • 启动 nginx 并输入 url

http://192.168.99.100:8091/static1/2.jpeg

http://192.168.99.100:8091/static2/5.jpeg

  • 结果

  • 结果分析
    从访问 url,映射关系 relation 以及文件位置 location 综合来看,可以得出以下规律:

2.jpeg : alias = location !=url

5.jpeg : root + location = url

  • 结论

个人认为,alias 起到一个文件路径重定向的功能,能有效隐藏文件真实路径。相对来说root 更像在指定 root 文件夹中寻找文件,文件路径树容易被猜测导致安全问题的发生。

    本文首发于 cartoon的博客

    转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/nginx/nginx中root与alias关键字的区别/

前言

这段时间上岸了,就有时间整理电脑的资料(强迫症重度患者),就向maven以及gradle的仓库位置动手了。

目的

改变maven的默认位置

步骤

  • 修改maven的配置文件setting.xml(maven安装位置:\conf)

将localRepository的标签值修改成想要设置的目录。

  • 复制修改后的setting.xml到仓库所在位置并重启

  • (非必须)修改idea中maven设置

    Setting->Build->Build Tools->Maven

修改圈起来的三个配置项就可以了

本文首发于cartoon的博客

转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/pm/修改maven包本地默认位置/

前言

近段时间在学dubbo,dubbo-admin死活装不上,无论是本地还是docker,所以把目光投向了其他配置中心,我选定的是阿里新开源的nacos。

正文

  • 拉取镜像到本地docker

    1
    docker pull nacos/nacos-server
  • 新建nacos容器

    1
    docker run --env MODE=standalone --name nacos -d -p 8848:8848 nacos/nacos-server

    其中env参数是指定容器所处环境,这里是指建立单机版的nacos。

  • 新建数据库用于节点以及数据的保存

    1
    create database nacos_config;

    其中数据库名自定义

  • 导入脚本
    可以到官网复制或者用我上传的脚本,提取码为jm6z

  • 修改nacos在conf的配置文件application.properties

主要修改的地方有:

db.url.0
db.url.1
db.user
db.password

由于我主要作为测试用的,所以没有设置主从数据库,注释了db.url.1

经过上面六步,nacos就能正常使用。

后记

这就是我配置nacos的历程。
因为nacos是阿里在19年1月才开源出来的,所以网上的资料不算多,大多都要翻官方文档,所以我就吃一下螃蟹。
而在开发连接nacos作为dubbo的配置中心中,我也遇到一点小坑,我打算把它放在另一篇文章中,还没动手写,所以应该会迟点出来。

本文首发于cartoon的博客

转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/docker/docker下配置nacos/

前言

说真的,平常看源码都是自己看完自己懂,很少有写出来的冲动。
但是在写算法的时候,经常用到java中各种集合,其中也比较常用到remove方法。
remove有重载函数,分别传入参数是索引index或者数据Object(指定泛型后自动转换),如果指定泛型是其他数据类型还好,但是指定的是Integer或者是int的话,或者就有点懵了。
这曾经也困惑过我,所以我就唯有用实践解惑了。

测试类设计

  • 测试类一

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Text {

    public void remove(int index){
    System.out.println("调用传参为int的remove方法");
    }

    public void remove(Integer object){
    System.out.println("调用传参为Integer的remove方法");
    }

    public void remove(Object object){
    System.out.println("调用传参为Object的remove方法");
    }
    }
  • 测试类二

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class Text {

    public void remove(Integer object){
    System.out.println("调用传参为Integer的remove方法");
    }

    public void remove(Object object){
    System.out.println("调用传参为Object的remove方法");
    }
    }
  • 测试类三

    1
    2
    3
    4
    5
    6
    public class Text {

    public void remove(Object object){
    System.out.println("调用传参为Object的remove方法");
    }
    }

结果

三个测试类分别传入int,Integer,Object型变量,观察效果。

  • 测试类一

    传入类型为int:调用传参为int的remove方法
    传入类型为Integer:调用传参为Integer的remove方法
    传入类型为Object:调用传参为Object的remove方法

  • 测试类二

    传入类型为int:调用传参为Integer的remove方法
    传入类型为Integer:调用传参为Integer的remove方法
    传入类型为Object:调用传参为Object的remove方法

  • 测试类三

    传入类型为int:调用传参为Object的remove方法
    传入类型为Integer:调用传参为Object的remove方法
    传入类型为Object:调用传参为Object的remove方法

从输出结果可以看出,当方法的传参的类层级逐渐变高时,层级较低的传参会进行向上转型适应传参的需要。

原因分析

下面我们先反编译各测试类的源码,结果如下

  • 测试类一

    invokevirtual #11 // Method remove:(I)V

    invokevirtual #15 // Method remove:(Ljava/lang/Integer;)V

    invokevirtual #18 // Method remove:(Ljava/lang/Object;)V

  • 测试类二

    invokevirtual #11 // Method remove:(Ljava/lang/Integer;)V

    invokevirtual #11 // Method remove:(Ljava/lang/Integer;)V

    invokevirtual #17 // Method remove:(Ljava/lang/Object;)V

  • 测试类三

    invokevirtual #10 // Method remove:(Ljava/lang/Object;)V

    invokevirtual #10 // Method remove:(Ljava/lang/Object;)V

    invokevirtual #10 // Method remove:(Ljava/lang/Object;)V

可以看出,反编译代码中都是调用实例方法的命令,所以结果中自动”向上转型”其实是jvm的功劳。jvm通过在编译时确定调用的传参类型,静态分派到具体方法的。
所以在前言中的困惑已经解除了,就是由于jvm中静态分派的实现,调用次序是int->Integer->Object。

后记

也没什么想说的,感觉在阅读源码的时候必须多想想为什么这样做,为什么要这样实现,同时通过断点或者反编译的手段找出自己的答案。keep going!

本文首发于cartoon的博客
转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/java/java的list接口的remove重载方法调用原理/

前言

在前后端交互的选择上,之前一直采用的是模板引擎(因为我只负责后端)。
而这次的一个算是作业吧,前后端都是我,所以就研究了一下JSON交互在java web的应用(主要是前端)。

优缺点

因为我是先写后端版本的,所以优缺点部分请跳转至JSON工具类的构建(后端版本)查看。

对比

因为我对js也是几天速成系列,所以框架型的jq以及ajax也是处于概念上的理解以及皮毛型的应用。
我所认识的主流的前端处理的ajax,所以就选用了ajax进行发送/接收解析json的处理。

需求拆分

因为没有打算深入学习前端,所以对需求的划分以及功能实现只限于满足这次的作业。
而我定义的功能/需求主要有两个

  • 外部函数只需进行传入接口url以及数据,接收并初步解析结果
  • 必须能进行get/post请求的发送以及接收
  • 只需要发送单实体数据

格式设计

因为是请求型报文,所以只需要形成普通的json格式

1
2
3
4
5
{
"name": "name",
"password":"password",
"account":"account"
}

代码设计

因为我希望传入接口url以及数据既能完成数据的发送与接收,而个人觉得ajax的请求响应参数中不同的只是url以及发送/接收数据不同了(限本次的需要),所以就在内部构建了一个通用的ajax的函数。

实际代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function transfer(url, data){
var result=new Array();
$.ajax({
type:"POST",
url:url,
dataType:"json",
async:false,
contentType:"application/json;charset=utf-8",
data:JSON.stringify(data),
success:function (dataReturn) {
var temp=JSON.stringify(dataReturn);
var dataObj = JSON.parse(temp);
result[0]=dataObj.status;
result[1]=dataObj.object;
}
});
return result;
}

代码思路

  1. 外部函数传入url以及数据data,data为对象类型的数据
  2. 构建ajax请求发送数据
  3. 因返回的json报文主要有两项:状态status以及数据object,所以采用数组型的返回结果
  4. 通过JSON.stringify以及JSON.parse将返回的json字符串转换成对象
  5. 根据键值拆分返回结果到3中定义的数组返回
  6. 调用函数通过result[0]判断获取操作结果,result[1]获取操作的详细数据

结果展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function clickLogin() {
var userName=$("#userName").val();
var password=$("#password").val();
var data={};
data['number']=userName;
data['password']=password;
var result=transfer("/text_project/login",data);
if(result[0]=='200'){
window.location.href="/text_project/main";
}
else{
console.log("error");
}
}

不足之处

  1. get请求的需求没有解决,曾经花了7个小时去研究解决,但是因为对js以及ajax不熟悉,知道问题所在,但是没有解决
  2. 不同结果的处理没有过多的处理
  3. 原本想构建一个统一的网络访问函数,但是由于get请求的未解决这个想法失败了

后记

因为这次时间赶,所以只是几天速成系列,只追求能用。
但是通过这次的速成,也学习到了怎么去快速掌握或满足自身需要的一些方法,也算是接触到不同方面,能在以后的JAVA后端的学习中有所对比借鉴吧。

相关链接

这是我在前台发送/接收解析json的一点经验,我另外写了一篇文章:JSON工具类的构建(后端版本),配合使用会很香哦~

本文首发于cartoon的博客
转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/json/json工具类的构建前端版本/

前言

在前后端交互的选择上,之前一直采用的是模板引擎(因为我只负责后端)。
而这次的一个算是作业吧,前后端都是我,所以就研究了一下JSON交互在java web的应用(主要是前端)。

优缺点

  • 前后端耦合

    • 模板引擎加载只是将jsp的交互方式移植到html上,前端文件格式改变了,但是jsp中前后端耦合的缺点没有改变。
    • json交互中,数据通过js/jquery动态加载在页面上,数据与页面进行分离,页面只是单纯用于展示。
  • 数据加载逻辑复用

    • 模板引擎的方式中,如果有很多相似的页面元素以及一样的数据返回格式,那只是复制粘贴大法了。
    • 在模板引擎的例子中,只需要定义一套数据加载模型,传入不同的页面元素id以及数据则能实现逻辑复用
  • 后端接口的复用
    因为我的学习路线的问题,所以我开发过安卓原生一段时间。

    • 模板引擎式的加载必须使用webView组件加载,且需另进行原生构建时接口也要另外构建。
    • json交互,谷歌爸爸鼓励使用json进行交互(一年多前的事,现在不清楚了),且安卓原生内置GJSON进行json解析与构建,所以可以在原生以及跨平台的构建有很好的平衡。

对比

既然选择了json交互的方式,而java官方据我所知是没有内置对json的支持,Spring在Controller的层面使用RestContrller注解实现对json的支持。
但是我个人强迫症很强,我对Contrller(或者说是Presenter)的定义是

  • 对用户访问的url作页面的映射
  • 对用户触发的事件进行数据的传递与返回
    正是这两点的定义,我需要在Service层组合统一格式的结果返回到上层,所以需要第三方json支持。
    可选择的JSON库有很多,GSON,FastJson,Jackson,根据对比,我选择为马老师充值一波。

需求拆分

我初步定义的需求主要有三个

  • Service传递统一处理结果到上层
  • 无论是单一数据实体或者List型数据处理的结果是一样的
  • 处理过程是独立的,不依赖于实体类的支持

格式设计

在网上看过很多后端返回数据的格式,很多都是返回一个处理的status以及具体的数据,而这个status是根据http状态码进行设定的,因为这次时间比较紧,所以我就采用了这个方案。

1
2
3
4
5
6
{
"status": "status",
"object": {

}
}

代码设计

因为我希望Controller能直接拿到结果,所以构建结果的过程全放在Result类中。
而结果构建我主要分为两种:只有状态码(通知处理结果)以及具有返回结果(数据显示),而根据结果的个人也分为两种:单个数据以及List型数据。

实际代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
@Getter
@Slf4j
public class ResultSet {

private JSONObject result;

public ResultSet(){
result=new JSONObject();
}

/**
* 初始化状态码
* @param status
*/
public void initStatus(String status){
result.put("status", status);
}

/**
* 初始化状态码以及返回数据
* @param status
* @param obj
*/
public void initData(String status,Object obj){
initStatus(status);
if(obj instanceof List){
List list=(List)obj;
JSONArray array=new JSONArray();
for(Object object:list){
array.add(putObjectToJSON(object));
}
result.put("object",array);
}
else {
result.put("object",putObjectToJSON(obj));
}
}

/**
* 将单个Object放入json文件中
* @param obj
* @return
*/
private JSONObject putObjectToJSON(Object obj){
JSONObject result=new JSONObject();
Field[] fields=obj.getClass().getDeclaredFields();
for(Field field:fields){
field.setAccessible(true);
String fieldName=field.getType().getSimpleName();
if(fieldName.equals("Department")||fieldName.equals("Job")){
JSONObject tempJson=new JSONObject();
try {
Object tempObject = field.get(obj);
Field[] tempFields=tempObject.getClass().getDeclaredFields();
for(Field tempField:tempFields){
tempField.setAccessible(true);
tempJson.put(tempField.getName(),tempField.get(tempObject));
tempField.setAccessible(false);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if(fieldName.equals("Department")){
result.put("depart",tempJson);
}
else {
result.put("job",tempJson);
}
}
else {
try {
result.put(field.getName(),field.get(obj));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
field.setAccessible(false);
}
return result;
}
}

代码思路

  1. Service通过initStatus/initData传入数据/状态码进行对象的生成
  2. Controller通过result的getter方法获取处理结果
  3. 单个数据以及List数据的处理
    1. 单个对象直接通过putObjectToJSON进行处理
    2. List数据通过对象类型判断,向下转型,遍历元素形成JSONArray进行处理,对元素处理的方法也是采用putObjectToJSON
  4. 嵌套对象的处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Employee {

    private int id;

    private Department depart;

    private Job job;

    private String name;

    ..................
    }
    1. 通过反射获取对象的所有成员变量类型以及对应的值
    2. 遇到上述bean类含有嵌套自定义对象时,递归生成json文件加入到结果json中

结果演示

​ (左为单object型,右为List型)

不足之处

  1. 状态码的设置应该采用枚举类的赋值,能更好的约束返回的状态码
  2. 在对象转换方面,应该采用配置扫描的方式。在配置文件中写入bean的包所在,在嵌套对象转换时通过扫描配置文件的信息判断
  3. 在嵌套对象的转换方面,只是做了一层的嵌套转换,更多层的没有考虑到,之后会再重构的
  4. 对异常处理方面,只是简单的输出错误信息。应该对错误信息进行进一步的处理

后记

因为这次时间比较赶,从项目的立项到成品的建立花了5天时间,所以注意到很多细节,但是没有去处理。
有想过之后有时间的话将这个工具类的细节完善起来,形成jar包供自己或者供开源。

相关连接

这只是后台对结果的统一处理,我另外写了一篇文章:JSON工具类的构建(前端版本),配合使用效果更佳哦~

本文首发于cartoon的博客
转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/json/json工具类的构建后端版本/

前言

​ 在研究java集合源码的时候,发现了一个很少用但是很有趣的点:Queue以及Deque,平常在写leetcode经常用LinkedList向上转型Deque作为栈或者队列使用,但是一直都不知道Queue的作用,于是就直接官方文档好了。

正文

概念

从上图看出,Queue以及Deque都是继承于Collection,Deque是Queue的子接口。

下面来看一下官方文档的解释。

A linear collection that supports element insertion and removal at both ends. The name deque is short for “double ended queue” and is usually pronounced “deck”. Most Deque implementations place no fixed limits on the number of elements they may contain, but this interface supports capacity-restricted deques as well as those with no fixed size limit.

A collection designed for holding elements prior to processing. Besides basic Collection operations, queues provide additional insertion, extraction, and inspection operations. Each of these methods exists in two forms: one throws an exception if the operation fails, the other returns a special value (either null or false, depending on the operation). The latter form of the insert operation is designed specifically for use with capacity-restricted Queue implementations; in most implementations, insert operations cannot fail.

从Deque的解释中,我们可以得知:Deque是double ended queue,我将其理解成双端结束的队列,双端队列,可以在首尾插入或删除元素。而Queue的解释中,Queue就是简单的FIFO队列。

所以在概念上来说,Queue是FIFO的单端队列,Deque是双端队列。

而在使用上,又有什么差别呢?

使用

从上图我们可以得知,Queue有一个直接子类PriorityQueue,而Deque中直接子类有两个:LinkedList以及ArrayDeque。

  • PriorityQueue

我觉得重点就在圈定的两个单词:无边界的,优先级的堆。然后再看看源码

在第一张图片的源码中,明显看到PriorityQueue的底层数据结构是数组,而无边界的形容,那么指明了PriorityQueue是自带扩容机制的,具体请看PriorityQueue的grow方法。

在第二张第三张图片中,可以看到插入元素的时候是需要经过compareTo的处理,那么最常用就是一些范围极值的输出,类似于堆排序的用法。

下面演示一下正反序输出三个元素的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static void negativePrint(int[] nums) {
PriorityQueue<Integer> queue=new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
for(int temp:nums){
queue.add(temp);
}
System.out.println();
System.out.print("倒序输出:");
for(int i=0;i<3;i++){
System.out.print(queue.poll()+" ");
}
}

private static void positivePrint(int[] nums){
PriorityQueue<Integer> queue=new PriorityQueue<>();
for(int temp:nums){
queue.add(temp);
}
System.out.print("正序输出:");
for(int i=0;i<3;i++){
System.out.print(queue.poll()+" ");
}
}
1
2
正序输出:1 2 3 
倒序输出:9 8 8

这个在一些排行榜或者输入第N个最大/小元素会比较常用。

  • LinkedList以及ArrayDeque

从官方解释来看,ArrayDeque是无初始容量的双端队列,LinkedList则是双向链表。而我们还能看到,ArrayDeque作为队列时的效率比LinkedList要高,而在栈的使用场景下,无疑具有尾结点不需判空的LinkedList较高效。

下面演示ArrayDeque作为队列以及LinkedList作为栈的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static void usingAsQueue() {
Deque<Integer> queue=new ArrayDeque<>();
System.out.println("队列为空:"+queue.isEmpty()); //判断队列是否为空
queue.addLast(12); //添加元素
System.out.println("队列为空:"+queue.isEmpty()); //判断队列是否为空
System.out.println(queue.peekFirst()); //获取队列首部元素
System.out.println(queue.pollFirst()); //获取并移除栈顶元素
System.out.println("队列为空:"+queue.isEmpty()); //判断队列是否为空
}

private static void usingAsStack() {
//作为栈使用
Deque<Integer> stack=new LinkedList<>();
System.out.println("栈为空:"+stack.isEmpty()); //判断栈是否为空
stack.addFirst(12);
System.out.println("栈为空:"+stack.isEmpty()); //判断栈是否为空
System.out.println(stack.peekFirst()); //获取栈顶元素
System.out.println(stack.pollFirst()); //获取并移除栈顶元素
System.out.println("栈为空:"+stack.isEmpty()); //判断栈是否为空
System.out.println("============================================");
}

栈为空:true
栈为空:false
12
12

栈为空:true

队列为空:true
队列为空:false
12
12
队列为空:true

小提示

在Deque中,获取并移除元素的方法有两个,分别是removeXxx以及peekXxx。

存在元素时,两者的处理都是一样的。但是当Deque内为空时,removeXxx会直接抛出NoSuchElementException,而peekXxx则会返回null。

所以无论在实际开发或者算法时,推荐使用peekXxx方法

其实ArrayDeque和LinkedList都可以作为栈以及队列使用,但是从执行效率来说,ArrayDeque作为队列以及LinkedList作为栈使用会是更好的选择。

另外,我在leetcode看到有人采用Vector下的Stack,这个同步加锁粒度过大(对象级),另外我觉得算法中没有线程同步的需要吧。

  • 小结

PriorityQueue可以作为堆使用,而且可以根据传入的Comparator实现大小的调整,会是一个很好的选择。

ArrayDeque通常作为栈或队列使用,但是栈的效率不如LinkedList高。

LinkedList通常作为栈或队列使用,但是队列的效率不如ArrayQueue高。

总结

在java中,Queue被定义成单端队列使用,Deque被定义成双端队列使用。

而由于双端队列的定义,Deque可以作为栈或者队列使用,而Queue只能作为队列或者依赖于子类的实现作为堆使用。

本文首发于cartoon的博客
转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/java/queue%E4%B8%8Edeque%E7%9A%84%E5%8C%BA%E5%88%AB/

前序

在看HashMap源码的时候,看到HashMap的hash函数里面有用到>>>的运算符,之前经常在除2操作用到>>运算符,但是还是第一次看到>>>,于是就来记录一下。

情景复现

hashMap的hash函数源码

hashMap的hash函数源码

因为里面主要是先获取key的hashCode,这是jvm生成的,所以我单独用1模拟hashCode

1
System.out.println((h = 1) ^ (h >>> 16));

结果如下

1
情景复现: 1

步骤解析

这段代码主要由三段代码运算而成

  1. h直接赋值为1
  2. h>>>16位
  3. 步骤1与步骤2的异或运算

所以从结果推断出1>>>16结果为0

测试

1
2
3
4
System.out.println("-12>>>2结果为:"+Integer.toBinaryString(-12>>>2));
System.out.println("12>>>2结果为:"+Integer.toBinaryString(12>>>2));
System.out.println("-12>>2结果为:"+Integer.toBinaryString(-12>>2));
System.out.println("12>>2结果为:"+Integer.toBinaryString(12>>2));
1
2
3
4
-12>>>2结果为:111111111111111111111111111101
12>>>2结果为:11
-12>>2结果为:11111111111111111111111111111101
12>>2结果为:11

分析

从结果可以看出,利用正数做操作时,>>与>>>结果没有变化,但是负数的操作时发生了变化,这个对比证明了>>>的操作与符号位有关。

  • 正数操作

由于位运算时,利用Integer.toBinaryString方法输出不会输出前置0,所以可以推断两个运算符的操作都是右移n位补0

  • 负数操作

由于负数存储的是它的补码,在进行>>运算的时候明显看到生成的二进制字符串补1,且长度比正数运算的时候要长,java在二进制中是不区分符号位的,所以最后的十进制表示的数会异常大。

总结

两个运算符,在进行正数移位的时候操作是一样的。但是在处理负数时,>>补1,是带符号操作的。而>>>补0,是无符号操作的。

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/java/无符号运算符与有符号运算符的区别/