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

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


  • 首页

  • 归档

  • 搜索

集合的生产力工具类:Collections

发表于 2021-11-06

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

Collections 是 JDK 提供的一个工具类,位于 java.util 包下,提供了一系列的静态方法,方便我们对集合进行各种骚操作,算是集合框架的一个大管家。

PS:star 这种事,只能求,不求没效果,铁子们,《Java 程序员进阶之路》在 GitHub 上已经收获了 556 枚星标,铁子们赶紧去点点了,冲 600 star!

github.com/itwanger/to…

还记得我们前面讲过的 Arrays 工具类吗?可以回去温习下。

Collections 的用法很简单,在 Intellij IDEA 中敲完 Collections. 之后就可以看到它提供的方法了,大致看一下方法名和参数就能知道这个方法是干嘛的。

为了节省大家的学习时间,我将这些方法做了一些分类,并列举了一些简单的例子。

01、排序操作

  • reverse(List list):反转顺序
  • shuffle(List list):洗牌,将顺序打乱
  • sort(List list):自然升序
  • sort(List list, Comparator c):按照自定义的比较器排序
  • swap(List list, int i, int j):将 i 和 j 位置的元素交换位置

来看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("沉默王四");
list.add("沉默王五");
list.add("沉默王六");

System.out.println("原始顺序:" + list);

// 反转
Collections.reverse(list);
System.out.println("反转后:" + list);

// 洗牌
Collections.shuffle(list);
System.out.println("洗牌后:" + list);

// 自然升序
Collections.sort(list);
System.out.println("自然升序后:" + list);

// 交换
Collections.swap(list, 2,4);
System.out.println("交换后:" + list);

输出后:

1
2
3
4
5
css复制代码原始顺序:[沉默王二, 沉默王三, 沉默王四, 沉默王五, 沉默王六]
反转后:[沉默王六, 沉默王五, 沉默王四, 沉默王三, 沉默王二]
洗牌后:[沉默王五, 沉默王二, 沉默王六, 沉默王三, 沉默王四]
自然升序后:[沉默王三, 沉默王二, 沉默王五, 沉默王六, 沉默王四]
交换后:[沉默王三, 沉默王二, 沉默王四, 沉默王六, 沉默王五]

02、查找操作

  • binarySearch(List list, Object key):二分查找法,前提是 List 已经排序过了
  • max(Collection coll):返回最大元素
  • max(Collection coll, Comparator comp):根据自定义比较器,返回最大元素
  • min(Collection coll):返回最小元素
  • min(Collection coll, Comparator comp):根据自定义比较器,返回最小元素
  • fill(List list, Object obj):使用指定对象填充
  • frequency(Collection c, Object o):返回指定对象出现的次数

来看例子:

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码System.out.println("最大元素:" + Collections.max(list));
System.out.println("最小元素:" + Collections.min(list));
System.out.println("出现的次数:" + Collections.frequency(list, "沉默王二"));

// 没有排序直接调用二分查找,结果是不确定的
System.out.println("排序前的二分查找结果:" + Collections.binarySearch(list, "沉默王二"));
Collections.sort(list);
// 排序后,查找结果和预期一致
System.out.println("排序后的二分查找结果:" + Collections.binarySearch(list, "沉默王二"));

Collections.fill(list, "沉默王八");
System.out.println("填充后的结果:" + list);

输出后:

1
2
3
4
5
6
7
css复制代码原始顺序:[沉默王二, 沉默王三, 沉默王四, 沉默王五, 沉默王六]
最大元素:沉默王四
最小元素:沉默王三
出现的次数:1
排序前的二分查找结果:0
排序后的二分查找结果:1
填充后的结果:[沉默王八, 沉默王八, 沉默王八, 沉默王八, 沉默王八]

03、同步控制

HashMap 是线程不安全的,这个我们前面讲到了。那其实 ArrayList 也是线程不安全的,没法在多线程环境下使用,那 Collections 工具类中提供了多个 synchronizedXxx 方法,这些方法会返回一个同步的对象,从而解决多线程中访问集合时的安全问题。

使用起来也非常的简单:

1
java复制代码SynchronizedList synchronizedList = Collections.synchronizedList(list);

看一眼 SynchronizedList 的源码就明白了,不过是在方法里面使用 synchronized 关键字加了一层锁而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;

final List<E> list;

SynchronizedList(List<E> list) {
super(list);
this.list = list;
}

public E get(int index) {
synchronized (mutex) {return list.get(index);}
}

public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
}

那这样的话,其实效率和那些直接在方法上加 synchronized 关键字的 Vector、Hashtable 差不多(JDK 1.0 时期就有了),而这些集合类基本上已经废弃了,几乎不怎么用。

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
java复制代码public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);

return elementData(index);
}

public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);

int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--elementCount] = null; // Let gc do its work

return oldValue;
}
}

正确的做法是使用并发包下的 CopyOnWriteArrayList、ConcurrentHashMap。这些我们放到并发编程时再讲。

04、不可变集合

  • emptyXxx():制造一个空的不可变集合
  • singletonXxx():制造一个只有一个元素的不可变集合
  • unmodifiableXxx():为指定集合制作一个不可变集合

举个例子:

1
2
3
java复制代码List emptyList = Collections.emptyList();
emptyList.add("非空");
System.out.println(emptyList);

这段代码在执行的时候就抛出错误了。

1
2
3
4
php复制代码Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at com.itwanger.s64.Demo.main(Demo.java:61)

这是因为 Collections.emptyList() 会返回一个 Collections 的内部类 EmptyList,而 EmptyList 并没有重写父类 AbstractList 的 add(int index, E element) 方法,所以执行的时候就抛出了不支持该操作的 UnsupportedOperationException 了。

这是从分析 add 方法源码得出的原因。除此之外,emptyList 方法是 final 的,返回的 EMPTY_LIST 也是 final 的,种种迹象表明 emptyList 返回的就是不可变对象,没法进行增伤改查。

1
2
3
4
5
java复制代码public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}

public static final List EMPTY_LIST = new EmptyList<>();

05、其他

还有两个方法比较常用:

  • addAll(Collection<? super T> c, T... elements),往集合中添加元素
  • disjoint(Collection<?> c1, Collection<?> c2),判断两个集合是否没有交集

举个例子:

1
2
3
4
5
java复制代码List<String> allList = new ArrayList<>();
Collections.addAll(allList, "沉默王九","沉默王十","沉默王二");
System.out.println("addAll 后:" + allList);

System.out.println("是否没有交集:" + (Collections.disjoint(list, allList) ? "是" : "否"));

输出后:

1
2
3
css复制代码原始顺序:[沉默王二, 沉默王三, 沉默王四, 沉默王五, 沉默王六]
addAll 后:[沉默王九, 沉默王十, 沉默王二]
是否没有交集:否

整体上,Collections 工具类作为集合框架的大管家,提供了一些非常便利的方法供我们调用,也非常容易掌握,没什么难点,看看方法的注释就能大致明白干嘛的。

不过,工具就放在那里,用是一回事,为什么要这么用就是另外一回事了。能不能提高自己的编码水平,很大程度上取决于你到底有没有去钻一钻源码,看这些设计 JDK 的大师们是如何写代码的,学会一招半式,在工作当中还是能很快脱颖而出的。

恐怕 JDK 的设计者是这个世界上最好的老师了,文档写得不能再详细了,代码写得不能再优雅了,基本上都达到了性能上的极致。

可能有人会说,工具类没什么鸟用,不过是调用下方法而已,但这就大错特错了:如果要你来写,你能写出来 Collections 这样一个工具类吗?

这才是高手要思考的一个问题。

这是《Java 程序员进阶之路》专栏的第 64 篇。Java 程序员进阶之路,风趣幽默、通俗易懂,对 Java 初学者极度友好和舒适😘,内容包括但不限于 Java 语法、Java 集合框架、Java IO、Java 并发编程、Java 虚拟机等核心知识点。

github.com/itwanger/to…

亮白版和暗黑版的 PDF 也准备好了呢,一起成为更好的 Java 工程师,冲!

本文转载自: 掘金

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

Java中的SOLID原则

发表于 2021-11-06

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

前言

Robert C. Martin提出了5条面向对象的设计原则,并将其缩写为SOLID。这个首字母缩写词的每一个字母都在谈论Java中的原则。当以一种组合的方式使用所有的SOLID原则时,就会更容易开发出易于管理的软件。

Robert C. Martin,世界级编程大师,设计模式和敏捷开发先驱,敏捷联盟首任主席,C++ Report前主编。20世纪70年代初成为职业程序员,后创办Object Mentor公司并任总裁。后辈程序员亲切地称之为“Bob大叔”。

Robert C. Martin
SOLID什么意思?


如上所述,SOLID代表了Java的五个原则,分别是:

  • S:单一职责原则
  • O:开闭原则
  • L:里氏替换原则
  • I:接口隔离原则
  • D:依赖倒置原则

本期内容小黑会深入讨论每个原则。首先考虑第一个原则,即单一职责原则。

单一职责原则(SRP)

单一职责原则规定只能有一个原因可以修改类。如果有多个原因要修改类,那么应该根据功能将类重构为多个类。

为了便于理解,可以通过下面的代码。假设现在有一个Customer类。

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
java复制代码import java.util.List;

public class Customer {
String name;
int age;
long bill;
List<Item> listsOfItems;
Customer(String name,int age){
this.name=name;
this.age=age;
}
// 计算账单不应该是Customer的责任
public long calculateBill(long tax){
for (Item item:listsOfItems) {
bill+=item.getPrice();
}
bill+=tax;
this.setBill(bill);
return bill;
}
// 生成报告也不应该是Customer的责任
public void generateReport(String reportType) {
if(reportType.equalsIgnoreCase("CSV")){
System.out.println("Generate CSV report");
}
if(reportType.equalsIgnoreCase("XML")){
System.out.println("Generate XML report");
}
}
// 省略getter,setter
}

上面这个例子有以下问题:

  • 如果账单的计算逻辑有任何变化,那么我们需要更改Customer类;
  • 如果您想要再添加一个要生成的报告类型,那么我们需要更改Customer类;

那么按照原则,账单计算和报告生成不应该是Customer类的责任,我们应该拆分成多个类。

创建一个单独的类用来做账单计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码import java.util.List;

public class BillCalculator {
public long calculateBill(Customer customer,long tax){
long bill=0;
List listsOfItems=customer.getListsOfItems();
for (Item item:listsOfItems) {
bill+=item.getPrice();
}
bill+=tax;
customer.setBill(bill);
return bill;
}
}

创建一个单独的类用来做报告生成。

1
2
3
4
5
6
7
8
9
10
java复制代码public class ReportGenerator {
public void generateReport(Customer customer,String reportType) {
if(reportType.equalsIgnoreCase("CSV")) {
System.out.println("Generate CSV report");
}
if(reportType.equalsIgnoreCase("XML")) {
System.out.println("Generate XML report");
}
}
}

如果我们需要更改账单计算中的任何内容,我们不需要修改Customer类,我们将在BillCalculator类中进行更改。

如果想添加另一种报告类型,那么您需要在ReportGenerator类而不是Customer类中进行更改。

开闭原则

实体或对象应该对扩展保持开放,但对修改保持关闭。

一旦我们编写了类并测试了它,就不应该一次又一次地修改它,而是应该对扩展开放。如果我们修改了已经测试的类,可能会导致大量额外的工作来测试它,而且可能会引入新的bug。

策略设计模式是开闭原则的一种实现方式。服务类可以根据需求使用不同的策略来执行特定的任务,因此我们将保持服务类的封闭,但同时,通过引入新的策略来实现策略接口,系统对扩展是开放的。在运行时,您可以根据需要使用任何新的策略。

我们来通过一个简单的代码例子来理解这个原则。

假设我们需要根据输入的类型决定创建两种格式的报告文件,比如CSV和XML,注意在设计时要考虑以后可能会增加新的格式。

首先我们先通过枚举定义文件格式。

1
2
3
java复制代码public enum ReportingType {
CSV,XML;
}

然后创建一个服务类用来生成文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public class ReportingService {
public void generateReportBasedOnType(ReportingType reportingType) {
if("CSV".equalsIgnoreCase(reportingType.toString())) {
generateCSVReport();
} else if("XML".equalsIgnoreCase(reportingType.toString())) {
generateXMLReport();
}
}
private void generateCSVReport() {
System.out.println("Generate CSV Report");
}
private void generateXMLReport(){
System.out.println("Generate XML Report");
}
}

这段代码逻辑很简单,能够实现我们的需求,并且经过了测试。

现在我们需要增加一种新的报告文件格式,比如EXCEL,那我们的代码首先要修改枚举类:

1
2
3
java复制代码public enum ReportingType {
CSV,XML,EXCEL;
}

接下来我们要对已经经过测试的服务类进行修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码public class ReportingService {
public void generateReportBasedOnType(ReportingType reportingType) {
if("CSV".equalsIgnoreCase(reportingType.toString())) {
generateCSVReport();
} else if("XML".equalsIgnoreCase(reportingType.toString())) {
generateXMLReport();
} else if("Excel".equalsIgnoreCase(reportingType.toString())) {
// 增加excel生成逻辑
generateExcelReport();
}
}
private void generateCSVReport() {
System.out.println("Generate CSV Report");
}
private void generateXMLReport(){
System.out.println("Generate XML Report");
}
// 新增生成excel的方法
private void generateExcelReport() {
System.out.println("Generate Excel Report");
}
}

因为我们在原来已经测试过了的代码上进行了修改,为了不出BUG,我们不得不重新对所有功能进行测试。这显然太不优雅了,主要原因就是这个方式不符合开闭原则。

接下来我们用开闭原则的方式重新完成我们的服务类。

1
2
3
4
5
6
7
java复制代码public class ReportingService {
public void generateReportBasedOnStrategy(ReportingStrategy reportingStrategy)
{
// 通过一个策略生成报告文件
reportingStrategy.generateReport();
}
}

创建一个名为ReportingStrategy的接口,代表报告文件生成的策略。

1
2
3
java复制代码public interface ReportingStrategy {
void generateReport();
}

然后我们分别创建出生成CSV和XML各自的策略实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码public class CSVReportingStrategy implements ReportingStrategy {
@Override
public void generateReport() {
System.out.println("Generate CSV Report");
}
}

public class XMLReportingStrategy implements ReportingStrategy {

@Override
public void generateReport() {
System.out.println("Generate XML Report");
}
}

这样,我们在使用时,只需要按照需要创建不同的策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码public class GenerateReportMain {

public static void main(String[] args) {
ReportingService rs=new ReportingService();
//生成CSV文件
ReportingStrategy csvReportingStrategy=new CSVReportingStrategy();
rs.generateReportBasedOnStrategy(csvReportingStrategy);

//生成XML文件
ReportingStrategy xmlReportingStrategy=new XMLReportingStrategy();
rs.generateReportBasedOnStrategy(xmlReportingStrategy);
}
}

当需要增加Excel类型时,只需要新增一个Excel策略即可。

1
2
3
4
5
6
java复制代码public class ExcelReportingStrategy implements ReportingStrategy {
@Override
public void generateReport() {
System.out.println("Generate Excel Report");
}
}

没有对ReportingService做任何修改。我们刚刚添加了新的类ExcelReportingStrategy,只需要测试Excel相关的逻辑,不用再重新测试服务类和其他文件格式的代码。

还有一个例子可以帮助你更清晰地理解开闭原则。想必你应该在chrome浏览器中安装过扩展插件吧。

chrome浏览器的主要功能是浏览不同的网站。当你用chrome浏览器浏览外语网站时,你想进行翻译安装一个翻译插件就可以了。

这种为增加浏览器功能而添加内容的机制是一种扩展。因此,浏览器是一个对扩展开放但对修改关闭的完美例子。

里氏替换原则

里氏替换原则的定义是每个子类或派生类都应该可以替换它们的父类或基类。

它是一个独特的面向对象原则。一个特定的父类型的子类型在没有造成任何复杂或破坏的情况下应该有能力代替父类型。

接口隔离原则

简单地说,接口隔离原则规定不应该强制客户端实现它不使用的方法。

可以将你不支持的方法抛出UnsupportedOperationException,但不建议这样做,这会让你的类很难使用。

同样为了理解这个原则,我们通过下面的代码示例来更好的理解。

假设现在有一个接口Set。

1
2
3
4
5
6
java复制代码public interface Set<E> {
boolean add(E e);
boolean contains(Object o);
E ceiling(E e);
E floor(E e);
}

创建一个TreeSet来实现Set接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码public class TreeSet implements Set{
@Override
public boolean add(Object e) {
// Implement this method
return false;
}
@Override
public boolean contains(Object o) {
// Implement this method
return false;
}
@Override
public Object ceiling(Object e) {
// Implement this method
return null;
}
@Override
public Object floor(Object e) {
// Implement this method
return null;
}
}

再创建一个HashSet。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码public class HashSet implements Set{
@Override
public boolean add(Object e) {
return false;
}
@Override
public boolean contains(Object o) {
return false;
}
@Override
public Object ceiling(Object e) {
return null;
}
@Override
public Object floor(Object e) {
return null;
}
}

你发现了,即使在hashSet中不需要ceiling()方法和floor()方法,也要进行实现。

这个问题可以用下面的方式实现:

创建另一个名为NavigableSet的接口,它将具有ceiling()和floor()方法。

1
2
3
4
java复制代码public interface NavigableSet<E> {
E ceiling(E e);
E floor(E e);
}

将Set接口进行修改。

1
2
3
4
java复制代码public interface Set<E> {
boolean add(E e);
boolean contains(Object o);
}

现在TreeSet可以实现两个接口Set和NavigableSet。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码public class TreeSet implements Set,NaviagableSet{
@Override
public boolean add(Object e) {
// Implement this method
return false;
}
@Override
public boolean contains(Object o) {
// Implement this method
return false;
}
@Override
public Object ceiling(Object e) {
// Implement this method
return null;
}
@Override
public Object floor(Object e) {
// Implement this method
return null;
}
}

而HashSet只需要实现Set接口。

1
2
3
4
5
6
7
8
9
10
java复制代码public class HashSet implements Set{
@Override
public boolean add(Object e) {
return false;
}
@Override
public boolean contains(Object o) {
return false;
}
}

这就是java中的接口隔离原则。如果你去看JDK源码的话,会发现Set接口依赖体系就是这种方式。

依赖倒置原则

依赖倒置原则定义,实体应该只依赖于抽象的而不是具体的。高级模块不能依赖于任何低级模块,而应该依赖于抽象。

让我们再通过另一个实际例子来更好的理解这个原则。

当你去商场买东西,你决定用你的银行卡付款,当你把银行卡给收银员时,收银员不会关注你使用的是哪个银行的卡,即使你给了一张农村信用社的卡,他也不会拿出专门的信用社的刷卡机,甚至你使用的是借记卡还是信用卡都不重要,都可以成功的刷卡付款。

在这个例子中,收银员的刷卡机依赖的是银行卡的抽象,而不会关心银行卡的具体细节,这就是依赖倒置原则。

总结

现在想必你已经知道了SOLID所有五个组成部分的基本定义,分别是单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。

所以当你写代码的时候,你应该把这些核心原则牢记于心,把这些原则作为你写代码的规范,让你的代码更加高效优雅。

如果对你锁帮助,点个赞是对我最大的鼓励!

本文转载自: 掘金

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

80% 的人都不会的 15 个 Linux 实用技巧

发表于 2021-11-06

熟悉 Linux 系统的同学都知道,它高效主要体现在命令行。通过命令行,可以将很多简单的命令,通过自由的组合,得到非常强大的功能。

命令行也就意味着可以自动化,自动化会使你的工作更高效,释放很多手工操作,让你有更多的时间去做更有意义的事情。

这篇文章,会分享一些非常实用小技巧,希望能够帮助你提高工作效率,学完就能够用得上!

  1. 快速清空文件的方法

快速清空一个文件,有 N 种方法,我比较喜欢下边这种,因为它最短

$ > access.log

不过瘾?好吧,我也顺便总结下,其它几种最常见的清空文件的方法

1
2
3
4
5
6
c复制代码: > access.log
true > access.log
cat /dev/null > access.log
echo -n "" > access.log
echo > access.log
truncate -s 0 access.log

简单解释下, : 在 shell 中是一个内置命令,表示 no-op,大概就是空语句的意思,所以 : 的那个用法,就是执行命令后,什么都没有输出,将空内容覆盖到文件。

  1. 快速生成大文件

有时候,在 Linux 上,我们需要一个大文件,用于测试上传或下载的速度,通过 dd 命令可以快速生成一个大文件

1
shell复制代码$ dd if=/dev/zero of=file.img bs=1M count=1024

上述命令,生成一个文件名为 file.img 大小为 1G 的文件。

  1. 安全擦除硬盘数据

介绍一种擦除硬盘数据的方法,高效,安全。可以通过 dd 命令,轻松实现:

$ dd if=/dev/urandom of=/dev/sda

使用 /dev/urandom 生成随机数据,将生成的数据写入 sda 硬盘中,相当于安全的擦除了硬盘数据。

当年陈老师,如果学会了这条命令,可能也不会有艳兆门事件了。

  1. 快速制作系统盘

在 Linux 下制作系统盘,老毛桃神么工具都弱爆了,直接一条命令搞定:

1
shell复制代码$ dd if=ubuntu-server-amd64.iso of=/dev/sdb

哈哈,是不是很爽,sdb 可以 U 盘,也可以是普通硬盘

  1. 查看某个进程的运行时间

可能,大部分同学只会使用 ps aux,其实可以通过 -o 参数,指定只显示具体的某个字段,会得到更清晰的结果。

1
2
3
css复制代码$ ps -p 10167 -o etimes,etime
ELAPSED ELAPSED
1712055 19-19:34:15

通过 etime 获取该进程的运行时间,可以很直观地看到,进程运行了 19 天

同样,可以通过 -o 指定 rss 可以只获取该进程的内存信息。

`$ ps -p 10167 -o rss

RSS

2180`

  1. 动态实时查看日志

通过 tail 命令 -f 选项,可以动态地监控日志文件的变化,非常实用

$ tail -f test.log

如果想在日志中出现 Failed 等信息时立刻停止 tail 监控,可以通过如下命令来实现:

$ tail -f test.log | sed '/Failed/ q'

  1. 时间戳的快速转换

时间操作,对程序员来说就是家常便饭。有时候希望能够将时间戳,转换为日期时间,在 Linux 命令行上,也可以快速的进行转换:

1
2
perl复制代码$ date -d@1234567890 +"%Y-%m-%d %H:%M:%S"
2009-02-14 07:31:30

当然,也可以在命令行上,查看当前的时间戳

`$ date +%s

1617514141`

  1. 优雅的计算程序运行时间

在 Linux 下,可以通过 time 命令,很容易获取程序的运行时间:

1
2
3
4
sql复制代码$ time ./test
real 0m1.003s
user 0m0.000s
sys 0m0.000s

可以看到,程序的运行时间为: 1.003s。细心的同学,会看到 real 貌似不等于 user + sys,而且还远远大于,这是怎么回事呢?

先来解释下这三个参数的含义:

1
2
3
sql复制代码real:表示的钟表时间,也就是从程序执行到结束花费的时间;
user:表示运行期间,cpu 在用户空间所消耗的时间;
sys:表示运行期间,cpu 在内核空间所消耗的时间;

由于 user 和 sys 只统计 cpu 消耗的时间,程序运行期间会调用 sleep 发生阻塞,也可能会等待网络或磁盘 IO,都会消耗大量时间。因此对于类似情况,real 的值就会大于其它两项之和。

另外,也会遇到 real 远远小于 user + sys 的场景,这是什么鬼情况?

这个更好理解,如果程序在多个 cpu 上并行,那么 user 和 sys 统计时间是多个 cpu 时间,实际消耗时间 real 很可能就比其它两个之和要小了

  1. 命令行查看ascii码

我们在开发过程中,通常需要查看 ascii 码,通过 Linux 命令行就可以轻松查看,而不用去 Google 或 Baidu

$ man ascii

  1. 优雅的删除乱码的文件

在 Linux 系统中,会经常碰到名称乱码的文件。想要删除它,却无法通过键盘输入名字,有时候复制粘贴乱码名称,终端可能识别不了,该怎么办?

不用担心,下边来展示下 find 是如何优雅的解决问题的。

1
2
3
4
shell复制代码$ ls  -i
138957 a.txt 138959 T.txt 132395 ڹ��.txt

$ find . -inum 132395 -exec rm {} \;

命令中,-inum 指定的是文件的 inode 号,它是系统中每个文件对应的唯一编号,find 通过编号找到后,执行删除操作。

  1. Linux上获取你的公网IP地址

在办公或家庭环境,我们的虚拟机或服务器上配置的通常是内网 IP 地址,我们如何知道,在与外网通信时,我们的公网出口 IP 是神马呢?

这个在 Linux 上非常简单,一条命令搞定

`

curlip.sb

c

u

r

l

i

p

.

s

b

curl ifconfig.me`

上述两条命令都可以

  1. 如何批量下载网页资源

有时,同事会通过网页的形式分享文件下载链接,在 Linux 系统,通过 wget 命令可以轻松下载,而不用写脚本或爬虫

1
2
ruby复制代码$ wget -r -nd -np --accept=pdf http://fast.dpdk.org/doc/pdf-guides/
# --accept:选项指定资源类型格式 pdf
  1. 历史命令使用技巧

分享几个历史命令的使用技巧,能够提高你的工作效率。

1
2
3
4
diff复制代码!!:重复执行上条命令;
!N:重复执行 history 历史中第 N 条命令,N 可以通过 history 查看;
!pw:重复执行最近一次,以pw开头的历史命令,这个非常有用,小编使用非常高频;
!$:表示最近一次命令的最后一个参数;

猜测大部分同学没用过 !$,这里简单举个例子,让你感受一下它的高效用法

1
2
3
4
shell复制代码$ vim /root/sniffer/src/main.c
$ mv !$ !$.bak
# 相当于
$ mv /root/sniffer/src/main.c /root/sniffer/src/main.c.bak

当前工作目录是 root,想把 main.c 改为 main.c.bak。正常情况你可能需要敲 2 遍包含 main.c 的长参数,当然你也可能会选择直接复制粘贴。

而我通过使用 !$ 变量,可以很轻松优雅的实现改名,是不是很 hacker 呢?

  1. 快速搜索历史命令

在 Linux 下经常会敲很多的命令,我们要怎么快速查找并执行历史命令呢?

通过上下键来翻看历史命令,No No No,可以通过执行 Ctrl + r,然后键入要所搜索的命令关键词,进行搜索,回车就可以执行,非常高效。

  1. 真正的黑客不能忽略技巧

最后,再分享一个真正的黑客不能忽略技巧。我们在所要执行的命令前,加一个空格,那这条命令就不会被 history 保存到历史记录

有时候,执行的命令中包含敏感信息,这个小技巧就显得非常实用了,你也不会再因为忘记执行 history -c 而烦恼了。

本文转载自: 掘金

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

SSIS学习使用三:Integration Services

发表于 2021-11-06

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

翻译参考

本文主要参考翻译自 the Stairway to Integration Services 系列文章的 Adding Rows in Incremental Loads - Level 3 of the Stairway to Integration Services,目的在于对 SSIS 有一个全面清晰的认识,所有内容在原文的基础上进行实操,由于版本差异、个人疑问等多种原因,未采用完全翻译的原则,同时也会对文中内容进行适当修改,希望最终可以更有利于学习和了解 SSIS,

感谢支持!

介绍

上一篇介绍了 SSIS 数据流任务加载数据的基本配置。下面将介绍增量加载(Incremental Load),以实现上一篇创建的SSIS包可重新执行(即:可以重复执行转移增量数据[Data Rows])。

什么是增量加载?

一个增量加载仅仅加载与上一次的加载不同的数据。

“不同”包括:

  • 新的行 New rows
  • 更新的行 Updated rows
  • 删除的行 Deleted rows

本质上,增量加载是可重新执行的,这意味着可以一遍又一遍地执行加载器而不会造成损害。不仅如此,可重新执行意味着加载器被设计为可以多次执行,而不会导致服务器上不必要或重复的工作。

下面,看一下增量加载的目标:

  1. 我们要插入那些在源表中是新的但是还没有加载到目标表中的数据
  2. 我们要更新那些自上一次加载到目标表中的所有有任何更改的数据
  3. 我们要删除那些已经从源表移除的目标表中的数据

新行数据的增量加载

Adding Rows in Incremental Loads 增量加载中添加行

上一节创建的 SSIS 项目 FirstSSIS debug运行后,会把选取的数据加载到dbo.FullName表中,一共有 19972 条数据。但是,如果再次执行 SSIS 包,它将会再次加载这 19972 条数据到 FullName 表中。

而我们的需求肯定不是每次都加载重复的数据到目标表中,那么,如何实现增量加载的目标呢?

检测源表中新增的数据

修改FirstSSIS包的目标表FullName

此处我们先修改下之前的 FirstSSIS 包。原因在于,后面要实现对源表和目标表的数据的对比,而只有 FirstName、MiddleName、LastName 三个字段的 FullName 表无法唯一确定一行数据,也就无法唯一确定的比较源表和目标表中的差异,必须要引入一个唯一值(主键)字段。

FullName 表结构如下,同时对其他字段添加和源表一样的约束:

1
2
3
4
5
6
sql复制代码CREATE TABLE [dbo].[FullName](
[BusinessEntityID] [int] PRIMARY KEY,
[FirstName] [nvarchar](50) NOT NULL,
[MiddleName] [nvarchar](50),
[LastName] [nvarchar](50) NOT NULL
)

模拟源表新数据

接下来,先模拟增加新行(有两种办法,一种是向源表插入数据,实现新增行;另一种是从目标表删除一些数据,这样源表多出的数据就是新数据【实现不变更源表数据】)。

如下,删除目标表中MiddleName是NULL的数据,执行后一共删除8499行

1
2
3
4
sql复制代码Use AdventureWorks2012
go
Delete dbo.FullName
Where MiddleName Is NULL

查找在一个表但不在另一个表中的数据

增量加载新行的第一步是监测新行。有以下几种方法实现:

方法一:查找在源表但是不在目标表中的行。如下T-SQL语句:

1
2
3
4
5
6
7
8
sql复制代码Select BusinessEntityID,
FirstName,
MiddleName,
LastName
From Person.Person
where BusinessEntityID not in (
select BusinessEntityID from dbo.FullName
);

查找在一个表但不在另一个表的前提是两个表可以唯一确定一条数据,否则没法比较。

方法二:通过左连接 LEFT JOIN 查询查找不在目标表中的数据

1
2
3
4
5
6
7
8
sql复制代码select 
src.BusinessEntityID,
src.FirstName,
src.MiddleName,
src.LastName
FROM Person.Person src
LEFT JOIN dbo.FullName dest on src.BusinessEntityID=dest.BusinessEntityID
WHERE dest.BusinessEntityID IS NULL;

第二种方法会比第一种效率高,涉及表扫描的问题。

SSIS包中实现新增数据的增量加载

我们需要在数据流任务的 OLE DB源 和 OLE DB目标(FullName) 之间添加组件,实现对增量数据的加载。

删除数据流路径

右键连接源和目标的 数据流路径,点击删除。

如下,此时下侧的错误列表显示未映射的输入列错误

添加”查找”(Lookup)转换组件

从 Toolbox 中拖拽 Lookup查找 的转换组件到源和目标适配器中间。

拖拽 源数据流路径 到 “查找”转换

查找转换(Lookup Transformation)的含义是:它在另一个表、视图或查询中匹配流入转换的行。即:转到一个表、视图或查询,查看是否在这些列中找到匹配,如果匹配则带回这些列。

此处有几个注意点:

  1. 如果在数据流和查找(Lookup)表、视图或查询之间没发现任何匹配列,默认查找转换配置使该转换失败。
  2. 如果在查找lookup表中有多个匹配,则查找转换仅仅返回第一个匹配的数据。

这些注意点是恶毒的(”vicious”),因为如果找不到匹配,操作将失败。同时当连接数据流中的行和查找(Lookup)表、视图或查询中的行时,转换仅仅返回第一个匹配行。

配置”查找”(Lookup)

双击 Lookup Transformation 打开编辑器。默认会显示常规页。

  • 缓存模式

常规页中第一项是缓存模式(Cache Mode)属性

缓存模式控制何时以及如何执行实际的查找操作。

“无缓存”(No Cache)模式下,当每一行流经转换时,都会进行查找操作。无论何时,每行数据传入查找(Lookup)时,转换都会对查找表、视图或查询执行查询;并将任何返回的值添加到流过转换的行中。

“完全缓存”(Full Cache)模式下,在数据任务执行之前,查找操作(lookup operation)尝试从查找(Lookup)的表、视图或查询中,加载所有的行到RAM的查找缓存(Lookup cache)中。这里的“尝试”是指如果查找表、视图或查询返回一个较大的数据集,或者服务器RAM受限(低速运行或没有足够的RAM),查找将会失败。查找缓存保存来自配置的表、视图或查询的值,在缓存中找到的匹配会添加到流经转换的行。

如果由于RAM受限在完全缓存模式下查找转换加载失败,该如何处理?一个选择是使用”无缓存”模式。第二个选择是部分缓存模式(Partial Cache mode)。

(除文中讨论的范围外,还有其他选择)

“部分缓存”(Partial Cache)模式下,转换首先在每行流过时检查“查找缓存(Lookup cache)”,查找匹配项。如果在缓存中没有匹配,会发生一个查找操作。匹配数据会添加到数据行和查找缓存中。如果查找相同匹配列的另一行数据流过转换,匹配将从查找缓存获取。

此处选择默认选项:完全缓存。因为当前Lookup查找的数据集相对较少(19972)。大数据量可以考虑其他模式。

  • 无匹配项时的处理

下一步选择 “指定如何处理无匹配项的行”(“Specify how to handle rows with no matching entries”) 为 “将行重定向到无匹配输出”(“Redirect rows to no match output”)。

  • 连接

点击”连接”页,设置 OLEDB连接管理器 的属性为[server-name].AdventureWorks2012,和OLE DB源一样,配置一个连接管理器的接口。

下面,同样从连接管理器的SQL Server实例和数据库配置中选择一个表或输入SQL查询。此处,输入如下的T-SQL查询:

1
2
3
4
5
sql复制代码Select BusinessEntityID,
FirstName,
MiddleName,
LastName
From dbo.FullName

  • 列

进入”列”页,在”列”的上方有两个表格外观的网格。左侧的是标记为”可用输入列”(Available Input Columns),包含进入查询转换(Lookup Transformation)的输入缓冲区的列的列表(它们是连接到OLE DB源的输出的列)。另一个表格是可用查找列(Available Lookup Columns),它们是存在于”连接”页中配置的表、视图或查询中的列(本示例为查询)

  • 设置查找匹配条件

单击“可用输入列”中的“BusinessEntityID”列,然后将其拖到“可用查找列”中的“BusinessEntityID”列上。

此处的查找与联接(join)查询一样?将”BusinessEntityID”放到”BusinessEntityID”上时,在“可用输入列”的“BusinessEntityID”列和“可用查找列”的“BusinessEntityID”列之间出现的连线类似于联接(join)的ON子句。它定义了驱动 查找功能(Lookup function)的 匹配条件。

“可用查找列”包含checkbox复选框及全选的复选框。如果查找转换类似于联接(join),则复选框就是一种用于将联接表中的列添加到SELECT子句的机制。此处不选择任何列。

我们已经配置了一个查找转换(Lookup Transformation),用以打开目标表,并将数据流管道中存在的记录与目标表中的记录进行匹配。数据流管道中存在的记录来自 OLE DB源适配器 —— 从Person.Person表加载到数据流中。目标表是dbo.FullName,我们在“查找转换”的“连接”页面上使用T-SQL查询对其进行了访问。

“查找转换”配置为通过将“目标”表中的“BusinessEntityID”列值与“源”表(通过OLE DB源适配器)中的“BusinessEntityID”列值进行比较来查找匹配项。

“查找转换”配置为发送与”查找转换的不匹配输出”不匹配的行。如果在“目标”表中的“BusinessEntityID”列值和“源”表中的“BusinessEntityID”列值之间找到匹配项,则查找转换会将这些行发送到“匹配输出”。

  • 连接到目标

接下来继续构建增量加载。

点击”确定”(OK)按钮关闭查找转换编辑器。

然后,点击查找转换,拖动查找下面的绿色数据流路径到 OLE DB目标(名字为FullName)。出现提示框时,选择”查找无匹配输出”(Lookup No Match Output)。

  • 回顾(review)

下面回顾下此处完成的工作,有很多很重要的点:

为什么是“查找无匹配输出”(Lookup No Match Output)。在SSIS中,查找转换(Lookup Transformation)提供这种内建的输出,捕获在源(当前例子是Person.Person表)中但不在查找表(当前例子是dbo.FullName目标表)中的记录,这就是”查找无匹配输出”。

此处原文为:the Lookup Transformation provides this built-in output to catch records in the Lookup table (the dbo.Contact destination table, in this case) that do not exist in the source (the Person.Contact table) - it's the Lookup No Match Output.。

此处应为作者笔误,因为肯定在“源”(用来查找的数据就来自于“源”,所以一定在“源”中)

为什么这里是无匹配(no match)?因为BusinessEntityID列的值不存在于目标表。如果在源表而不在目标表,那么就是一个新行 —— 上一次加载后添加到源表中的行。这就是想要加载的新行。

  • 查看数据流路径

右键源和查找转换之间的数据流路径,点击”编辑”。

在数据流路径编辑器中的元数据页,可以看到,它的配置 和 查找转换与目标之间的数据流路径 的 元数据是一样的。

查找转换的无匹配输出是查找转换输入的精确拷贝。这个场景就是:如果没有发现匹配,则要发送经过查找转换的无匹配输出的所有数据列,以便于被下游(downstream)使用。

关闭数据流路径编辑器,现在的数据流任务是这样的。

运行及多次执行

通过按F5键或菜单上的”启动调试”按钮,将会看到:

仅仅加载了8499新行(这是上面模拟新行时删除的行数)。

下面就可以重新执行加载(re-execute the load)。在debug按钮旁边有重新启动的按钮。 SSIS包停止又再次执行,这次查找转换在目标表中找到了所有的行。没有发送到查找转换的“无匹配输出的行”。

总结

此处已经完成了几个目标。

首先,创建一个加载器,该加载器仅将新行从源表添加到目标表。其次,构建的加载器是可重新执行的,它不会将行的重复副本堆积到目标表。

刚刚构建了的SSIS包是“函数性”的(functional,表示的是可重复执行的)。许多在生产环境中的 SSIS包 包含执行相似加载的数据流任务。在增量加载中仅加载新行的场景:比如一个包括历史每日货币兑换率的表(数据不会随着时间而改变);还有保存每天温度(高温)的表(数据也是永不会更新)等。

但是对于频繁更新源数据的情况,当前的增量加载明显就没那么灵活了!

本文转载自: 掘金

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

springboot使用RedisTemplate

发表于 2021-11-06

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

1.pom引入

1
2
3
4
5
xml复制代码 <!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.配置文件application.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ini复制代码#-----------------redis--------------------------
# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=123654
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=5000
#-----------------redis end------------------------

3.创建个RedisConfig类

1
2
javascript复制代码操作redis的时候可以又多种序列化方法,直接字符串
JSON,JSON带有过滤敏感属性的
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
typescript复制代码@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplateString(RedisConnectionFactory redisConnectionFactory) {
// 配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
RedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer); // key序列化
redisTemplate.setValueSerializer(stringSerializer); // value序列化
redisTemplate.setHashKeySerializer(stringSerializer); // Hash key序列化
redisTemplate.setHashValueSerializer(stringSerializer); // Hash value序列化
redisTemplate.afterPropertiesSet();
return redisTemplate;
}


@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
RedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer); // key序列化
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // value序列化
redisTemplate.setHashKeySerializer(stringSerializer); // Hash key序列化
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); // Hash value序列化
redisTemplate.afterPropertiesSet();
return redisTemplate;
}


@Bean
public RedisTemplate<String, Object> redisTemplateGroup(RedisConnectionFactory redisConnectionFactory) {
// 配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
RedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer); // key序列化
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer(getMapper())); // value序列化
redisTemplate.setHashKeySerializer(stringSerializer); // Hash key序列化
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer(getMapper())); // Hash value序列化
redisTemplate.afterPropertiesSet();
return redisTemplate;
}

// 获取JSON工具
private final ObjectMapper getMapper() {
ObjectMapper mapper = new ObjectMapper();
JsonFilterUtil.addFilterForMapper(mapper);
//将类名称序列化到json串中,去掉会导致得出来的的是LinkedHashMap对象,直接转换实体对象会失败
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL); //设置输入时忽略JSON字符串中存在而Java对象实际没有的属性
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}

}

序列化参数过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
csharp复制代码public static void addFilterForMapper(ObjectMapper mapper) {

Set<String> sets =new HashSet<String>();
sets.add("userEmail");
sets.add("userPhone");
sets.add("userImagePath");
sets.add("userPassword");
sets.add("userSex");
sets.add("userStatus");
//sets.add("liveType");
sets.add("ip");
sets.add("userCreateDate");
SimpleBeanPropertyFilter fieldFilter = SimpleBeanPropertyFilter.serializeAllExcept(sets);
SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter("fieldFilter", fieldFilter);
mapper.setFilterProvider(filterProvider).addMixIn(ChartUser.class, FieldFilterMixIn.class);
}

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
27
28
29
typescript复制代码 /**
* 添加数据
* @param key
* @param value
*/
public void set(String key, Object value) {
//delete(key);
ValueOperations<String, Object> vo = redisTemplate.opsForValue();
vo.set(key, value);
}

/**
* 获取数据
* @param key
* @return
*/
public Object get(String key) {
ValueOperations<String, Object> vo = redisTemplate.opsForValue();
return vo.get(key);
}

/**
* 删除数据
* @param key
* @return
*/
public boolean delete(String key) {
return redisTemplate.delete(key);
}

本文转载自: 掘金

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

Spring WebSocket 实现 ServerEnd

发表于 2021-11-06

最近项目中需要使用WebSocket实现实时传递消息功能,网上大多数都是 Socket 实现,而 使用Spring 的WebSocket 的也有不少,但是大多数都只写了一种方法:@ServerEndpoint() 这个方法 ,其实还有另一种方法 WebSocketConfigurer 。这里将会把两种方法介绍下。

@ServerEndpoint() 方法

服务端配置

  • WebSocket 配置类
1
2
3
4
5
6
7
java复制代码@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
  • 服务端类
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
java复制代码@Component
@ServerEndpoint(value ="/websocket/server1")
public class WebSocketServer {
private Logger logger = LoggerFactory.getLogger(WebSocketServer.class);
@OnOpen
public void onOpen(Session session) {
logger.info("-----server open");

}

@OnMessage
public void onMessage(Session session, String message) throws IOException {
logger.info("-----server message:{}", message);
if ("ABC".equals(message)){
session.getBasicRemote().sendText("DEF");
}
}

@OnMessage
public void onBinaryMessage(ByteBuffer buffer, Session session){
logger.info("-----server binary message:{}", buffer);
}

@OnClose
public void onClose(Session session, CloseReason closeReason){
logger.info("-----server close");
}

@OnError
public void onError(Session session, Throwable throwable){
logger.error("-----server error", throwable);
}

}

客户端

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
java复制代码public class WebSocketClient extends Endpoint {
private Logger logger = LoggerFactory.getLogger(WebSocketClient.class);

@Override
public void onClose(Session session, CloseReason closeReason) {
super.onClose(session, closeReason);
logger.info("-----client close");
}

@Override
public void onError(Session session, Throwable throwable) {
super.onError(session, throwable);
logger.error("-----client error", throwable);
}

@Override
public void onOpen(final Session session, EndpointConfig endpointConfig) {
logger.info("-----client open");
session.addMessageHandler(new MessageHandler.Whole<String>() {
public void onMessage(String s) {
logger.info("-----client message:{}", s);
try {
onHandleMessage(session, s);
} catch (IOException e) {
e.printStackTrace();
}
}
});
try {
session.getBasicRemote().sendText("ABC");
} catch (IOException e) {
e.printStackTrace();
}
}

private void onHandleMessage(Session session, String message) throws IOException {
if ("DEF".equals(message)){
session.close();
}
}
}

连接方法

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
ClientEndpointConfig clientEndpointConfig = ClientEndpointConfig.Builder
.create().configurator(new ClientEndpointConfig.Configurator(){
@Override
public void beforeRequest(Map<String, List<String>> headers) {
super.beforeRequest(headers);
List<String> values = new ArrayList<String>();
values.add("v"); // header value
headers.put("k",values);// header
}
}).build();
Session session = container.connectToServer(WebSocketClient.class, clientEndpointConfig, URI.create("ws://localhost:8181/websocket/server1"));
  • 要想忽略ssl认证,则需要添加SSLContext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码        SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new X509ExtendedTrustManager[]{x509ExtendedTrustManager},new SecureRandom());

WebSocketContainer container = ContainerProvider.getWebSocketContainer();
ClientEndpointConfig clientEndpointConfig = ClientEndpointConfig.Builder
.create().configurator(new ClientEndpointConfig.Configurator(){
@Override
public void beforeRequest(Map<String, List<String>> headers) {
super.beforeRequest(headers);
List<String> values = new ArrayList<String>();
values.add("v");
headers.put("k",values);
}
}).build();
clientEndpointConfig.getUserProperties().put(SSL_CONTEXT_PROPERTY, sslContext);

x509ExtendedTrustManager 必须是 javax.net.ssl.X509ExtendedTrustManager 下的包。

连接后Log 信息

1
2
3
4
5
6
yaml复制代码2021-10-30 11:17:44.515  INFO 15808 --- [nio-8181-exec-1] c.e.websocket.spring1.WebSocketClient    : -----client open
2021-10-30 11:17:44.518 INFO 15808 --- [nio-8181-exec-2] c.e.websocket.spring1.WebSocketServer : -----server open
2021-10-30 11:17:44.532 INFO 15808 --- [nio-8181-exec-3] c.e.websocket.spring1.WebSocketServer : -----server message:ABC
2021-10-30 11:17:44.537 INFO 15808 --- [lient-AsyncIO-1] c.e.websocket.spring1.WebSocketClient : -----client message:DEF
2021-10-30 11:17:44.538 INFO 15808 --- [lient-AsyncIO-1] c.e.websocket.spring1.WebSocketClient : -----client close
2021-10-30 11:17:44.538 INFO 15808 --- [nio-8181-exec-4] c.e.websocket.spring1.WebSocketServer : -----server close

WebSocketConfigurer 方法

服务端配置

  • AbstractWebSocketHandler 实现,主要是接收消息
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
java复制代码@Component
public class WebSocketServerHandler extends AbstractWebSocketHandler {
private Logger logger = LoggerFactory.getLogger(WebSocketServerHandler.class);
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
super.afterConnectionEstablished(session);
logger.info("-----server open");
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
super.afterConnectionClosed(session, status);

logger.info("-----server close");
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
super.handleTransportError(session, exception);
logger.error("-----server error", exception);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
super.handleTextMessage(session, message);
logger.info("-----server text message:{}", message.getPayload());
if ("ABC".equals(message.getPayload())){
session.sendMessage(new TextMessage("DEF"));
}
}
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
super.handleBinaryMessage(session, message);
logger.info("-----server binary message:{}", message.getPayload());
}
@Override
protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
super.handlePongMessage(session, message);
logger.info("-----server pong message:{}", message.getPayload());
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
super.handleMessage(session, message);
logger.info("-----server message:{}", message.getPayload());
}
}
  • HandshakeInterceptor 实现,主要作用是对请求的拦截
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码@Component
public class WebSocketServerInterceptor implements HandshakeInterceptor {
private Logger logger = LoggerFactory.getLogger(WebSocketServerInterceptor.class);

public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {
logger.info("-----server beforeHandshake");
logger.info("-----server beforeHandshake request Headers :{}",request.getHeaders());
logger.info("-----server beforeHandshake: attributes{}",attributes);
return true; // 默认 false ,会报 DeploymentException: The HTTP response from the server [200] did not permit the HTTP upgrade to WebSocket 错误
}

public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
logger.info("-----server afterHandshake");
}
}
  • WebSocket 配置,主要是配置跨域、请求路径等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码@EnableWebSocket
@Configuration
public class WebSocketServerConfig implements WebSocketConfigurer {
@Autowired
private WebSocketServerHandler webSocketServerHandler;
@Autowired
private WebSocketServerInterceptor webSocketServerInterceptor;

public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketServerHandler, "websocket/server2")
.addInterceptors(webSocketServerInterceptor)
.setAllowedOrigins("*");
}
}

客户端

  • AbstractWebSocketHandler实现,主要作用是接收数据,连接监听等。
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
java复制代码@Component
public class WebSocketClientHandler extends AbstractWebSocketHandler {
private Logger logger = LoggerFactory.getLogger(WebSocketClientHandler.class);

@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
super.afterConnectionEstablished(session);
logger.info("-----client open");
session.sendMessage(new TextMessage("ABC"));
}

@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
super.afterConnectionClosed(session, status);

logger.info("-----client close");
}

@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
super.handleTransportError(session, exception);
logger.error("-----client error", exception);
}

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
super.handleTextMessage(session, message);
logger.info("-----client text message:{}", message.getPayload());
if ("DEF".equals(message.getPayload())){
session.close();
}
}

@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
super.handleBinaryMessage(session, message);
logger.info("-----client binary message:{}", message.getPayload());
}

@Override
protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
super.handlePongMessage(session, message);
logger.info("-----client pong message:{}", message.getPayload());
}

@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
super.handleMessage(session, message);
logger.info("-----client message:{}", message.getPayload());
// 所有message 都会运行到这里,一般情况不重写。
}
}

连接方法

1
2
3
4
5
6
7
java复制代码        StandardWebSocketClient standardWebSocketClient = new StandardWebSocketClient();
WebSocketConnectionManager webSocketConnectionManager = new WebSocketConnectionManager(standardWebSocketClient, webSocketClientHandler, "ws://localhost:8181/websocket/server2");
webSocketConnectionManager.setOrigin("*");
HttpHeaders headers = new HttpHeaders();
headers.set("k", "v");
webSocketConnectionManager.setHeaders(headers);
webSocketConnectionManager.start();

连接后Log信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码2021-10-30 11:12:20.997  INFO 15779 --- [nio-8181-exec-1] o.s.w.s.c.WebSocketConnectionManager     : Starting WebSocketConnectionManager
2021-10-30 11:12:20.997 INFO 15779 --- [nio-8181-exec-1] o.s.w.s.c.WebSocketConnectionManager : Connecting to WebSocket at ws://localhost:8181/websocket/server2
2021-10-30 11:12:26.049 INFO 15779 --- [nio-8181-exec-2] c.e.w.s.WebSocketServerInterceptor : -----server beforeHandshake
2021-10-30 11:12:26.050 INFO 15779 --- [nio-8181-exec-2] c.e.w.s.WebSocketServerInterceptor : -----server beforeHandshake request Headers :[sec-websocket-key:"C4QJYOfTt266k/88DfM6Lg==", connection:"upgrade", sec-websocket-version:"13", host:"localhost:8181", k:"v", upgrade:"websocket"]
2021-10-30 11:12:26.052 INFO 15779 --- [nio-8181-exec-2] c.e.w.s.WebSocketServerInterceptor : -----server beforeHandshake: attributes{}
2021-10-30 11:12:26.067 INFO 15779 --- [nio-8181-exec-2] c.e.w.s.WebSocketServerInterceptor : -----server afterHandshake
2021-10-30 11:12:26.091 INFO 15779 --- [nio-8181-exec-2] c.e.w.spring2.WebSocketServerHandler : -----server open
2021-10-30 11:12:26.091 INFO 15779 --- [cTaskExecutor-1] c.e.w.spring2.WebSocketClientHandler : -----client open
2021-10-30 11:12:26.109 INFO 15779 --- [cTaskExecutor-1] o.s.w.s.c.WebSocketConnectionManager : Successfully connected
2021-10-30 11:12:26.110 INFO 15779 --- [nio-8181-exec-3] c.e.w.spring2.WebSocketServerHandler : -----server text message:ABC
2021-10-30 11:12:26.114 INFO 15779 --- [lient-AsyncIO-2] c.e.w.spring2.WebSocketClientHandler : -----client text message:DEF
2021-10-30 11:12:26.114 INFO 15779 --- [nio-8181-exec-3] c.e.w.spring2.WebSocketServerHandler : -----server message:ABC
2021-10-30 11:12:26.114 INFO 15779 --- [lient-AsyncIO-2] c.e.w.spring2.WebSocketClientHandler : -----client close
2021-10-30 11:12:26.114 INFO 15779 --- [lient-AsyncIO-2] c.e.w.spring2.WebSocketClientHandler : -----client message:DEF
2021-10-30 11:12:26.115 INFO 15779 --- [nio-8181-exec-4] c.e.w.spring2.WebSocketServerHandler : -----server close

总结

@ServerEndpoint() 这种方式的,操作简单,方法封装的很好,但是不能拦截 WebSocket 的请求,也就不能活去header 值,加入需要校验,那么就会不知所措。

WebSocketConfigurer 这种方法,操作起来稍微复杂一些,但是可以自己设置拦截器,拦截请求,能获取到请求中的所有的内容。

他们都各有利弊,根据自己项目的实际情况来选择使用哪种方式。

最后demo地址:github.com/wdmxzf/java…

本文转载自: 掘金

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

Terraform 基于Azure上使用模块(四)

发表于 2021-11-06

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

六、使用Git管理模块

如果我们计划在多个环境中共享这个模块,最好的做法是将该模块放在源代码控制存储库中,那么我们就可以从源代码控制中获得所有的好处,比如更改跟踪。

此外,我们还得到了版本标签。标记模块是一种最佳实践,因为它允许我们将模块的稳定工作版本“固定”到 Terraform 配置中。这样可以防止任何中断更改影响已经在生产中的配置。

要从 git 仓库中使用 Terraform 模块,请将 source 参数更改为 git URL。在我们的示例中,我已经将我们的存储帐户模块上传到 Azure DevOps 回购。这是一个公共 git repo,不需要任何身份验证配置。我们可以使用 https URL,并在前缀 git: : :。

1
2
3
4
5
6
7
8
ini复制代码#Create Storage Account
module "storage_account" {
source = "git::https://allanore@dev.azure.com/allanore/TerraformModulesExample/_git/TerraformModulesExample?ref=v0.1"

saname = "tfdemosa23432"
rgname = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
}

如果我们运行 terraform init,我们可以在控制台输出中看到该模块从 git repo 下载并保存到。Terraform/modules 本地目录:

1
2
3
4
5
6
7
8
9
10
bash复制代码..
Please install a compatible extension version or remove it.
Initializing modules...
Downloading git::https://allanore@dev.azure.com/allanore/TerraformModulesExample/_git/TerraformModulesExample?ref=v0.1 for storage_account...
- storage_account in .terraform/modules/storage_account
Downloading git::https://allanore@dev.azure.com/allanore/TerraformModulesExample/_git/TerraformModulesExample?ref=v0.1 for storage_account2...
- storage_account2 in .terraform/modules/storage_account2

Initializing the backend...
...

另外,如果我们想使用带 SSH 的私有 Azure Repo,我们可以通过如下所示的 SSH URL 在 source 参数中引用我们的模块。我们还需要生成并安装用于认证的 SSH 证书。

1
ini复制代码git::git@ssh.dev.azure.com:v3/allanore/TerraformModulesExample/TerraformModulesExample?ref=v0.1

要使用来自 GitHub repo 的 Terraform 模块源代码,请使用到 GitHub 项目的 URL。在下面的例子中,我上传了我们的模块到 Github repo:

1
2
3
4
5
6
7
8
ini复制代码#Create Storage Account
module "storage_account" {
source = "github.com/**/TerraformModulesExample"

saname = "tfdemosa23432"
rgname = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
}

少年,没看够?点击石头的主页,随便点点看看,说不定有惊喜呢?欢迎支持点赞/关注/评论,有你们的支持是我更文最大的动力,多谢啦!

本文转载自: 掘金

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

MATLAB--数字图像处理 入门--采样 前言

发表于 2021-11-06

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

前言

Hello!小伙伴!

非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出~

自我介绍 ଘ(੭ˊᵕˋ)੭

昵称:海轰

标签:程序猿|C++选手|学生

简介:因C语言结识编程,随后转入计算机专业,有幸拿过一些国奖、省奖…已保研。目前正在学习C++/Linux/Python

学习经验:扎实基础 + 多做笔记 + 多敲代码 + 多思考 + 学好英语!

MATLAB–数字图像处理 采样

1、MATLAB软件安装后,点击进去,出现如下界面
其中右边空白区域(有fx那块区域),就是我们的编写代码的区域
左边就是工作区,有我们的一些变量值,上面就是软件的一些按钮,有一定的功能
在这里插入图片描述
2. 了解采样
首先,我们要知道采样是什么?
采样:我们获取到的图像一般为模拟图像,要让计算机进行处理需将其数字化,采样的作用就是将模拟图像转变为数字图像。一般来说,采样间隔越大,所得图像像素数越少,空间分辨率越低,质量差,严重时出现马赛克效应;采样间隔越小,所得图像像素数越多,空间分辨率越高,图像质量好,但数据量大。

简单的说,采样就是将图片数字化,采集的数字特征越多,其再计算机上显示的图像越逼真。

3、编写采样代码
首先准备一张图片,然后进入代码区域编写如下代码,按下Enter键,就有如下结果

1
2
3
4
scss复制代码t=imread('t1.jpg')   %读取图片
t1=rgb2gray(t) %将图片灰度化
t2=t1(1:4:end,1:4:end) %采样
imshow(t2) %显示在屏幕上

在这里插入图片描述

由于我也是才开始接触MATLAB和图像处理,有很多地方也还是不清楚。这里我说一下自己踩过的坑吧。

  1. 在MATLAB代码区换行是:shift+Enter
  2. 图片的路径有两种,一种是相对路径,一种是绝对路径。相对路径就是左边工作台有个路径,我们把图片存在这里,在编写代码的时候,就可以直接用图片的名字为路径(见下图,我们就直接存在bin这个目录下面就行)。第二种就是,输入图片在电脑存的路径,这个也好找,直接在文件里面就有在这里插入图片描述
  3. MATLAB默认只打开一个窗口,如果有2个imshow,只会出现第一imshow的内容,想要多个窗口,用figure。
  4. t2=t1(1:4:end,1:4:end) %采样 这句话的意思是,t2是计算机对t1横向、纵向都是每隔4的像素点取样,这样t2就会比t1更加模糊(采集的像素点少了)
  5. 有人会问,为什么一来要对图片进行灰度化处理呢? 这个是因为,一张图片,彩色的,有R、G、B三个通道的数据,也就是如果直接执行下面代码的话,会出现三种图片,对应R G B .t=imread('t1.jpg') t2=t(1:4:end,1:4:end) imshow(t2) 所以想要得到一张图片的话,就必须先进行灰度化处理。(这里我们图像处理都是先读取图片数据,读取的数据是以矩阵形式存在,矩阵的的值为灰度值,0~~255,没有彩色之分,所以彩色图片进来的话,系统会对r g b三个通道分别读取灰度值,也就形成了三张图片了。)
    在这里插入图片描述

完整代码及效果图:

1
2
3
4
5
6
7
8
9
10
11
scss复制代码t=imread('t1.jpg')
t1=rgb2gray(t)
imshow(t1),title('原图') %原图像 需要将其先转换为灰度图像
t2=t1(1:2:end,1:2:end)
t3=t1(1:4:end,1:4:end)
t4=t1(1:8:end,1:8:end)
t5=t1(1:16:end,1:16:end)
figure,subplot(2,2,1),imshow(t2),title('1:2采样')
subplot(2,2,2),imshow(t3),title('1:4采样')
subplot(2,2,3),imshow(t4),title('1:8采样')
subplot(2,2,4),imshow(t5),title('1:16采样')

在这里插入图片描述

在这里插入图片描述

本文转载自: 掘金

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

【Git】本地 git 绑定 github & git忽略更

发表于 2021-11-06

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

本地 git 绑定 github

首先需要安装好git,注册好github账号

  1. 生成公钥
    在任意处,git bash,输入以下命令
1
powershell复制代码 ssh-keygen -t rsa -b 4096 -C "注册github的邮箱"
1
2
powershell复制代码Generating public/private rsa key pair.
Enter file in which to save the key (/c/Users/Administrator/.ssh/id_rsa):“输入保存密钥的文件的文件名,可直接回车”
1
2
powershell复制代码Enter passphrase (empty for no passphrase):“可直接回车”
Enter same passphrase again:“可直接回车”
1
2
3
4
5
powershell复制代码Your public key has been saved in id_rsa.pub.
The key fingerprint is:
SHA256:NCYQZN5+D4HI9ZhuILjwiSJVo4t4ZEIjVZUznjWqbQE
。。。。。
。。。。

还会得到两个文件,在git bash打开的文件夹中

  1. 复制公钥
    在刚刚的git bash中
1
powershell复制代码$ clip < ~/.ssh/id_rsa.pub
  1. 设置sshkey
    打开自己的github主页
    点击最右上角的头像下的settings,找到SSH and GPR keys
    在这里插入图片描述
    在这里插入图片描述
    title的值自定义即可
    key的值填入,直接粘贴即可,我们已经使用clip命令复制了

在这里插入图片描述
这就完成了
绑定之后,就可以直接通过ssh地址来clone项目了

git忽略更新指定文件

方式一

通过修改.gitignore文件实现

  1. 打开通过git clone命令获取的项目
    在这里插入图片描述
  2. 打开.gitignore文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
javascript复制代码# Compiled class file
*.class

# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# 直接在这里添加即可,注意加上备注,好知道是忽略什么文件 #

配置语法:
以斜杠“/”开头表示目录;
以星号“*”通配多个字符;
以问号“?”通配单个字符
以方括号“[]”包含单个字符的匹配列表;
以叹号“!”表示不忽略(跟踪)匹配到的文件或目录;

方式二

通过命令实现
这种方式只能忽略文件,不常用

  1. 打开通过git clone命令获取的项目
  2. 输入命令
1
powershell复制代码git update-index --assume-unchanged 文件名
  1. 取消忽略
1
powershell复制代码git update-index --no-assume-unchanged 文件名

本文转载自: 掘金

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

this与super本质区别

发表于 2021-11-06

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

1、简介

this与super是类实例化时通往Object类通道的打通者;this和super在程序中由于其经常被隐式的使用而被我们忽略,但是理解其作用和使用规范肯定是必须的。接下来将详述this与super的作用和区别。

2、引子

先来看两段代码,无报错代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typescript复制代码package com.liziba.ts;

/**
* <p>
* 父类示例代码
* </p>
*
* @Author: Liziba
*/
public class Father {

private String name;

public String getName() {
return name;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
scala复制代码package com.liziba.ts;

/**
* <p>
* 子类示例代码
* </p>
*
* @Author: Liziba
*/
public class Son extends Father{

public Son(String name) {

}

}

此时将Father类中的构造函数修改为有参的构造函数,有错误的代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
arduino复制代码package com.liziba.ts;

/**
* <p>
* 父类示例代码
* </p>
*
* @Author: Liziba
*/
public class Father {

private String name;

public Father(String name) {
this.name = name;
}

public String getName() {
return name;
}

}

子类代码不修改,此时子类报错:

这就是super()的隐式使用导致的报错,具体原因是因为子类Son的构造函数中隐式的调用了父类的无参构造器,相当于隐式的super(),上面报错的代码和下面这个是等价的。

但是由于父类没有显示的申明无参构造函数,此时无参构造函数被有参构造函数覆盖了,所有super()调用无法抵达父类。此时的解决办法有两种:

1、父类中声明无参构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
arduino复制代码public class Father {

public String name;

// 父类什么无参构造函数
public Father() {
}

public Father(String name) {
this.name = name;
}
}

public class Son extends Father {

public Son(String name) {
super(); // 可以省略
}
}

2、子类显示的通过super调用父类的有参构造函数

1
2
3
4
5
6
7
scala复制代码public class Son extends Father {

public Son(String name) {
// 调用父类的有参构造函数
super(name);
}
}

接下来将详细分析this和super的作用和区别。

3、this

this相当于当前对象实例,或者当前对象的一个引用,this有如下作用:

  1. 调用当前对象中的方法和属性
  2. 区分对象属性和方法形参
  1. 调用构造方法(必须在构造函数的第一行)

this相当于当前对象实例举例:

1
2
3
4
5
6
7
8
9
10
11
scala复制代码public class Son extends Father{

private String homework = "Java编程思想";

public void doSomething() {
// this 相当于当前Son对象实例
synchronized (this) {

}
}
}

调用当前对象中的方法和属性举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typescript复制代码public class Son extends Father{

private String homework = "Java编程思想";

public void doSomething() {
// this 获取当前对象的属性
String hn = this.homework;
// this 调用当前对象的方法
this.doSomething2();
}

public void doSomething2() {
// toDo
}
}

区分对象属性和方法形参举例:

1
2
3
4
5
6
7
8
9
10
scala复制代码public class Son extends Father{

private String homework = "Java编程思想";

public Son(String homework) {
// 区分对象属性与方法形参
this.homework = homework;
}

}

调用其他构造方法举例:

1
2
3
4
5
6
7
8
9
10
11
12
scala复制代码public class Son extends Father{

private String homework = "Java编程思想";

public Son(String homework) {
// 调用其他构造方法,必须在第一行
this(homework, "你们未来都是架构师");
}

public Son(String homework, String name) {
}
}

4、super

super可以理解为父类(直接父类,如果有多层继承关系这里指的是最近的父类)对象的引用。super有如下的作用:

  1. 调用父类非私有的属性和方法
  2. 区分当前类与父类同名的属性和方法
  1. 调用父类的构造函数(必须在构造函数的第一行)

调用父类属性和方法示例:

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
typescript复制代码/**
* 父类
*/
public class Father {

public String name;

public void doSomething3() {
// toDo
}

public void doSomething4() {
// toDo
}
}

/**
* 子类
*/
public class Son extends Father{

public void doSomething() {
// 调用父类的非私有方法
super.doSomething3();
super.doSomething4();
// 调用父类的非私有属性
String name = super.name;
}

}

区分当前类与父类同名的属性和方法示例:

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
typescript复制代码/**
* 父类
*/
public class Father {

public String name;

public void doSomething3() {
// toDo
}

public void doSomething4() {
// toDo
}
}


/**
* 子类代码修改成如下
*/
public class Son extends Father {
public String name;

public void doSomething() {

// super可以区分父类方法与当前对象的方法
doSomething3();
doSomething4();
super.doSomething3();
super.doSomething4();

// 区分当前父类的属性与当前类的属性
String fatherName = super.name;
String sonName = name;
}

@Override
public void doSomething3() {
// todo
}

@Override
public void doSomething4() {
// todo
}
}

调用父类的构造函数(必须在构造函数的第一行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
scala复制代码/**
* 父类
*/
public class Father {

public String name;

public Father(String name) {
this.name = name;
}
}

/**
* 子类
*/
public class Son extends Father {

public Son(String name) {
super(name);
}
}

5、总结

5.1 对比差异

this 基本概念

  • 访问本类实例属性和方法

super 基本概念

  • 访问父类实例属性和方法

this 查找范围

  • 先查找本类,不存在再查找父类

super 查找范围

  • 直接查找父类

this 其他功能

  • 单独使用表示当前对象

super 其他功能

  • 子类复写父类方法,用于访问父类同名方法

5.2 相同点

  • 都是关键字,起指代作用
  • 构造方法中必须在第一行调用其它构造函数

5.3 总结图

本文转载自: 掘金

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

1…409410411…956

开发者博客

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