开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

leetcode 147 Insertion Sort L

发表于 2021-11-26

「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战」

描述

Given the head of a singly linked list, sort the list using insertion sort, and return the sorted list’s head.

The steps of the insertion sort algorithm:

  • Insertion sort iterates, consuming one input element each repetition and growing a sorted output list.
  • At each iteration, insertion sort removes one element from the input data, finds the location it belongs within the sorted list and inserts it there.
  • It repeats until no input elements remain.

The following is a graphical example of the insertion sort algorithm. The partially sorted list (black) initially contains only the first element in the list. One element (red) is removed from the input data and inserted in-place into the sorted list with each iteration.

Example 1:

1
2
ini复制代码Input: head = [4,2,1,3]
Output: [1,2,3,4]

Example 2:

1
2
ini复制代码Input: head = [-1,5,3,4,0]
Output: [-1,0,3,4,5]

Note:

1
2
kotlin复制代码The number of nodes in the list is in the range [1, 5000].
-5000 <= Node.val <= 5000

解析

根据题意,给定一个单向链表 head ,使用插入排序对链表进行排序,并返回排序后的列表 head 。

插入排序算法的步骤:

  • 插入排序进行迭代,每次重复消耗一个输入元素并生成一个排序的输出列表
  • 在每次迭代中,插入排序从输入数据中删除一个元素,在排序列表中找到它所属的位置并将其插入到那里
  • 重复直到没有输入元素剩余

题目给出的插入排序比较晦涩,其实考察的内容很基础,就是对链表节点的搜索和插入,我们每遍历一个新的节点,找到其在已经排好序的前面的链表中应该插入的位置,进行节点的插入操作即可。关键是要维护好需要用的不同指针,别被绕晕了就行。

解答

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
python复制代码class ListNode(object):
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution(object):
def insertionSortList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if not head: return head
L = ListNode(val=-10000)
L.next = curr = head
while curr.next:
pre = L
if curr.next.val>=curr.val:
curr = curr.next
continue
while pre.next.val<curr.next.val:
pre = pre.next
tmp = curr.next
curr.next = tmp.next
tmp.next = pre.next
pre.next = tmp
return L.next

运行结果

1
2
erlang复制代码Runtime: 172 ms, faster than 81.63% of Python online submissions for Insertion Sort List.
Memory Usage: 17.2 MB, less than 15.65% of Python online submissions for Insertion Sort List.

原题链接:leetcode.com/problems/in…

您的支持是我最大的动力

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

头脑风暴之最近的请求次数

发表于 2021-11-26

「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战」

题目:

写一个 RecentCounter 类来计算特定时间范围内最近的请求。

请你实现 RecentCounter 类:

RecentCounter() 初始化计数器,请求数为 0 。
int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
保证 每次对 ping 的调用都使用比之前更大的 t 值。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
scss复制代码输入:
["RecentCounter", "ping", "ping", "ping", "ping"]
[[], [1], [100], [3001], [3002]]
输出:
[null, 1, 2, 3, 3]

解释:
RecentCounter recentCounter = new RecentCounter();
recentCounter.ping(1); // requests = [1],范围是 [-2999,1],返回 1
recentCounter.ping(100); // requests = [1, 100],范围是 [-2900,100],返回 2
recentCounter.ping(3001); // requests = [1, 100, 3001],范围是 [1,3001],返回 3
recentCounter.ping(3002); // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3

解决思路

题目意思就是往队列里存放元素啊,每次ping就存一个数,然后存的时候看一下之前的数是否在t-3000之外了,是就删除;

比如题目给的例子:

输入:
[“RecentCounter”, “ping”, “ping”, “ping”, “ping”]
[[], [1], [100], [3001], [3002]]
输出:
[null, 1, 2, 3, 3]
就是第一次存个1,第二次存个100……

然后每次存的时候,看看之前存的元素,有多少个在这次存的 t 到t-3000之间;

存1的时候,1在[-2999,1]之间,就这一个数,所以说返回数目1;
……

存3002的时候就删除1,因为1不在[2,3002]之间,而100,3001,3002都是在[2,3002]之间,就返回数目3;

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码class RecentCounter {
Queue<Integer> q;
public RecentCounter() {
q = new LinkedList();
}

public int ping(int t) {
q.add(t);
while (q.peek() < t - 3000) {
q.poll();
}
return q.size();
}
}

最后

  • 时间复杂度:O(Q),其中 QQ 是 ping 的次数。
  • 空间复杂度:O(W),其中 W = 3000 是队列中最多存储的 ping 的记录数目。

**往期文章:

  • 好看的图表怎么画,看完这几个 API 你就会了
  • 二叉树刷题总结:二叉搜索树的属性
  • 二叉树总结:二叉树的属性
  • 二叉树总结:二叉树的修改与构造
  • StoreKit2 有这么香?嗯,我试过了,真香
  • 看完这篇文章,再也不怕面试官问我如何构造二叉树啦!
  • 那帮做游戏的又想让大家氪金,太坏了!
  • 手把手带你撸一个网易云音乐首页 | 适配篇
  • 手把手带你撸一个网易云音乐首页(三)
  • 手把手带你撸一个网易云音乐首页(二)
  • 手把手带你撸一个网易云音乐首页(一)
  • 代码要写注释吗?写你就输了
  • Codable发布这么久我就不学,摸鱼爽歪歪,哎~就是玩儿
  • iOS 优雅的处理网络数据,你真的会吗?不如看看这篇
  • UICollectionView 自定义布局!看这篇就够了

请你喝杯 ☕️ 点赞 + 关注哦~

  1. 阅读完记得给我点个赞哦,有👍 有动力
  2. 关注公众号— HelloWorld杰少,第一时间推送新姿势

最后,创作不易,如果对大家有所帮助,希望大家点赞支持,有什么问题也可以在评论区里讨论😄~**

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

『十倍程序员』SpringBoot接入kafka最简教程

发表于 2021-11-26

「这是我参与11月更文挑战的第24天,活动详情查看:2021最后一次更文挑战」。

点赞再看,养成习惯👏👏

前言

Hello 大家好,我是l拉不拉米,今天的『十倍程序员』系列给大家分享SpringBoot项目如何快速接入kafka。

上集回顾

上集《『十倍程序员』Docker部署kafka+zookeeper》中,我们已经学会了通过docker-compose将kafka环境搭建起来,现在我们要通过Springboot的项目接入kafka,实现消息的生产和消费。

Springboot接入kafka

1、创建Springboot工程

image.png

2、Pom文件引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
xml复制代码<!-- 必须 -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>

<!-- 下面三个依赖包非必须,方便演示用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>

3、application.yml配置文件

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
yml复制代码# 应用服务 WEB 访问端口
server:
port: 8080

# 应用名称
spring:
application:
name: kafka
kafka:
bootstrap-servers: 127.0.0.1:9092 # kafka集群信息,多个节点通过“,”分隔
producer: # 生产者配置
retries: 3 # 设置大于0的值,则客户端会将发送失败的记录重新发送
batch-size: 16384 # 16K
buffer-memory: 33554432 # 32M 缓冲区大小
acks: 1 # 应答级别:多少个分区副本备份完成时向生产者发送ack确认(可选0、1、all/-1)
# 指定消息key和消息体的编解码方式
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
consumer: # 消费者配置
group-id: testGroup # 消费者组
enable-auto-commit: false # 关闭自动提交
# 当kafka中没有初始offset或offset超出范围时将自动重置offset
# earliest:重置为分区中最小的offset;
# latest:重置为分区中最新的offset(消费分区中新产生的数据);
# none:只要有一个分区不存在已提交的offset,就抛出异常;
auto-offset-reset: latest
max-poll-records: 50 # 批量消费每次最多消费多少条消息
# 指定消息key和消息体的编解码方式
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
listener:
missing-topics-fatal: false # 消费端监听的topic不存在时,项目启动会报错(关掉)
type: batch # 批量消费

4、生产者

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
java复制代码@Slf4j
@RestController
public class KafkaProducer {

private final KafkaTemplate<String, Object> kafkaTemplate;

@Autowired
public KafkaProducer(KafkaTemplate<String, Object> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}

@PostMapping("/message")
public void sendMessage(@RequestBody User user) {
kafkaTemplate.send("user", user).addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
@Override
public void onFailure(Throwable throwable) {
log.info("sendMessage onFailure:{}", throwable.getMessage());
}

@Override
public void onSuccess(SendResult<String, Object> result) {
log.info("sendMessage onSuccess:{},{},{}", result.getRecordMetadata().topic(), result.getRecordMetadata().partition(), result.getRecordMetadata().offset());
}
});
}
}

5、消费者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码@Slf4j
@Component
public class KafkaConsumer {

@KafkaListener(topics = {"user"})
public void userListener(ConsumerRecords<String, String> payload) {
if (payload.isEmpty()) {
return;
}

payload.partitions().forEach(tp -> {
List<ConsumerRecord<String, String>> tpRecords = payload.records(tp);
tpRecords.forEach(record -> {
final User user = JSON.parseObject(record.value(), User.class);
log.info("userListener -> user = {}", user);
});
});
}
}

6、Postman调用接口生产消息

image.png

查看日志

image.png

接入完毕。

总结

通过这两篇文章,我们发现通过docker容器化和Springboot的高效化,简单化接入kafka的能力,整个开发过程不到半个小时。对比传统方式需要大半天的时间,可以说实现了『十倍程序员』的水平。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

RocketMQ API的入门及使用 API使用步骤 API

发表于 2021-11-26

「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战」

API使用步骤

在使用MQ原生API的时候,生产者和消费者的使用分为下面的几个步骤

生产者

  1. 创建生产者(Producer),需要指定生产者名
  2. 指定NameServer地址,没有指定的话,则从操作系统中读取NAMESRV_ADDR 配置项地址
  3. 启动生产者(Producer)
  4. 构建消息体(Message),其中包含:Topic,Tag及消息体内容
  5. 发送消息体
  6. 关闭生产者
1
2
3
4
5
6
7
8
9
10
11
12
java复制代码//1.创建生产者
DefaultMQProducer producer = new DefaultMQProducer("PRODUCER_GROUP");
//2.设置NameAddr地址
producer.setNamesrvAddr("192.168.0.201:9876");
//3.启动生产者
producer.start();
//4.构建消息体
Message message = new Message("TopicTest", "test1", "message".getBytes());
//5.发送消息
SendResult send = producer.send(message);
//6.关闭生产者
producer.shutdown();

消费者

  1. 创建消费者(Consumer),并指定消费者组名
  2. 指定NameServer地址,没有指定的话,则从操作系统中读取NAMESRV_ADDR 配置项地址
  3. 启动消费者
  4. 监听Topic,设定回调函数,接受消息后并处理消息
  5. 启动消费者(Consumer)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码//1.创建消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CONSUMER_GROUP");
//2.设置NameAddr地址
consumer.setNamesrvAddr("192.168.0.201:9876");
//3.设置订阅的Topic,及过滤消息条件
consumer.subscribe("TopicTest", "test1");
//4.设置消息从哪里开始拉取
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
//5.设置监听回调函数
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgList, ConsumeConcurrentlyContext context) {
//6.接受消息后处理
for (MessageExt messageExt : msgList) {
System.out.println("消息id:" + messageExt.getMsgId() + ",消息体:" + messageExt.getBody());
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//7.启动消费者
consumer.start();

API使用样例

生产者

基础消息

单向发送

生产者将消息发送给MQ后,无需关心发送MQ的情况,就直接执行后面的操作逻辑。

image.png

  • 代码实现
1
java复制代码producer.sendOneway(message);

同步发送

生产者将消息发送给MQ后,等待MQ返回结果后,在执行后面的流程操作,这种发送方式可以知道是否发送MQ成功,能够知道发送结果,如果发送失败,生产者可以重试发送或者走异常逻辑。

image.png

  • 代码实现
1
java复制代码SendResult result = producer.send(message);

通过SendResult可以拿到发送的结果信息
image.png

异步发送

生产者将消息发送给MQ后,不需要等待MQ返回结果,而是去执行后面的逻辑,生产者会提供一个接口供MQ调用,MQ将消息处理成功后通过指定的接口回调给生产者。

image.png

  • 代码实现
1
2
3
4
5
6
7
8
java复制代码producer.send(message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
}
@Override
public void onException(Throwable e) {
}
});

image.png

顺序消息

在采用默认方式发送消息的时候,客户端会采用轮询的方式将消息发送到不同的MessageQueue中,消费者拉取消息的时候是跟MessageQueue绑定在一起的,一个MessageQueue对应的消费者只会有一个客户端,所以在发送消息的时候,将需要局部有序的消息发送到同一个MessageQueue上去,这样的话消费者在消费时候拉取到的消息就是局部有序。
而消费者在拿取消息的时候需要从多个MessageQueue中拿取消息,这个时候生产者发生消息的时候是保证每个队列有序的,但是多个队列之间的数据是无序的,如果需要保证局部有序,就需要消费者拿取数据的时候按队列一个个取数据,取完一个队列数据后在去取下一个队列的数据,这个的话就可以保证消费者拿取的消息局部有序。这时候消费者就需要注册MessageListenerOrderly监听器,这样在拉取数据的时候就会保证一个一个队列去取数据,从而保证消息的局部有序,MessageListenerConcurrently这个监听器每次从多个队列中拿去一批消息给客户端,无法保证消息有序。
RocketMQ的顺序消息只能够保证局部有序,如果需要保证消息的全局有效性,只能够将消息队列个数设置为1个,这样的话就会保证全局有序,但是性能低下,只有一个实例能够处理消息。

  • 代码实现

实现MessageQueueSelector接口,自定义MessageQueue选择的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码for (int i = 0; i < 10; i++) {
Message message = new Message("TopicTest", "test1", String.valueOf(i), "message".getBytes());
SendResult result = producer.send(message, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
Integer i = (Integer) arg;
int index;
if (i < 5) {
index = 0;
} else {
index = 1;
}
return mqs.get(index);
}
}, i);
}

image.png

广播消息

消息者在广播模式下,所有的消费者都可以消费到这个消息,集群模式下,同一个组的消费者,每一条消息只能够被同一个组下的某个消费者消费,而广播模式则是将消息发送给每一个消费者去消费消息。广播消息播跟生产者没有关系,只跟消费者的消费模式有关系

  • 代码实现

将消费者的消息模式设为广播模式即可,其他代码一样,默认集群模式

1
java复制代码consumer.setMessageModel(MessageModel.BROADCASTING);

延迟消息

MQ在收到消息后不会将消息立即发送给消费者,而是延迟指定的时间才将消息发送给消费者,从而达到消息延迟的作用。在开源的版本中,延迟消息只支持18个消息的延迟级别

messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

  • 代码实现

生产者发送消息的时候告诉MQ延迟级别

1
java复制代码 message.setDelayTimeLevel(2);

批量消息

将多条消息合并成一个消息体发送给MQ,这样的话可以减少网络带宽,提高吞吐量。发送批量消息的限制比较多:

  • 一个批量消息的大小不能大于1MB
  • 批量消息的Topic必须一致
  • 延迟消息,事务消息不支持批量消息

官方提供的API中,如果消息大于最大值,可以采用:ListSplitter 将消息体拆分成批量消息后在发送
代码实现:

1
2
3
4
5
6
java复制代码List<Message> batchList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Message message = new Message("TopicTest", "test1", "message".getBytes());
batchList.add(message);
}
SendResult result = producer.send(batchList);

image.png

过滤消息

过滤消息最常用的是:采用Tag来区分消息,消费者可以只拉取自己需要的消息,RocketMQ官方提供了类似sql语法一样的来过滤消息,通过MessageSelector.bySql()来实现消息的过滤,采用SQL过滤只支持推模式,拉模式不支持SQL过滤,过滤消息的语法按照SQL92标准执行,支持下面的语法:

  • 数值比较:>,>=,<,<=,BETWEEN等
  • 字符比较: <>,IN 等
  • 逻辑运算:AND,OR,NOT等

代码实现

消费者设定过滤规则

1
2
3
4
5
6
7
8
java复制代码// tag 过滤消息
consumer.subscribe("TopicTest", "test1 || test2");

//SQL 过滤,id 值不能为空
consumer.subscribe("TopicTest",MessageSelector.bySql("id is not null"));

//生产者发生消息,需要设置用户属性,相当于数据库索引
message.putUserProperty("id", String.valueOf(i));

事务消息

事务消息可以保证本地事务与消息发送两个操作的原子性,要么全部成功,要么全部失败。事务消息不支持延迟消息和批量消息,通过两阶段提交的思路来设计,事务消息只跟发送者有关系,跟消费者没有任何关系,对于消费者是无感的。实现机制原理图参考下图:

image.png

  • 代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码TransactionMQProducer producer = new TransactionMQProducer();
producer.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
//执行本地事务
return LocalTransactionState.COMMIT_MESSAGE;
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
//MQ回查本地事务结果
return LocalTransactionState.COMMIT_MESSAGE;
}
});
Message message = new Message("TopicTest", "test1", "message".getBytes());
//发送half消息
producer.sendMessageInTransaction(message, null);
producer.start();

消费者

拉模式

消费者主动从Broker上拉取消息,需要消费者端维护拉取消息的点位

  • 代码实现

实现类:DefaultLitePullConsumer 由这个类来实现,很多客户端需要维护的信息,官方API已经帮忙完成

1
2
3
4
5
6
7
java复制代码DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer("CONSUMER_GROUP");
litePullConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
litePullConsumer.subscribe("TopicTest", "*");
litePullConsumer.start();
while (running) {
List<MessageExt> messageList = litePullConsumer.poll();
}

image.png

推模式

Broker收到消息后,主动将消息推送给消费者,消费者只需要在第一次接入的时候设置拉取消息的点位,后续拉取消息不需要维护点位,由MQ来维护推消息的点位
ConsumeFromWhere枚举值:

  • CONSUME_FROM_LAST_OFFSET:从上次消费点开始消费
  • CONSUME_FROM_FIRST_OFFSET:从队列的最开始消费
  • CONSUME_FROM_TIMESTAMP:指定时间戳位置开启拉取
  • 代码实现

实现类:DefaultMQPushConsumer

1
2
3
4
5
6
7
8
9
java复制代码consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgList, ConsumeConcurrentlyContext context) {
for (MessageExt messageExt : msgList) {
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

netty(十五)Netty提升 - 协议设计以及常见协议介

发表于 2021-11-26

「这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战」

一、什么是协议?

在计算机网络与信息通信领域里,人们经常提及“协议”一词。互联网中常用的具有代表性的协议有IP、TCP、HTTP等。

简单来说,协议就是计算机与计算机之间通过网络实现通信时事先达成的一种“约定”。这种“约定”使那些由不同厂商的设备、不同的CPU以及不同的操作系统组成的计算机之间,只要遵循相同的协议就能够实现通信。

二、协议的必要性

通常,我们发送一封电子邮件、访问某个主页获取信息时察觉不到协议的存在,只有在我们重新配置计算机的网络连接、修改网络设置时才有可能涉及协议。

这就好比两个人使用不同国家的语言说话,怎么也无法相互理解。也可以比喻成没有断句的话,有多种不同的解释,双方可能会产生分歧。协议可以分为很多种,每一种协议都明确地界定了它的行为规范。两台计算机之间必须能够支持相同的协议,并遵循相同协议进行处理,这样才能实现相互通信。

TCP/IP 中消息传输基于流的方式,没有边界。

协议的目的就是划定消息的边界,制定通信双方要共同遵守的通信规则。

三、常见协议示例

3.1 redis协议

比如我们要发送消息给redis,需要遵从其协议。
比如我们要发送set key value 形式的命令,则我们首先要发送整个命令的长度,然后分别发送命令每个位置的长度,如下所示:

1
arduino复制代码set name Tom

则需要按照如下的协议发送:

1
2
3
4
5
6
7
swift复制代码*3 //命令长度
$3 //set长度
set //命令内容
$4 //key的长度
name //key的内容
$3 //value的长度
Tom //value的内容

我们使用如下代码连接redis并发送上面的内容:

创建一个客户端,连接本地redis:

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
scss复制代码public class RedisProtocol {
// 回车换行
static final byte[] LINE = {13, 10};

public static void main(String[] args) {
NioEventLoopGroup worker = new NioEventLoopGroup();

try {
ChannelFuture channelFuture = new Bootstrap().group(worker)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
//连接建立后触发以下方法,发送内容
@Override
public void channelActive(ChannelHandlerContext ctx) {
ByteBuf buf = ctx.alloc().buffer();
buf.writeBytes("*3".getBytes());
buf.writeBytes(LINE);
buf.writeBytes("$3".getBytes());
buf.writeBytes(LINE);
buf.writeBytes("set".getBytes());
buf.writeBytes(LINE);
buf.writeBytes("$4".getBytes());
buf.writeBytes(LINE);
buf.writeBytes("name".getBytes());
buf.writeBytes(LINE);
buf.writeBytes("$3".getBytes());
buf.writeBytes(LINE);
buf.writeBytes("Tom".getBytes());
buf.writeBytes(LINE);
ctx.writeAndFlush(buf);
}
//接收redis返回值
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println(byteBuf.toString(StandardCharsets.UTF_8));
}
});
}
}).connect("127.0.0.1", 6379);

channelFuture.sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
worker.shutdownGracefully();
}
}
}

验证:

1
2
arduino复制代码127.0.0.1:0>get name
"Tom"

3.2 http协议

下面我们演示下netty当中解析http请求的示例代码。因为http协议我们自己实现太过复杂,所以我们直接使用netty提供的http编解码器:HttpServerCodec:

1
scala复制代码public final class HttpServerCodec extends CombinedChannelDuplexHandler<HttpRequestDecoder, HttpResponseEncoder> implements SourceCodec

如上图所示,我们发现这个类继承自CombinedChannelDuplexHandler,同时实现了编码和解码的功能。

下面我们提供个一个服务端代码,通过浏览器访问,看看最终能收到浏览器请求内容是什么样的:

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
scss复制代码public class HttpProtocolServer {

public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup(1);
NioEventLoopGroup worker = new NioEventLoopGroup();

try {
ChannelFuture channelFuture = new ServerBootstrap()
.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) {
nioSocketChannel.pipeline().addLast(new HttpServerCodec());
nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(msg);
System.out.println("****************");
System.out.println(msg.getClass());
super.channelRead(ctx, msg);
}
});
}
}).bind(8080);
channelFuture.sync();
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}

浏览器访问http://localhost:8080/index.html,得到如下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
makefile复制代码DefaultHttpRequest(decodeResult: success, version: HTTP/1.1)
GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="92", " Not A;Brand";v="99", "Google Chrome";v="92"
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
****************
class io.netty.handler.codec.http.DefaultHttpRequest
EmptyLastHttpContent
****************
class io.netty.handler.codec.http.LastHttpContent$1

分析如上内容,发现得到了很多http请求的参数内容,以及共接收到两个类型的类,分别是:DefaultHttpRequest 和 LastHttpContent,也就是说,http请求会给我们两部分的内容,分别是请求和请求体,这里无论是get还是post,都是如此。

所以,当我们想要处理一个http请求的话,需要对这两个请求进行判断去分别处理:

1
2
3
4
5
java复制代码if (msg instanceof HttpRequest){
// TODO do something...
}else if(msg instanceof HttpContent){
// TODO do something...
}

也可以使用netty提供的SimpleChannelInboundHandler的针对单一类型的入站处理器去处理请求:

1
2
3
4
5
6
typescript复制代码 nioSocketChannel.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpRequest httpRequest) {

}
});

处理处理请求,一个正常的http请求还需要返回响应,我们用DefaultFullHttpResponse最为响应。这个类需要需要指定请求协议的version,以及请求的状态。

除此之外,我们给请求增加上响应内容:Hello World!

完整代码如下所示:

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
scss复制代码public class HttpProtocolServer {

public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup(1);
NioEventLoopGroup worker = new NioEventLoopGroup();

try {
ChannelFuture channelFuture = new ServerBootstrap()
.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) {
nioSocketChannel.pipeline().addLast(new HttpServerCodec());
nioSocketChannel.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpRequest httpRequest) {
System.out.println(httpRequest.uri());

//创建响应
DefaultFullHttpResponse response =
new DefaultFullHttpResponse(httpRequest.protocolVersion(), HttpResponseStatus.OK);

byte[] bytes = "<h1>Hello World!<h1>".getBytes(StandardCharsets.UTF_8);
//添加响应内容长度,否则会浏览器会一直处于加载状态
response.headers().setInt(CONTENT_LENGTH, bytes.length);
//添加响应内容
response.content().writeBytes(bytes);

//写入响应
channelHandlerContext.channel().writeAndFlush(response);
}
});
//
// nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
// @Override
// public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// System.out.println(msg);
// System.out.println("****************");
// System.out.println(msg.getClass());
//
// if (msg instanceof HttpRequest) {
// // TODO do something...
// } else if (msg instanceof HttpContent) {
// // TODO do something...
// }
// super.channelRead(ctx, msg);
// }
// });
}
}).bind(8080);
channelFuture.sync();
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}

浏览器请求http://localhost:8080/index.html,查看结果:

1
2
bash复制代码/index.html
/favicon.ico

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

OpenCV、NumPy和Matplotlib直方图比较

发表于 2021-11-26

「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战」

前言

使用 OpenCV 提供的 cv2.calcHist() 函数可以用来计算直方图。此外,NumPy 和 Matplotlib 同样也为创建直方图提供了类似的函数。出于提高性能目的,我们来比较这些函数,使用 OpenCV、NumPy 和 Matplotlib 创建直方图,然后测量每个直方图计算的执行时间并将结果绘制在图形中,比较不同库中计算直方图的不同效率。

OpenCV、NumPy和Matplotlib灰度直方图比较

使用 timeit.default_timer 测量执行时间,因为它会自动提供系统平台和 Python 版本上可用的最佳时钟,为了使用 timeit.default_timer 测量执行时间,首先需要将其导入:

1
python复制代码from timeit import default_timer as timer

可以使用以下方法计算程序的执行时间:

1
2
3
4
python复制代码start = timer()
# 程序执行
end = timer()
execution_time = start - end

考虑到 default_timer() 测量值可能会受到同时运行的其他程序的影响。因此,获取准确计时的最佳方法是重复数次并使用最佳时间。当让,我们也可以采用其他方法来测量更加精确的时间,例如多次重复运行取平均值,或者其他更加有效避免异常的方法,在这里为了简单起见,我们重复数次并使用最佳时间。

而为了计算和比较直方图,我们需要使用以下函数:

  1. OpenCV 提供 cv2.calcHist() 函数
  2. NumPy 提供的 np.histogram() 函数
  3. Matplotlib 提供的 plt.hist() 函数

用于计算上述每个函数的执行时间的代码如下所示。首先,导入所需库:

1
2
3
4
python复制代码import numpy as np
import cv2
from matplotlib import pyplot as plt
from timeit import default_timer as timer

然后编写可视化函数,在同一图中显示运行结果,进行更好的对比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
python复制代码def show_img_with_matplotlib(color_img, title, pos):
img_RGB = color_img[:, :, ::-1]

ax = plt.subplot(1, 4, pos)
plt.imshow(img_RGB)
plt.title(title)
plt.axis('off')

def show_hist_with_matplotlib_gray(hist, title, pos, color):
ax = plt.subplot(1, 4, pos)
plt.title(title)
plt.xlabel("bins")
plt.ylabel("number of pixels")
plt.xlim([0, 256])
plt.plot(hist, color=color)

plt.figure(figsize=(18, 6))
plt.suptitle("Comparing histogram (OpenCV, numpy, matplotlib)", fontsize=14, fontweight='bold')

接下来,就是程序的最关键部分了,使用不同库中计算直方图的方法,并获取程序运行时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ini复制代码image = cv2.imread('example.png')
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 计算 cv2.calcHist() 执行时间
start = timer()
hist = cv2.calcHist([gray_image], [0], None, [256], [0, 256])
end = timer()
# 乘以1000将单位转换为毫秒
exec_time_calc_hist = (end - start) * 1000

# 计算 np.histogram() 执行时间
start = timer()
hist_np, bin_np = np.histogram(gray_image.ravel(), 256, [0, 256])
end = timer()
exec_time_np_hist = (end - start) * 1000

# 计算 plt.hist() 执行时间
start = timer()
# 调用 plt.hist() 计算直方图
(n, bins, patches) = plt.hist(gray_image.ravel(), 256, [0, 256])
end = timer()
exec_time_plt_hist = (end - start) * 1000

最后,我们绘制灰度图及其直方图:

1
2
3
4
5
6
python复制代码how_img_with_matplotlib(cv2.cvtColor(gray_image, cv2.COLOR_GRAY2BGR), "gray", 1)
show_hist_with_matplotlib_gray(hist, "grayscale histogram (OpenCV)-" + str('% 6.2f ms' % exec_time_calc_hist), 2, 'm')
show_hist_with_matplotlib_gray(hist_np, "grayscale histogram (Numpy)-" + str('% 6.2f ms' % exec_time_np_hist), 3, 'm')
show_hist_with_matplotlib_gray(n, "grayscale histogram (Matplotlib)-" + str('% 6.2f ms' % exec_time_plt_hist), 4, 'm')

plt.show()

OpenCV、NumPy和Matplotlib灰度直方图比较

由上面实例可以看出,cv2.calcHist() 的执行速度比 np.histogram() 和 plt.hist() 都快。因此,出于性能考虑,在计算图像直方图时可以使用 OpenCV 函数。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

laravel的地址管理restful api 一、地址管理

发表于 2021-11-26

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

一、地址管理

1.1 创建地址模型以及迁移文件

运行命令php artisan make:model Address -m
在这里插入图片描述
在迁移文件中创建表结构:

1
2
3
4
5
6
7
8
9
10
11
php复制代码Schema::create('addresses', function (Blueprint $table) {
$table->id();
$table->integer('user_id')->comment('用户id');
$table->string('name')->comment('收获人');
$table->integer('city_id')->comment('city表中的id');
$table->string('address')->comment('详细地址');
$table->string('phone')->comment('手机号');
$table->tinyInteger('is_default')->default(0)->comment('默认地址 0=>不是默认,1=>是默认');
$table->timestamps();
$table->index('user_id'); // 查询索引
});

在这里插入图片描述
运行命令php artisan migrate:
在这里插入图片描述


1.2 创建地址验证类

运行命令php artisan make:request Web/AddressRequest:
在这里插入图片描述
写入:

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
php复制代码    public function rules()
{
return [
'name' => 'required',
'city_id' => [
'required',
function ($attribute, $value, $fail) {
$city = City::find($value);
if (empty($city)) $fail('城市不存在');
},
],
'address' => 'required',
'phone' => 'required|regex:/^1[3-9]\d{9}$/',
];
}

/**
* 提示消息
*/
public function messages() {
return [
'name.required' => '收货人必填',
'city_id.required' => '地址不能为空',
'address.required' => '详细地址不能为空',
'phone.required' => '手机号不能为空',
];
}

在这里插入图片描述


1.3 配置地址transform创建地址资源API控制器

给模型配置可批量插入的字段:

1
2
php复制代码    use HasFactory;
protected $fillable = ['user_id','name', 'phone', 'address', 'city_id', 'is_default'];

在这里插入图片描述


在City.php模型中写入查找父级的联动:

1
2
3
4
5
6
php复制代码    /**
* 查找父类
*/
public function parent() {
return $this->belongsTo(City::class, 'pid', 'id');
}

在这里插入图片描述

在辅助函数中写入根据区id查找上级联动数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
php复制代码    /**
* 通过区ID 查询完整省市区数据
*/
if (!function_exists('city_name')) {
function city_name($city_id) {
$city = City::where('id', $city_id)->with('parent.parent.parent')->first();
// $str = $city['parent']['parent']['parent']['name'] ?? '';
// $str .= $city['parent']['parent']['name'] ?? '';
// $str .= $city['parent']['name'] ?? '';
// $str .= $city['name'] ?? '';
$str = [
$city['parent']['parent']['parent']['name'] ?? '',
$city['parent']['parent']['name'] ?? '',
$city['parent']['name'] ?? '',
$city['name'] ?? ''
];

return trim(implode(' ', $str));
}
}

在这里插入图片描述

创建AddressTransformer.php写入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
php复制代码<?php 

namespace App\Transformers;

use App\Models\Address;
use League\Fractal\TransformerAbstract;

class AddressTransformer extends TransformerAbstract {
public function transform(Address $address) {
return [
'id' => $address->id,
'name' => $address->name,
'city_id' => $address->city_id,
'city_name' => city_name($address->city_id),
'phone' => $address->phone,
'address' => $address->address,
'is_default' => $address->is_default,
'created_at' => $address->created_at,
'updated_at' => $address->updated_at,
];
}
}

在这里插入图片描述

运行命令php artisan make:controller Web/AddressController --api
在这里插入图片描述
写入增删改查以及是否默认方法:

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
php复制代码<?php

namespace App\Http\Controllers\Web;

use App\Http\Controllers\BaseController;
use App\Http\Requests\Web\AddressRequest;
use App\Models\Address;
use App\Transformers\AddressTransformer;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class AddressController extends BaseController
{
/**
* 我的地址列表
*/
public function index()
{
$address = Address::where('user_id', auth('api')->id())->get();
return $this->response->collection($address, new AddressTransformer());
}

/**
* 添加地址
*/
public function store(AddressRequest $request)
{
Address::create([
'user_id' => auth('api')->id(),
'name' => $request->input('name'),
'phone' => $request->input('phone'),
'address' => $request->input('address'),
'city_id' => $request->input('city_id'),
]);
return $this->response->created();
}

/**
* 地址详情
*/
public function show(Address $address)
{
return $this->response->item($address, new AddressTransformer());
}

/**
* 更新地址
*/
public function update(AddressRequest $request, Address $address)
{
$address->update([
'user_id' => auth('api')->id(),
'name' => $request->input('name'),
'phone' => $request->input('phone'),
'address' => $request->input('address'),
'city_id' => $request->input('city_id'),
'is_default' => $request->input('is_default')
]);
return $this->response->noContent();
}

/**
* 删除地址
*/
public function destroy(Address $address)
{
$address->delete();
return $this->response->noContent();
}

/**
* 默认地址
*/
public function isDefault(Address $address)
{
if ($address->is_default == 1) {
return $this->response->noContent(); // 如果改地址已经设置默认地址 直接返回
}
try {
DB::beginTransaction(); // 开启事物, 防止一个设置成功一个设置失败
// 先把所有的地址都设置为非默认
$default_address = Address::where('user_id', auth('api')->id())
->where('is_default', 1)
->first();
if (!empty($default_address)) {

$default_address->is_default = 0;
$default_address->save();
}
// 在把当前的设置成默认
$address->is_default = 1;
$address->save();
DB::commit();
return $this->response->noContent();
} catch (\Exception $e) {
DB::rollback();
throw $e;
}
}
}

1.4 配置地址路由

1
2
3
4
php复制代码 // 地址增删改查
$api->resource('address', AddressController::class);
// 设置默认地址
$api->patch('address/{address}/default', [AddressController::class, 'isDefault']);

在这里插入图片描述


1.5 订单地址修改

由于之前没有写地址相关的api,地址那块是模拟的假数据,现在把它修改过来:

1
2
3
4
php复制代码// 地址数据
$address = Address::where('user_id', auth('api')->id())
->orderBy('is_default', 'desc')
->get();

在这里插入图片描述


还有一处地方就是在提交订单时候验证地址是否存在:

1
php复制代码'address_id' => 'required|exixts:addresses,id'

在这里插入图片描述


1.6 测试效果

1、添加地址
在这里插入图片描述

2、地址列表

在这里插入图片描述

3、修改地址
在这里插入图片描述

4、地址详情
在这里插入图片描述
5、删除地址
在这里插入图片描述
6、设置默认地址
在这里插入图片描述

在学习的php的路上,如果你觉得本文对你有所帮助的话,那就请关注点赞评论三连吧,谢谢,你的肯定是我写博的另一个支持。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

推荐学Java——数据表操作

发表于 2021-11-26

说明

上节内容学习了数据库 MySQL 的安装、验证、数据库管理工具、数据库的基本操作命令,还没有学习的同学可以从主页去看上一篇推送内容

本节内容就学习有关数据库中表的操作,这其中包括 表内 和 表关联 的:创建、列数据类型、数据查询、筛选、删除、添加、修改等等操作,这块内容极为重要,重点不但需要掌握基本的 SQL使用规则,还要掌握一些系统提供的SQL函数。

提示:你可以将sql理解为一种编程语言格式,那么这其中用到的所有标点符号都是英文的,括号都是成对出现的,这种低级错误千万别给自己养成”习惯”。

表操作知识点梳理.png

表(CRUD)

1,创建表

1
2
3
sql复制代码create table 表名(
列名 列的类型(长度) 列的约束,
列名2 列的类型(长度) 列的约束);
  • 列的类型

int:和 Java中相同

char:固定长度(长度指的是字符个数)

varchar:可变长度(长度指的是字符个数)

double:

float:

boolean:

date:YYYY-MM-DD

time:hh:mm:ss

datetime:YYYY-MM-DD hh:mm:ss,默认值是 null

timestamp:YYYY-MM-DD hh:mm:ss,默认使用当前时间

text:主要用来存放文本。

blob:存放二进制。

  • 列的约束

主键约束:primary key

唯一约束:unique

非空约束:not null

举例,创建学生表

  1. 登录MySql
1
sql复制代码mysql -uroot -p密码
  1. 进入指定数据库(如果没有数据库,则利用前面的创建数据库语句进行创建)
1
sql复制代码use 数据库名字;

这两步是必须的,我们的表都是在库的基础上才有的,所以在创建表之前我们需要选对要使用的数据库。

1,创建学生表

1
2
3
4
5
6
sql复制代码create table student(
sid int primary key,
sname varchar(30),
ssex int,
sage int
);

2,查看表

1
sql复制代码show tables;

3,查看表的创建过程

1
sql复制代码show create table 表名;

4,查看表结构

1
sql复制代码desc 表名;

5,修改表

  • 添加列(add)
1
2
sql复制代码alter table 表名 add 列名 列的类型 列的约束
alter table student add chengji int not null;
  • 修改列(modify)
1
sql复制代码alter table 表名 modify 列名 列的类型(长度);
  • 修改列名(change)
1
sql复制代码alter table 表名 change 原列名 新列名 列名的类型(长度);
  • 删除列(drop)
1
sql复制代码alter table 表名 drop chengji;
  • 修改表名(rename)
1
sql复制代码rename table 原表名 to 新表名;
  • 修改表的字符集
1
sql复制代码alter table 表名 character set 字符集名称;

6,删除表

1
sql复制代码drop table 表名;

表内、表与表之间的 SQL

1,插入数据

1
sql复制代码insert into 表名(列名1,列名2,列名3...) values (列名1对应的值,列名2对应的值,列名3对应的值);

批量插入

1
sql复制代码insert into student values(103,'lunzima',1,18),(104,'houyi',0,20),(105,'hanbin',1,22);

批量插入效率高于单条插入,但是批量插入其中一条如果出错,可能引起同批插入的其他条数据错误。

注:

  • 如果是全列名插入,则可以省略表名后的列名不写,例如下面这样:
1
sql复制代码insert into student values(100,'yasuo',0,25);
  • 设置了主键的列,插入时要保证主键不重复。
  • 插入中文乱码解决方法

在 MySql 的安装目录(比如我的是:C:\Program Files\MySQL\MySQL Server 5.5)下,找到my.ini文件,打开后编辑default-character-set=gbk这句代码即可,默认是utf8

然后在命令行重新登录账户,操作即可。

  • 部分列插入时,列名不能省略。
1
sql复制代码insert into 表名(要插入的列1,列2...) values(对应列的值...);

2,查询表中记录

1
sql复制代码select * from 表名;
  • 查看表中指定列的数据
1
sql复制代码select 列名1,列名2 from 表名;
  • 别名as查询
1
sql复制代码select 表名的别名.列名1,表名的别名.列名2 from 表名 as 表名的别名;

别名as是可以省略的。也可以给列名加别名,像下面这样

1
sql复制代码select 表名的别名.列名1 列名1的别名,表名的别名.列名2 列名2的别名 from 表名 表名的别名;
  • 去重查询
1
2
sql复制代码// 查询表中某列数据,并去掉重复值
select distinct 列名 from 表名;
  • select 运算查询
1
sql复制代码select *,列名*0.85 from 表名;

这里的运算符可以是+,-,*,/。增加的列也可以添加别名;增加的列仅仅是在查询结果上显示,不会真正改变表中的结构。

  • where后的条件写法

关系运算符:>,>=,<,<=,!=,<>

1
2
3
sql复制代码select * from 表名 where 列名 关系运算符 限定条件的值;
// 比如这样
select * from product where price <> 500;

其中,!=不是标准的 SQL 语法,<>才是标准的不等于。

逻辑运算符:and,or,no

1
2
3
4
sql复制代码// 举例
select * from product where price > 10 and price < 2000;
// 上面这句和下面这句等价
select * from product where price between 11 and 1999;

模糊查询:like

+ `_`:代表一个字符。
+ `%`:代表多个字符。
1
2
3
4
sql复制代码// 查询商品名字中带有'代码'两个字的所有商品
select * from product where pname like '%代码%';
// 查询商品名字中第二个字符是'一'的所有商品
select * from product where panme like '_一%';

在某个范围获得值:in

1
2
sql复制代码// 查出商品编号为 3 和 5 的所有商品
select * from product where cno in (3,5);
  • 排序查询:order by

最后执行,对select的结果进行操作。

+ `asc`:升序(默认排序方式)
+ `desc`:降序
1
2
3
4
sql复制代码// 按照某列进行排序
select * from 表名 order by 列名 desc/asc;
// 结合 where 条件的结果,在进行排序
select * from 表名 where 列名 条件运算符 条件值 order by desc/asc;
  • 聚合函数
+ `sum()`:求和
+ `avg()`:求平均值
+ `max()`:最大值
+ `min()`:最小值
+ `count()`:统计数量
1
2
3
4
5
6
7
8
9
10
sql复制代码// 求和
select sum(要求和的列名) from 表名;
// 求平均值
select avg(要求平均值的列名) from 表名;
// 求最大值
select max(要求最大值的列名) from 表名;
// 求最小值
select min(要求最小值的列名) from 列名;
// 统计数量
select count(表中任意一列或者直接写`*`) from 表名;

注意:聚合函数不能直接跟在where后面。

1
2
sql复制代码// 比如:查出价格大于平均值的所有商品
select * from product where price > (select avg(price) from product);
  • 分组:group by

将表中某列值相同的记录放在一起,称为一组。

1
2
3
4
sql复制代码// 按照某列去分组,对于列名相同的记录默认会显示排在前面的
select * from 表名 group by 列名;
// 按照某列排序,并统计该列的数量
select 列名,count(列名) from 表名 group by 列名;
  • having

条件筛选。出现在分组之后,其后可以接聚合函数。where关键字出现在分组之前,其后不可接聚合函数。

1
2
sql复制代码// 比如:查询商品表中,按照商品编号分组显示每组的平均价格,并查询平均价格大于 60元的所有商品。
select *,avg(price) from product group by cno having avg(price) > 60;

3,删除表中记录

  • 删除指定某条记录
1
2
3
sql复制代码delete from 表名 where 条件;
// 例如
delete from student where sid=105;

注:如果不指定条件,则会将表中的数据一条一条全部删除。

  • truncate 和 delete from 表名; 删除表中数据有何区别?

前者是将表直接删除,然后重新创建表,表中无数据。后者是一条一条删除表中所有数据。

在数据量较少的情况下,后者效率高;反之,前者高。

4,更新表中记录

  • 更新某条记录
1
sql复制代码update 表名 set 要更新的列名1=列的值1,要更新的列名2=列的值2 where 条件;

比如这样:

1
sql复制代码update student set sname='寒冰',sage=23 where sid=105;
  • 更新所有记录的某些列
1
sql复制代码update 表名 set 要更新的列1=值1,要更新的列2=值2;

总结

  • 表的操作,根据个人职位和功能需求来定,一个完整体系表的构建是一个具体业务的逻辑体现。
  • 大多开发者在表内和表与表之间进行操作的时候多,其中最主要也是用的做多的操作是查询和筛选。

小编特意创建了一个公众号:推荐学java,分享与java相关的内容,并且以原创为主,欢迎大家搜索关注,一起学Java!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

FastAPI 入门系列 之 初识FastAPI!

发表于 2021-11-26

这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战。

我在刚开始接触Python Web 的时候,一直用 Flask 框架写接口服务,直到去年,看到了 FastAPI 框架,尝试着使用了一下,瞬间感觉还不错,用起来很顺畅,经过一段时间的了解和体验,后来决定在工作的正式项目中使用,正式运用在了生产环境,感觉没什么大问题。前段时间总结了 Flask 框架的一些使用教程,FastAPI 当然也不能落下,接下就一起来了解一下 FastAPI 的世界。

简介

FastAPI 是 一个 用来构建 API 服务的高性能 Web 框架,目前仅支持 Python 3.6+ 以上版本,是建立在 Pydantic和 Starlette 基础上的,Pydantic 是一个基于 Python 类型提示来定义数据验证、序列化和文档(使用JSON模式)的库;Starlette 是一个轻量级的 ASGI 框架/工具包,非常适合构建高性能异步服务。FastAPI 还吸收了其他 Web 框架的优点,相当于站立在了巨人的肩膀上,因此FastAPI 拥有开发效率高、性能优越、API开发标准化等优点。

简单使用

可以使用以下命令对 FastAPI 进行进行安装。

1
2
python复制代码pip install fastapi
pip install uvicorn

uvicorn 是一个基于 uvloop 和 httptools 构建的高性能的 ASGI 服务器,为FastAPI 应用提供服务器运行支持,也可以将其与 Gunicorn 结合使用,以拥有异步多进程服务器。

新建一个main.py文件,编写如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
python复制代码from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def hello():
return {"message": "Hello FastAPI!"}


@app.get("/name")
async def hello_name(name: str):
return {"Hello": name}

如果想要定义成异步方法的话,直接在定义函数时在前面添加async关键字即可。
然后在命令行执行以下命令启动服务:

1
python复制代码uvicorn main:app --reload

image.png

其中main文件名,app 定义的FastAPI 实例名称,–reload 代表热重启(热加载),代码改变后自动重启服务器,默认运行在8000端口,可以使用--port来指定其他端口。

打开浏览器访问 http://127.0.0.1:8000 ,将会看见以下 JSON 响应信息:

1
json复制代码{"message":"Hello FastAPI!"}

另外 FastAPI 自带 API 交互文档,我们可以通过以下地址查看接口文档。

  • Swagger UI 风格:http://127.0.0.1:8000/docs

image.png

  • ReDoc 风格:http://127.0.0.1:8000/redoc

image.png

使用交互文档可以方便的进行接口调试,交互体验上也是无比的友好,这绝对是一大优点。

image.png

总结

如果想要快速搭建 API 服务,FastAPI 确实会是一个不错的选择。但是,FastAPI 也绝不是像很多人盲目吹捧的那样,什么“可与 NodeJS 和 Go 比肩的极高性能”,那倒还不至于,只能说是一个优秀的 Python Web 框架,加上营销运营的确实不错,然后火了起来,但是项目仍存在很多的问题没有解决,希望越来越好吧。

原创不易,如果小伙伴们觉得有帮助,麻烦点个赞再走呗~

最后,感谢女朋友在工作和生活中的包容、理解与支持 !

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

历史上最伟大的一次 Git 代码提交

发表于 2021-11-26

「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战」

Git 是一个分布式版本控制系统,缔造者是大名鼎鼎的林纳斯·托瓦茲 (Linus Torvalds),Git 最初的目的是为了能更好的管理 Linux 内核源码。


PS:为了能够帮助更多的 Java 爱好者,已将《Java 程序员进阶之路》开源到了 GitHub(本篇已收录)。该专栏目前已经收获了 715 枚星标,如果你也喜欢这个专栏,觉得有帮助的话,可以去点个 star,这样也方便以后进行更系统化的学习:

github.com/itwanger/to…

每天看着 star 数的上涨我心里非常的开心,希望越来越多的 Java 爱好者能因为这个开源项目而受益,而越来越多人的 star,也会激励我继续更新下去~

大家都知道,Linux 内核是开源的,参与者众多,到目前为止,共有两万多名开发者给 Linux Kernel 提交过代码。

但在 1991 年到 2002 年期间,Linus 作为项目的管理员并没有借助任何配置管理工具,而是以手工方式通过 patch 来合并大家提交的代码。

倒不是说 Linus 喜欢手工处理,而是因为他对代码版本管理工具非常挑剔,无论是商用的 clearcase,还是开源的 CVS、SVN 都入不了他的法眼。

直到 2002 年,Linus 才相中了一款分布式版本控制系统 BitKeeper,虽然是商用的,但 BitKeeper 愿意让 Linux 社区免费使用,这让 Linus 非常开心和满意。

时间来到 2005 年,由于 BitKeeper 提供的默认接口不能满足 Linux 社区用户的全部需要,一位开发者在未经允许的情况下反编译了 BitKeeper 并利用了未公开的接口,于是 BitKeeper 的著作权拥有者拉里·麦沃伊就气愤地收回了 Linux 社区免费使用的权力。

没办法,Linus 只好自己硬着头皮上了。他对新的版本控制系统制订了若干目标:

  • 速度
  • 设计简单
  • 允许成千上万个并行开发的分支
  • 完全分布式
  • 有能力高效管理类似 Linux 内核一样的超大规模项目

结果,令人意想不到的是,Linus 只用了 10 天时间就用 C语言完成了第一个版本,嗯。。神就是神。并且给这个版本起了一个略带嘲讽意味的名字——Git(在英式英语俚语中表示“不愉快的人”)。

源代码的自述文件有进一步的阐述:

The name “git” was given by Linus Torvalds when he wrote the very first version. He described the tool as “the stupid content tracker” and the name as (depending on your way)

从 Git 的设计上来看,有两种命令:分别是底层命令(Plumbing commands)和高层命令(Porcelain commands)。一开始,Linus 只设计了一些给开源社区的黑客们使用的符合 Unix KISS 原则的命令,因为黑客们本身就是动手高手,水管坏了就撸起袖子去修理,因此这些命令被称为 plumbing commands。

Linus 在提交了第一个 git commit 后,就向社区发布了 git 工具。当时,社区中有位叫 Junio Hamano 的开发者觉得这个工具很有意思,便下载了代码,结果发现一共才 1244 行代码,这更令他惊奇,也引发了极大的兴趣。Junio 在邮件列表与 Linus 交流并帮助增加了 merge 等功能,而后持续打磨 git,最后 Junio 完全接手了 Git 的维护工作,Linus 则回去继续维护 Linux Kernel 项目。

Junio Hamano 觉得 Linus 设计的这些命令对于普通用户不太友好,因此在此之上,封装了更易于使用、接口更精美的高层命令,也就是我们今天每天使用的 git add, git commit 之类。Git add 就是封装了 update-cache 命令,而 git commit 就是封装了 write-tree, commit-tree 命令。

如果选历史上最伟大的一次 Git 代码提交,那一定是这 Git 工具项目本身的第一次代码提交。这次代码提交无疑是开创性的,如果说 Linux 项目促成了开源软件的成功并改写了软件行业的格局,那么 Git 则是改变了全世界开发者的工作方式和写作方式。

如今,Git 已经成为全球软件开发者的标配。

原本的 Git 只适用于 Unix/Linux 平台,但随着 Cygwin、msysGit 环境的成熟,以及 TortoiseGit 这样易用的GUI工具,Git 在 Windows 平台下也逐渐成熟。

PS1:Cygwin 的主要目的是通过重新编译,将 POSIX 系统(例如Linux、BSD,以及其他Unix系统)上的软件移植到Windows上。

PS2:msysGit 前面的 4 个字幕来源于 MSYS 项目,而 MSYS 又源于 MinGW(Minimalist GNU for Windows,最简GNU工具集),通过增加了一个由bash提供的shell环境以及其他相关工具软件,组成了一个最简系统(Minimal System),利用MinGW提供的工具,以及Git针对MinGW的一个分支版本,可以在Windows平台为Git编译出一个原生应用,结合MSYS就组成了msysGit。

Git 和传统的版本控制工具 CVS、SVN 有不小的区别,前者关心的是文件的整体性是否发生了改变,后两者更关心文件内容上的差异。

Git 存储项目随时间改变的快照

除此之外,Git 更像是一个文件系统,每个使用它的主机都可以作为版本库,并且不依赖于远程仓库而离线工作。开发者在本地就有历史版本的副本,因此就不用再被远程仓库的网络传输而束缚。

Git 中的绝大多数操作都只需要访问本地文件和资源,一般不需要来自网络上其它计算机的信息。因为在本地磁盘上就有项目的完整历史,所以 Git 的大部分操作看起来就像是在瞬间完成的。

在多人协作的情况下,Git 可以将本地仓库复制给其他开发者,那些发生改变的文件可以作为新增的分支被导入,再与本地仓库的进行分支合并。

如果你希望后面的学习更顺利,请记住 Git 这三种状态:

  • 已提交(committed),表示数据已经安全的保存在本地数据库中
  • 已修改(modified),表示修改了文件,但还没保存到数据库中
  • 已暂存(staged),表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中

由此引入了 Git 的三个工作区域:

  • Git 仓库,用来保存项目的元数据和对象数据库
  • 工作目录,对项目的某个版本进行独立提取
  • 暂存区域,保存了下次将提交的文件列表信息,也可以叫“索引”

Git 的工作流程是这样的:

  • 在工作目录中修改文件
  • 暂存文件,将文件的快照放入暂存区域
  • 提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录

接下来,我们来看一下 Git 的安装,Linux 和 Windows 系统的安装大家可以到 Git 官网上查看安装方法,上面讲的非常详细。

git-scm.com/downloads

我个人使用的 macOS 系统,可以直接使用 brew install git 命令安装,非常方便。

安装成功后,再使用 git --version 就可以查看版本号了,我本机上安装的是 2.23.0 版本。


参考资料:

维基百科:zh.wikipedia.org/wiki/Git
ProGit:www.progit.cn/
hutusi:改变世界的一次代码提交

这是《Java 程序员进阶之路》专栏的第 73 篇(记得去点个 star 哦)。该专栏风趣幽默、通俗易懂,对 Java 爱好者极度友好和舒适😄,内容包括但不限于 Java 基础、Java 集合框架、Java IO、Java 并发编程、Java 虚拟机、Java 企业级开发(Maven、Git、SSM、Spring Boot)等核心知识点。

github.com/itwanger/to…

让我们一起成为更好的 Java 工程师吧!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…179180181…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%