0%

子矩阵的最大累加和问题
给定一个矩阵matrix,其中的值有正有负有0,返回子矩阵的最大累加和
例如,矩阵matrix为:
-90 48 78
64 -40 64
-81 -7 66
其中,最大累加和的子矩阵为:
48 78
-40 64
-7 66
所以返回累加和209

例如,matrix为:
-1 -1 -1
1 2 2
-1 -1 -1
其中最大累加和的子矩阵为
2 2
所以返回累加和为4

假设一个2行4列的矩阵如下:
-2 3 -5 7
1 4 -1 -3

可以先把两行元素累加,然后得到累加数组[-1,7,-6,4],接下来求这个累加数组的最大累加和,结果是7.也就说,必须含有2行元素的子矩阵的最大和为7,且这个子矩阵是
3
4
也就是说,如果一个矩阵共有k行且限定必须有k行元素的情况下,我们只要把矩阵中每一列的k个元素累加生成一个累加数组,然后求出这个数组的最大累加和,这个最大累加和就是必须含有k行元素的子矩阵中的最大累加和

利用题目的第一个例子来讲述:
首先考虑只有一行的矩阵[-90.48,78],因为只有一行,所以累加数组arr就是[-90.48.78]这个数组的最大累加和为126

接下来考虑含两行的矩阵
-90 48 78
64 -40 64
这个矩阵的累加数组就是上一步的累加数组[-90.48,78]的基础上,依次在每个位置上加上矩阵最新一行[64,-40,64]的结果,即[-26,8,142],这个数组的最大累加和为150.

接下来考虑含有3行的矩阵:
-90 48 78
64 -40 64
-81 -7 66
这个矩阵的累加数组就是上一步累加数组[-26,8,142]的基础上,依次在每个位置上加上矩阵的最新一行[-81 -7 66]的结果,即[-107,1,208],这个数组的最大累加和为209。此时,必须从矩阵第一行元素开始,并往下的所有子矩阵都已查找完毕,接下来从矩阵的第二行开始,继续这样的过程。。。

整个过程中最关键的地方有两处:

  • 用求累加数组的最大累加和的方式得到每一步的最大子矩阵的累加和
  • 每一步的累加数组可以利用前一步求出的累加数组很方便的得到
    由于求一个数组的最大子数组累加和时间复杂度为O(N),所以如果矩阵大小为N×N,则全部过程的时间复杂度为O(N^3)
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

public int maxSum(int[][] m){
if(m==null || m.length==0 || m[0].length==0)
return 0;
int col = m[0].length;
int[] sumArr = new int[col];
int res = Integer.MIN_VALUE;
for(int i=0;i<m.length;i++){
Arrays.fill(sumArr, 0);
for(int j=i;j<m.length;j++){
for(int k=0;k<col;k++){
sumArr[k] += m[j][k];
}
res = Math.max(res, maxSum(sumArr));
}
}
return res;
}

//求子数组累加最大和
public int maxSum(int[] arr){
if(arr==null || arr.length==0)
return 0;
int sum = arr[0];
int res = sum;
for(int i=1;i<arr.length;i++){
if(arr[i]+sum>arr[i]){
sum += arr[i];
}else
sum = arr[i];
res = Math.max(sum, res);
}
return res;
}

代码质量与整洁度成正比
全员生产维护(TPM 一种质量保证升段):5S:

  • 整理(Seriri):即组织
  • 整顿(Seiton):所谓整齐
  • 清楚(Seiso):所谓清洁
  • 清洁(Seiketsu):所谓标准化
  • 身美(Shitsuke):所谓纪律(自律)

小处的东西往往能影响大局,比如简单的缩进风格对于软件质量的提高不逊于架构、编程语言等高级概念

衡量代码质量的唯一有效标准:WTF/min (笑)

整洁代码

认为机器自动生成代码代替人工是不科学的,代码永存

勒布朗法则:稍后等于永不

整洁的代码只做好一件事。它简单直接,从不隐藏设计者的意图,充满了干净利落的抽象和直截了当的控制语句

代码要尽量少,越小越好

童子军军规:让营地比你来时更干净

有意义的命名

一旦发现有更好的名称,就换掉旧的。

不要用xxList来指称一组东西,除非它真的是List类型,List一次有特殊意义,如果这个容器并非真的是个List,就会引起错误的判断,所以用XXGroup,或复数形式会更好,即便容器就是个List,最好也别在名称中写出容器类型名。

提防使用不同之处较小的名称,如XYZContorllerForEffcientHandlingOfStrings和XYZContorllerForEffcientStorageOfStrings

不要用误导性名称,如小写字母l和大写字母O作为变量名,看起来像数组1和0

如果只为满足编译器和解释器而改写代码就会制造麻烦,比如同一作用范围内两样不同的东西不能重名,可能会随手改变其中一个名称,或以错误的拼写充数,结果就是出现在更正拼写错误后编译器出错(如class已有他用,就给一个变量命名为klass,相当可怕的做法)

以数字系列命名(a1,a2.。。aN)这样的名称纯属误导。

废话都是冗余,variable一词永远不该出现在变量中。Table一词永远不该出现在表名中。NameString不会比Name更好(难道Name会是一个浮点数?)如果缺少明确约定,变量moneyAmount和money没区别,customerInfo和customer没区别,accountData和account没区别,theMessage和message没区别。要区分名称,就要以读者能鉴别不同之处的方式来区分。

使用可搜索的名称

单字母名称仅用于短方法中的本地变量,名称长短应该与其作用域大小相对应。

避免使用编码

把类型或作用域编进名称里,徒然增加了解码的负担。

  • java变量是强类型的,不需要匈牙利标记法
  • 不必用m_前缀来标明成员变量。应该把类和函数做的足够小,消除对成员前缀的需要。

类名

类名和对象名应该是名词或名词短语,如Customer、WikiPage、Account、AddressParser。避免使用Manager、Processor、Data、Info这样的类名。类名不应该是动词。

方法名

方法名应当是动词或动词短语,如postPayment、deletePage或save。属性访问器、修改器和断言应该根据其值命名,并依标准加上get、set、is前缀。

重载构造器时,使用描述了参数的静态工厂方法名通常好于构造器中直接传入意义不明的函数,如:

1
2
3
Complex f = Complex.FromRealNumber(23.0);
好于
Complex f = new Complex(23.0);

可以考虑将相应的构造器设置为private来强制使用这种命名手段。

名称不要耍宝

不要使用某一种文化特有的俚语

每个概念对应一个词

给每个抽象概念选一个词,并且一以贯之。例如,fetch、retrieve、get给多个类中的同种方法命名是可怕的,最好就选一个用到底

别用双关语

例如使用add,在多个类中都有add方法,该方法通过增加或连接两个现存值来获得新值。假设要写一个新类,其中有个方法,把单个参数放到群集中去,该把这个方法叫做add吗?这样做貌似和其他add方法保持一致,但实际上语义却不同。一个用insert或append之类的词,把该方法命名为add就是双关语了。

使用解决方案领域名称

使用源自所涉问题领域的名称

添加有意义的语境

不要添加没用的语境

只有短名称足够清楚,就要把长名称好,别给名称添加不必要的语境

函数

短小

函数第一规则是短小,第二规则是还要更短小。
函数20行封顶最佳

if、else、while语句等,其中的代码块应该只有一行。该行应该是一个函数调用语句,这样不但能保持函数短小,且块内调用的函数拥有较具说明性的名称。

只做一件事

函数应该做一件事。做好这件事,只做这一件事。
要判断函数是否不止做了一件事,还有一个方法,就是看是否能再拆出一个函数,该函数不仅只是单纯地重新诠释其实现

每个函数一个抽象层级

例如,getHtml位于较高抽象层,PathParser.render中间抽象层,StringBuilder.append低抽象层。
函数中混杂不同抽象层级往往让人迷惑,可能无法判断某个表达式是基础概念还是细节。

自顶向下读代码:向下规则

我们想要让代码拥有自顶向下的阅读顺序,想要让每个函数后面都跟着位于下一个抽象层级的函数,这样一来,在查看函数列表时就能循抽象层级向下阅读了。

switch语句

写出短小的switch语句很难,不过还是能够确保每个switch都埋藏在比较低的抽象层级,而且用于不重复。

1
2
3
4
5
6
7
8
9
10
11
12
public Money calculatePay(Employee e){
switch(e.type){
case COMMISSONED:
return caculateCommissionedPay(e);
case HOURLY:
return caculateHourlyPay(e);
case SALARIED:
return caculateSalariedPay(e);
default:
throw new InvalidEmployeeType(e);
}
}

上述代码问题:首先它太长,如果出现新雇员类型,还会变的更长。其次,他名下做了不止一件事,第三,它违反了单一职责原则。第四,它违反了开放闭合原则,每当添加新类型时,就必须修改之。

该问题的解决方案,是将switch语句埋到抽象工厂低下,不让任何人看到,该工厂使用switch语句为Employee的派生物创建适当的实体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface EmployeeFactory{
public Employee makeEmployee(EmployeeRecord r)
}

public class EmployeeFactoryImpl implements EmployeeFactory{
public Employee makeEmployee(EmployeeRecord r){
switch(r.type){
case COMMISSONED:
return new ComissionedEmployee(r);
case HOURLY:
return new HourlyEmployee(r);
case SALARIED:
return new SalariedEmployee(r);
default:
throw new InvalidEmployeeType(e);
}
}
}

对于switch语句,推荐的规则是如果只出现一侧,用于创建多态对象,而且隐藏在某个继承关系中,在系统其它部分看不到,就还能接受,当然就事论事,有时也会部分或全部违反该规则

使用描述性的名称

沃德原则:如果每个例程都让你感到深合己意,那就是整洁代码

别害怕长名称。长而具有描述性的名称,要比短而令人费解的名称好。长而具有描述性的名称,要比描述性的长注释好。使用某种命名约定,让函数名称中的多个单词容易阅读,然后使用这些单词给函数取个能说清其功能的名称。

命名方式要保持一致。使用与模块名一脉相承的短语,名词和动词给函数命名。

函数参数

最理想的参数数量是0,其次是1,再次是2,应尽量避免3.有足够的理由才能使用三个以上参数。

标识参数丑陋不堪。向函数传入布尔值简直就是骇人听闻的做法。这样做,方法签名立刻变得复杂起来,宣布本函数不止做一件事,如果表示为true将会这样做,标识为false则会那样做。

如果函数看起来需要三个以上参数,就说明其中一些参数应该封装为类了

给函数取个好名字,能较好地解释函数的意图,以及参数的顺序和意图。对于一元函数,函数和参数应当形成一种非常良好的动词/名词对形式。例如write(name)

必须保证函数“只做一件事”,才能有效避免副作用。

函数要么做什么事,要么回答什么事,但二者不可兼得。函数应该修改某对象的状态,或是返回该对象的有关信息。两样都干会导致混乱。

使用异常代替返回错误码。如果这样做,错误处理代码就能从主路径代码中分离出来,得到简化
4c8bf45524e5691088c803b01e63b20f.png
25b17f5755b4cb67da2d9fbf52326a01.png

最好吧try-catch代码块从主体部分抽离处来,另外形成函数。

返回错误码通常暗示某处有个类或是枚举,定义了所有错误码。
这样的类就是一块依赖磁铁,其他许多类都得导入和使用它,当其枚举修改时,所有这些其他类都需要重新编译和部署。而使用异常代替错误码,新异常就可以从异常类派生出来,无需重新编译或重新部署。

尽量不要重复。重复可能是软件中一切邪恶的根源,许多原则和实践规则都是为控制与消除重复而创建。如数据库范式为了消灭数据重复而服务。OOP将代码集中到基类从而避免冗余。aop、cop(面向组件编程)多少也都是消除重复的一种策略。

只要函数保持短小,偶尔出现的return,break,continue语句没有坏处,甚至比单入单出原则更具有表达力。另一方面,goto只在大函数中才有道理,所以应该尽量避免使用。

写代码和写别的东西一样,先是想什么就写什么,然后再打磨它。初稿也许粗陋无序,就会斟酌推敲,直至满意。一开始都冗长而复杂,有太多缩进和嵌套循环。有过长的参数列表。名称是随意取得,也会有重复代码。然后打磨这些代码,分解函数,修改名称,消除重复。还会缩短和重新安置方法。有时还拆散类,同时保持测试通过。最后遵循规则组装好这些函数。
并不是一开始就按照规则写函数。这没人做得到。

注释

注释的恰当用法是弥补用代码表达意图时遭遇的失败。注释总数一种失败,我们总无法找到不用注释就能表达自我的方法,所以要有注释。
但注释会撒谎,只是存在的时间越久,就离其描述的代码越远,越来越变得错误,原因很简单,程序员不能坚持维护注释。

不准确的注释比没有注释坏的多。真实只在一处地方有:代码。只有代码能忠实地告诉你它做的事。尽管有时也需要注释,但我们也该花心思尽量减少注释量。

注释不能美化糟糕的代码。

有些注释是必须的,也是有利的,不过要记住,唯一真正好的注释是你想办法不去写的注释
好的注释

  • 法律信息
  • 提供信息的注释
  • 对意图的解释
  • 阐释 即把某些晦涩难懂的参数或返回值的意义翻译为某种可读形式
  • 警示 警告其他程序员会出现某种后果
  • TODO注释:在源码中放置要做的工作列表。
  • 放大:注释可以用来放大某种看来不合理之物的重要性
  • 公共API中的javadoc

坏注释:

  • 喃喃自语

  • 多余的注释

  • 误导性注释

  • 循规式注释:所谓每个函数都要有javadoc或每个变量都要有注释的规矩是愚蠢可笑的。

  • 日志式注释

  • 废话式注释

  • 能用函数或变量时就别用注释。

  • 位置标记

  • 括号后面的注释

    1
    2
    3
    4
    5
    6
    7
    8
    9
    try{
    。。。
    while(){
    ...
    } //while
    }//try
    catch(Exception e){
    ...
    }//catch
  • 归属与签名:用签名搞脏代码很不好,合适的位置是代码版本控制工具(如git)

  • 注释掉的代码

  • html注释

  • 非本地信息:例如别在本地注释的上下文环境中给出系统级的信息,注释应该保证描述了离它最近的代码

  • 信息过多

  • 不明显的联系

  • 函数头:短函数不需要太多描述,选个好名字通常比写函数头注释好

  • 范例

格式

沟通是专业开发者的头等大事(而不是“让代码工作”)

垂直格式

一般的源码文件不要超过500行,否则可以考虑拆分
每个空白行都是一条线索,标识出新的独立概念。比如方法之间就应该有一个空白行。

紧密相关的代码应该互相靠近。

垂直距离

  • 变量声明:变量声明应尽可能靠近其使用位置。因为函数很短,本地变量应该在函数的顶部出现。
  • 循环中的控制变量应该总是在循环语句中声明。
  • 偶尔,在较长的函数中,变量可能在某个代码块顶部,或在循环之前声明。
  • 实体变量应该在类的顶部声明(java的习惯)
  • 相关函数:若某个函数调用了另一个,就应该把它们放到一起,而且调用者应该尽可能放到被调者的上面。
  • 概念相关:概念相关的代码应该放到一起。相关性越强,彼此之间的距离就该越短:相关性应建立在直接依赖的基础上,如函数间调用,或者执行相似操作的一组函数也是相关性很高的(如共同的命名模式,执行同一基础任务的不同变种)

垂直顺序

一般而言,被调用的函数应该放在执行调用的函数下面。最重要的概念应该先出来。

水平格式

一般来说,一行的上限为120个字符

空格字符可以强调其前面的运算符,如

1
2
xxx / yyy
b*b - 4*a*c

乘法因子之间没加空格,因为他们具有较高优先级,加减法运算符之间用空格隔开,因为其优先级较低。

现代语言可以用不对齐的声明和赋值,因为它们指出了重点。如果有较长的列表需要做对其处理,那么问题就是在列表的长度上而不是对齐上。(按正常的格式来即可)

缩进用来体现层级结构,有了缩进会使得程序更容易阅读,没有的话就会无法阅读。尽量不要违反缩进规则,即使是在短小的if,while中。

有时while或for语句的体为空,如果无法避免地使用,则要确保空范围体的缩进,用括号包围起来。不然很容易被while循环语句同一行末尾的分号所欺骗,除非把分号放到另一行再加以缩进。

1
2
3
4
5
6
7
8

while(xxx); //很容易忽略

while(xxx){
} //good

while(xxx)
; //good

作者的格式规则

成员变量在最上面
构造函数
静态函数(根据调用关系排序,调用者在上,被调者在下)
成员函数(同上)
函数要尽可能短

对象和数据结构

我们一般不愿暴露数据细节,更愿意以抽象形态表述数据,这并不只是用接口或getter、setter就万事大吉。要以最好的方式呈现某个对象包含的数据,需要做严肃的思考

数据、对象和反对称性

》过程式代码难以添加新数据结构,因为必须修改所有涉及到相关操作的函数。
(比如对圆形、矩形的面积计算工具类中,如果要添加新的类型数据结构,则必须要修改工具类中所有根据类型来操作的具体函数。但如果要添加新的功能函数很简单,只需在工具中增加该新函数即可)
》面向对象代码难以添加新函数,因为必须修改所有类。
(同样,如果所有的形状类型都继承基类,并且每个都重写了其操作函数,添加新类型是很容易的,只需要重写相应的基类函数即可,但如果要添加新的功能函数,就必须对每一个实现类都添加该函数的实现)

德墨忒尔律(The Law of Demeter)

该规律认为,模块不应了解它所操作对象的内部情形。对象隐藏数据,暴露操作。这意味着对象不应通过存取器暴露其内部结构,因为这样更像是暴露而非隐藏其内部结构。
德墨忒尔律认为,类C的方法f只应该调用以下对象的方法:

  • C
  • 由f创建的对象
  • 作为参数传递给f的对象
  • 由C的实体变量持有的对象
    方法不应该调用由任何函数返回的对象的方法,即:只跟朋友谈话,不与陌生人谈话
    1
    2
    3
    String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

    //上述代码违反了德墨忒尔律,因为它调用了getOptions()返回值的getScratchDir()函数,又调用了getScratchDir()返回值的getAbsolutePath()函数。
  • 常用的调用链模式明显违反了该律,但却带来了编码上极大的方便,因此对待这种规律也得辩证去看,不能一味死守教条。没有通用的规律,只有具体的场景*

数据传送对象

只有一个公共变量,没有函数的类叫做数据传送对象(DTO)。更常见的是bean结构,它具有赋值器和取值器操作的私有变量。(一般的javabean)

错误处理

使用异常而非返回码

先写try-catch-finally语句

在编写可能抛出异常的代码时,最好先写出try-catch-finally语句,这能帮你定义代码的用户应该期待什么,无论代码块中执行的代码出什么错。

使用不可检查异常

可检查异常的代价是违反开放封闭原则。如果在方法中抛出可检查异常,而catch语句在三个层级之上,就意味着对软件中较低层级的修改,都将波及到高层级的签名。修改好的模块必须重新构建、发布,即使他们自身所关注的任何东西都没改动过。

给出异常发生的环境说明

应该创建信息充分的错误消息,并和异常一起传递出去,包括失败的操作和失败类型,而不应该仅仅是堆栈踪迹。

依调用者需要定义异常类

打包:当打包一个第三方API,就降低了对它的依赖,未来可以不太痛苦地改用其他代码库,也不用绑死在某个特定厂商的API设计上。

对于代码的某个特定区域,单一异常类通常可行。伴随异常发送出来的信息能够区分不同错误。一个想要捕获某个异常,并且放过其他异常,就使用不同的异常类。

别返回null值

特例模式:创建一个类或配置一个对象,用来处理特例,由底层来处理特例,客户代码就不用应付异常行为了。异常行为被封装到特例对象中。

如果打算在代码中返回null值,不如抛出异常,或是返回特例对象。如果在调用某个第三方API中可能返回null值的方法,可以考虑用新方法打包这个方法,在新方法中抛出异常或返回特例对象。

别传递null值

除非API要求传递null,否则要尽可能避免传递null值。

边界

尽量避免把容器(或在边界上的其他接口)在系统中传递。把这种边界接口留在类或近亲类中,避免从公共API中返回边界接口,或将边界接口作为参数传递给公共API。

学习性测试

学习第三方代码很难,整合第三方代码也很难,同时做这两件事难上加难。而如果采用不同的做法,不要在生产代码中实验新东西,而是编写测试来浏览和理解第三方代码,就有更好的理解效果。

不要一上来就看源码,或者整合源码。多写测试程序,去发现不同的用法,发现问题,有了问题后再以问题为导向去看源码中的解决方法,否则很容易就陷入到无穷无尽的“代码海”中

使用尚不存在的代码

如果有的API还没设计出来,则我们可以先自己设计一套fake的,然后来根据它设计,一旦真实的设计出来后,就可以编写适配器来跨界,这样原先的逻辑几乎不用改动。所做的工作只是适配器

整洁的边界

单元测试

TDD:测试驱动开发

TDD三定律

  1. 在编写不能通过的单元测试前,不可编写生产代码。
  2. 只可编写刚好无法通过的单元测试,不能编译也算不通过
  3. 只可编写刚好足以通过当前失败测试的生产代码
    这三条定律将你限制在大概30s一个的循环中。测试和生成代码一起编写,测试只比生产代码早写几秒中

保持测试整洁

测试必须随生产代码的演进而修改。测试越脏,就越难修改,测试代码越缠结,你就越有可能话更多时间塞进新测试,而不是编写新生产代码。修改生产代码后,旧测试就会开始失败,而测试代码中乱七八糟的东西将阻碍代码再次通过,余数测试变得就像是不断翻番的债务。
测试代码和生产代码一样重要,他需要被思考,被设计和被照料,它该像生产代码一般保持整洁

整洁测试

整洁的测试的要素:可读性、可读性、还是可读性

测试应该遵循的模式:构造-操作-检验
第一个环节构造测试数据,第二个环节操作测试数据,第三个部分检验操作是否得到期望的结果

每个测试一个断言

每个测试函数应该有且只有一个断言,虽然苛刻,但方便理解(但不好做到)

更好一些的规律或许是每个测试函数只测试一个概念,不要超长的测试函数,测完这个又测那个。

最佳规则应该是尽可能减少每个概念的断言数量,每个测试函数只测试一个概念。

F.I.R.S.T

整洁的测试应遵循以下5条规则:

  • 快速:测试应该足够快
  • 独立:测试应该相互独立,某个测试不应为下一个测试设定条件
  • 可重复:测试应当可在任何环境中重复通过
  • 自足验证:测试应该有布尔输出,不论通过或失败,不应该查看日志文件,或手工对比不同的文件来确认测试是否通过
  • 及时:测试应该及时编写,单元测试应该恰好在使其通过的生产代码之前编写。

类的组织:

类应该从一组变量列表开始。先是公共静态常量,然后是私有静态变量,私有实体变量,最后是公共变量。
公共函数跟在变量列表之后。我们喜欢把某个公共函数调用的私有工具函数紧随在该公共函数后面。

类应该短小

单一权责原则(SRP)

类或模块应有且只有一条加以修改的理由,就是它只应有一个权责。
系统应该由许多短小的类而不是少量巨大的类组成。每个小类封装一个权责,只有一个修改的原因,并与少数其他类一起协同达成期望的系统行为。

内聚

类应该只有少量实体变量。类中的每个方法都应该操作一个或多个这种变量。方法操作的变量越多,就越粘聚到类上。说明内聚性越高。
保持函数和参数列表短小,有时会导致一组子集方法所用的实体变量数量增加。出现这种情况,往往意味着至少有一个类要从大类中挣扎出来。你应当尝试将这些变量和方法拆分到两个或多个类中,让新的类更内聚,

保持内聚性就会得到许多短小的类

如果类堆积了很多只为少量函数而共享的实体变量,就应该拆分该类,让小的类更内聚。所以,将大函数拆分为许多小函数,往往也是将类拆分为多个小类的时机。

为了修改而组织

开放封闭原则(OCP)

类应该对扩展开放,对修改封闭。一般是通过子类化手动端,重新架构的新类对新功能是开放的,同时可以不触及其他类。

依赖倒置原则(DIP)

类应该依赖于抽象而不是依赖于具体细节。

系统

将系统的构造与使用分开

软件系统应该将启始过程和启始过程之后的运行时逻辑分离开,在启始过程中构建应用对象,也会存在相互缠结的依赖关系。

分解main

方法之一是将全部构造过程搬迁到main或被称之为main的模块中

工厂

使用抽象工厂来创建具体的项目

依赖注入

在依赖管理的情景中,对象不应负责实体化对自身的依赖。反之,它应当将这份权责移交给其他“有权力”的机制,从而实现控制反转。因为初始设置是一种全局问题,这种授权机制通常要么是main例程,要么是有特定目的的容器。

扩容

软件系统与物理系统可以类比,它们的架构都可以递增式地增长,只要我们持续将关注面恰当地切分。在aop中,被称为方面(aspect)的模块构造指明了系统哪些点的行为会以某种一致的方式被修改,从而支持某种特定的场景。行为的修改由AOP框架以无损方式在目标代码中进行。

下列是Java中三种方面或类似方面的机制

Java代理

jdk提供的动态代理仅能与接口协同工作。对于代理类,得使用字节码操作库,比如cglib,asm或javaassist
代码量和复杂度是代理的两大弱点,创建整洁代码变得很难。另外,代理也没有提供在系统范围内指定执行点的机制。而这正是真正的AOP解决方案所必须的。

纯JavaAOP框架

如spring

aspectJ的方面

通过方面来实现关注面切分的功能最全工具是aspectj语言,在80%到90%用到方面特性的情况下,springaop和jboss aop提供的手段已经足够,但aspectj是更强有力的工具,其弱势在于需要采用新工具,学习新语言构造和使用方式。

优化决策

模块化和关注面切分成就了分散化管理和决策,最好是授权给最有资格的人。

迭进

kent Beck简单设计四条规则

  • 运行所有测试
  • 不可重复
  • 表达了程序员的意图
  • 尽可能减少类和方法的数量
    以上规则按重要程度排列

并发编程

对象是对过程的抽象,线程是对调度的抽象

为什么要并发

并发是一种解耦策略,他帮助我们把做什么(目的)和何时(时机)分解开。单线程应用中,目的与时机紧密耦合,很多时候只要查看堆栈追踪即可断定应用程序的状态。

解耦目的和时机能明显地改进应用程序的吞吐量和结构

  • 并发并不总能改进性能
  • 并发算法的设计有可能与单线程系统的设计极不相同
  • 采用web或EJB容器时,最后了解并发问题,直到容器在做什么

关于编写并发软件的中肯说法:

  • 并发会在性能和编写额外代码上增加一些开销
  • 正确的并发是复杂的,即使对于简单的问题也是如此
  • 并发缺陷并非总能重现,所以常被看作偶发事件而忽略,未被当做真的缺陷看待;
  • 并发长处需要对设计策略的根本性修改。

并发防御原则:

单一权责原则:

应该分离并发相关代码和其他代码

限制数据作用域

谨记数据封装,严格限制对可能被共享的数据的访问

使用数据副本

使用副本,从一开始就避免共享数据

线程应尽可能独立

尽可能缩小同步区域

尽早考虑关闭问题,尽早令其工作正常。

一些建议:

  • 将伪失败看作可能的线程问题(伪失败:不可能失败的失败,几率极小会出现)
    最好假设偶发事件根本不存在,否则代码可能搭建于不完善的基础上
  • 先使非线程代码可工作
    不要同时追踪非线程缺陷和线程缺陷,确保代码在多线程之外可工作
  • 编写可插拔的线程代码
    这样就能在不同的配置环境下运行
  • 编写可调整的线程代码
  • 运行多于处理器数量的线程
    系统在切换任务时会发生一些事,为了促使任务交换的发生,运行多于处理器或处理器核心数量的线程。任务交换越频繁,越有可能找到错过临界区或导致死锁的二代吗
  • 在不同平台上运行
    尽早并经常在所有目标平台上运行线程代码
  • 装置试错代码
    有时候只有少数路径会真的导致失败,但经过几率非常低。可以考虑装置代码,增加对Object.wait()、Thread.sleep()、Thread.yield()、Object。priority()等方法的调用,改变代码执行顺序。这些方法都会影响执行顺序,从而增加了侦测到缺陷的可能性
    可以使用一些面向切面的框架来构建调用上述方面的切面,来做随机测试

逐步改进

对一个命令行参数解析程序的案例研究

本案例非常详细清晰地描述了如何重构一个命令行参数解析程序
作用:对例如“-l -p 50 -d abc”的字符串做解析
当调用getInt(‘p’)时能得到50

具体过程见书

整个过程体现了抽象、封装的思想,尽可能用基类去实现相似的功能来避免重复。

JUnit内幕

是一个比较两个字符串的工具的重构过程。和上一章内容类似,都是详细描述了重构的过程

不要用实现接口的形式去获得接口中的常量,应该使用静态导入的方法:

1
import static XXXContants.*;

根据java内存模型,32位值的赋值操作是原子的,不可中断的;而64位值的赋值需要两次32位赋值,这意味着第一次和第二次32位赋值之间,其他线程可能插进来,修改其中一个值。

时至今日,经历了3个月的找实习工作终于落下了帷幕,幸运的是,最终拿到了自己最心仪的蚂蚁金服的offer。往年的话在现在这个时间点本来应该在实验室搬砖的,而今年受到疫情的影响,到现在还在家中。但在家也有在家的好处,在家如果有压力的话其实也能够全神贯注地去学习去准备找实习。

上次过年本来打算只在家呆两周就回学校复习的,但因为疫情导致回学校遥遥无期。时间不等人,各个公司招聘的节奏又跟往常一样,所以早早在家就开始复习了。一开始给自己定的目标,最理想的是蚂蚁金服,因为自己比较喜欢金融这一块的东西。蚂蚁算是国内金融+互联网这一块的巨头(貌似是估值最高的独角兽?第二是字节)。支付宝和微信几乎成了每个人必备的工具。发展的前景不可谓不好。在蚂蚁金服之后的理想的去向是字节跳动和腾讯。字节跳动作为估值仅次于蚂蚁金服的大厂,在近几年凭借着头条,抖音的火爆而迅速崛起。字节的产品可以说是占据了当代中青年内容输入的大头。我的同学,朋友,包括爸妈,爷爷奶奶都是头条或抖音的拥趸,每天把看头条当做读报纸一样的习惯去看。我自己也在疫情期间几乎每天都上头条了解国内外疫情的最新进展。在内容质量,排版方面来说,头条做的确实要比其他一些新闻应用做得好。例如相比于腾讯新闻,其内容的质量,排版都要更优(现在的腾讯新闻已经变成了百家号的聚集地,芝麻大的事情能发1000字的文章,狗屁不通浪费时间,如果腾讯新闻再不把把质量关感觉真的不行)。而相比于澎湃这种更专业的新闻应用。它又显得更亲民,少了一些复杂的专业术语,多了一些家长里短的闲聊。而抖音更是被誉为当代年轻人的精神鸦片,其影响力可见一斑。腾讯就不用多说了,游戏大厂,不说口碑,但用户量应该是国内毫无争议的第一。我给自己定的目标就是这三个,蚂蚁金服第一,腾讯or字节第二。但蚂蚁因为是阿里系,技术栈是java的,我自己本身的用java的,虽然说一个优秀的程序员无所谓语言,但是更换语言,尤其是对一门语言了解的越深入,更换的成本就越大,对于一门新语言的机制,语言特性都要重新学起。所以在语言方面,阿里对于我无疑是更合适的选择;而腾讯用的是C++,字节多用python和go,都需要更换成本。

这次算是第二次找工作了,自己也是有了之前一些找工作的经验,而且自己本身就耽误了1,2年,所以也没有时间再去试错,就要一步到位去定居的地方了。距离家乡近的,互联网方向发展前景好的地方,看下来应该就只有西安和成都两个地方。相比西安,成都的发展前景必然更好,天府之国,气候也湿润,其实是更适合人居住的。所以这次找工作就只把目标定在了成都,专心找成都的工作。

字节跳动

从2月中旬开始着手复习,先是把深入理解JVM又全面地看了一遍,把自己之前做的笔记也认真复盘,自己的项目也全面复盘了一遍。在二月底的时候就开始着手投简历了,其实这算投的比较早了,不过这种事情赶早不赶晚。早点投了简历也能逼迫自己早点进入学习的状态,先是投了一波京东,字节,和网易雷火。京东和字节是成都的,网易是杭州的,是为了练手(但网易效率真的太低了。。直到2个月后我都落定了才来了笔试邀请,有事就给鸽了🥶,京东到现在都没下文,要是能把北邮的简历都给筛了只能说明真的强无敌。。)。所以最后只来了字节的笔试邀请。成都的字节跳动是效率工程,之前是专门为内部hr做提高效率的工具,属于对内的一个团队。不过后来听说也要对外去卖产品了。非常有名的飞书(Lark)就是其产品,可以说是做的相当不错,国外也有很多团队在用。

这里要夸一波字节,效率真的高,全程都有hr跟进进度,不论结果怎样都会通知一声,而且流程很正式。就全程都给人感觉很舒服。字节的笔试不难,算是leetcode的medium难度。题目如下:

1
2
3
4
5
6
1.峰值定义:比前后元素都大;数组可能存在多个峰值,返回任意一个就行:如1 2 3 2 1 4 3,可以返回4或者3
>直接遍历比较前后即可,唯一需要注意的就是边界上的额外处理题目不难。这道题a过了


2.输出指定的二叉树的镜像:以二叉树对应的完全二叉树为参照,空白节点处使用#字符填充,使用层次遍历来表示二叉树,节点间使用空格分隔,如4 2 7 # 3 6 9。要求输出翻转的二叉树的层序。
>本来想直接按照层序遍历来按照节点的编号顺序来做的,其实画个图就能看出来,满二叉树只需要每层倒序就是其镜像树。但这么做只能a过80%。之后因为还有时间,就用重构二叉树,然后再求镜像的方法,依然是只能过80%。其实这道题最后还是没明白为什么没a掉,如果是时间复杂度的问题,重构可能比较慢,但每层倒序已经是O(n)的复杂度了,还是过不了,就有点不太明白了

所以最终笔试了90分(50+40)

笔试完的第二天,hr打电话来告诉我笔试通过,约了当天下午的一面。这是我找实习经历中的第一次面试。
题目如下:

1
2
3
4
5
6
1. jvm内存模型,垃圾回收方法,对象可达性分析
2. cms是怎么工作的,并发标记和重新标记之间发生了什么
3. mysql索引,聚簇和非聚簇索引,索引怎么优化
4. hashmap底层原理,hashtable,concurrenthashmap的区别和原理,怎么实现线程安全
5. aqs原理,如何把一个节点加到队列中
6. 算法题:类似围棋:01的棋盘,把所有被0包含的1变成0

其实真是要特别感谢这位面试官,给我找到了好多知识的欠缺之处。最明显的例子就是问了我cms的并发标记和重新标记之间的过程,其实这个部分在书上和其他面经或资料上很少提到,但却是CMS中很重要的部分,就是并发预清理,带着对这个部分的疑问,又去深入了解的三色标记法。可以说是对整个cms有了更深刻的认识。而hashmap更是老生常谈的问题,但要把它的整个工作过程完整细致地描述出来也不是一件轻松的事,这就是熟能生巧了。而对于算法题其实也是leetcode上的一道原题(bfs+标记),只不过最后因为时间不够就没做完了,但还是详细地描述了解法。整体答得还行。唯一的缺憾就是题没有做完。

面完第二天hr打电话来说一面通过,约了一下二面的时间(效率是真的高)
二面题目如下:

1
2
3
4
5
实现62进制加法:0-9a-z A-Z
getpost的区别,postput的区别
http状态码
mysql是binlog是什么
微博的长连接变短怎么做的

上来先是做题,不得不说字节对于算法能力看得真的很重。先笔试,然后每一轮面都有做题。算法题不难,就是多进制的加法,处理好映射和进位的问题就能迎刃而解,再者这种题做的太多了,几乎是不假思索地动手写。很快就写完了。剩余问的基础题也不难。只要好好准备一般都是在覆盖范围之内的。最后问了一道开放题。微博中如何长连接变短,变成t.cn/dasgjkd 这种形式。我回答的是在微博服务端利用hash对长连接生成相应的短连接,然后将其映射关系保存在数据库中,在访问的时候请求先到微博服务端,然后取出真正的连接后通过redirect或者forward来转到真实的地址。而对于网络的相关问题,重点在于幂等性上,知识并不难,但需要有所了解才能回答得上,所以还是应该广泛地涉猎知识

二面后隔了几天,hr打电话说二面通过了,简单聊了一下(基本是为什么想来成都,以及对于互联网快节奏的看法等)。之后就发offer了,效率是真的很高。

总结:字节作为互联网新贵,其前景相当不错。整个面笔试的流程突出的特点:
第一就是十分注重代码能力,除了1轮笔试,2轮面试都要做题。所以平常刷题还是很有必要的。
第二就是注重基础知识,对于网络,基础容器原理(hashmap,chm),并发原理(aqs工作原理、cas)的侧重点会更加多。
第三就是对于校招来说不太看项目,这是我面试过程中少有的两轮完全没有问任何项目,全程都在做题和问基础知识。因此突出一个基础知识以及刷题的重要性。
字节整个面试都使用飞书,体验感觉也很不错,感觉钉钉和飞书视频会议这部分做的很不错的

必须要感谢字节,首先发了offer让我心里有了底,而且指示了面试的大方向和重点。

之后经同学介绍,美团的业务部门也面了一下,整体的感觉也还行,面试题目如下:

1
2
3
4
5
序列化和反序列化一棵二叉树
jmm
mysql索引
泛型相关
项目相关

整体回答的还行,但最后问到入职的时间,人家需要能马上入职的,感觉不合适,就没有再继续了。

蚂蚁金服(阿里巴巴)

接下来是旷日持久的阿里的面试,从3月5日一面,到4月30日意向书发放,耗时2个月,经历了 笔试 + 基础面+主管面+交叉面+总监面+hr面+补笔试。基本算是7次试了。流程虽然很长,但非常有幸认识了非常好的主管,他的提问过程是非常比较体现语言魅力的,会挖掘你的潜力,去引导回答问题,而且不仅仅是问技术基础,还会提问一些开放性的问题来综合体现各方面的素质。还有一点是我觉得他的语气非常亲切,让人在交流过程中也不会紧张。而且他还给了我一些非常宝贵的建议,不仅仅是技术方面的提高建议,还有思维方面,在平时的工作生活中要养成多观察的建议。而且后续也一直是他帮忙跟进我的流程状态直到拿到offer,真的是非常非常感谢他。

今年的笔试是所有技术岗都必须参加的,但不得不说,题目真的太难了,只有第二题a了10%
题目如下:

1
2
3
1.输入10int表示10种牌个数(110的牌),可以打单牌(1),对子(11),顺子(12345), 连对(112233),求最少几次可以打完所有牌

2.给出n个不下降字符串(即string[i]>=string[i-1),求用给出的原始字符串,拼出不下降字符串的字符串最大个数?

第一题可能是需要贪心的就是类似于剪绳子,把绳子剪成2和3的小段,核心思想是去减4的段和5的段,所以我觉得应该是去尽可能凑其中的某两种或者某一种组合,如果直接使用递归形式的深度优先遍历,在复杂度低时可以解决,但是当数很大时就会耗很久,我也下来后写了代码进行验证,在数组全满的情况下需要将近10秒,所以其实还是不能全a的。由于做的时候没形成合适的代码流,(其实可以回溯拼一拼的)

第二题因为本来是以为和数组的最长递增子序列的做法类似,用动态规划的方法,只不过每次叠加的长度是其中字符串的长度,但这样做过不了,所以现在还没想到更好的办法,网上搜索也没有搜到更好的做法。

阿里的笔试风格和平常刷的leetcode的风格相差比较大,不太好做针对性的练习,所以还得平常多积累,多找找类似风格的题做

整个面试问题如下:

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
一面:
网约车怎么设计,
讲讲HashMap
Java继承怎么体现在数据库表上:
mybatis怎么解决N+1的问题
hibernate和mybatis的区别
讲一讲spring IOC AOP
StringStringBuffer、StringBuilder区别
ruby on rails和springboot相比有什么优缺点


二面:
并发,锁机制,悲观乐观
threadlocal
简单介绍Springboot
Spring事务传播行为
mysql事务隔离级别
Springbean的生命周期,包括静态代码块,构造函数,初始化方法的执行顺序
幂等性,post,get
Linux常用命令,怎么看java进程,怎么查看java的cpu使用率
印象深刻的事
技术中印象深刻的事

三面:
介绍一下项目
项目中有什么难点,具体的解决方法,解决后有什么改进(刨到灵魂深处)
平常是怎么学习技术的,除了技术书还看什么书
有什么其他爱好,比如体育爱好之类的
为什么笔试题没做出来,现在如果让你重新做,你有什么好的做法吗?要求细化到具体方法,不能只说贪心、动态规划之类的泛化方法(刨到灵魂深处)

技术终面:
介绍一下毕设
毕设中有什么亮点,通过项目学到了什么
介绍一下项目,项目中有什么难点,怎么解决的


hr面:
自己的亮点
为什么想去成都
金融在生活中的使用场景

因为之前笔试成绩比较低,所以又补了一轮笔试,这轮的题目比较符合平常做的题目类型:

1
2
3
4
5
6
题目一:判断数字是否是回文对称数字
例如: 1232113577531 左右对称的数字为回文数字


题目二:LRU缓存实现
最近最少使用(LRU)缓存方案,是在缓存已满并且需要将新近被使用的项目添加到缓存时首先丢弃最近最少使用的项目。(不能使用LinkedHashMap)

第一道题直接用StringBuilder的reverse。第二个可以用HashMap+LinkedList,map来映射k-v关系,list来维护要淘汰的顺序。

由于四月是阿里整个财年的年底,所以hr很忙,一直到4月30号晚上才发了意向书,整个阿里实习求职历经两个月终于能画下个比较完美的句号了

整个阿里面试流程的重点在以下几个方面:

  1. java基础知识,包括并发,容器(hashmap是重中之重)。
  2. 数据库基础知识,这一块比较广,涉猎的多总不是坏事
  3. 框架,主要是spring的原理,这一部分还是应该去看一下源码,用小demo打断点跑一下流程的,因为spring继承体系纷繁复杂,各种抽象类之间跳来跳去,干看代码很难走通逻辑,所以打断点看流程是最好的学习方式。
  4. 在项目中的难点和总结,必须要对项目有比较深刻的理解和想法,所以平时在做项目的时候要多总结多输出知识

在5月之前拿到了自己理想的offer,也算是对自己的辛苦刷题和学习有所交代,期间还有一些其他公司的小插曲不足道,拿到了两个自己最想要的offer,后面就要好好努力了!

未排序数组中累加和小于或等于给定值的最长子数组长度

给定一个无序数组arr,其中元素可正可负可0,给定一个整数k,求arr所有子数组中累加和小于或等于k的最长子数组长度。
例如:arr=[3,-2,-4,0,6], k=-2,相加和小于或等于-2的最长子数组为3,-2,-4,0 所以结果返回4
依次求以数组的每个位置结尾的,累加和小于或等于k的最长子数组长度,其中最长的那个子数组长度就是我们要的结果。

假如处理到位置30,从位置0到位置30的累加和是100(sum[0..30]=100),现在想求以位置30结尾的、累加和小于或等于10的最长子数组长度。再假设从位置0开始累加到位置10的时候,累加和第一次大于或等于90(sum[0..10]>=90),那么可以知道以位置30结尾的相加和小于或等于10的最长子数组就是arr[11..30]。也就是说,如果从0位置到j位置的累加和为sum[0..j],此时想求以j位置结尾的相加和小于或等于k的最长子数组长度。那么只要知道大于或等于sum[0..j]-k这个值的累加和最早出现在j之前的什么位置就可以,假设那个位置是i位置,那么arr[i+1..j]就是j位置结尾的相加和小于或等于k的最长数组。

由于不是具体值,而是范围值,所以不能直接用hashmap,为了方便地找到大于或等于某一个值的累加和最早出现的位置,可以按照如下方法生成辅助数组helpArr。
1.首先生成arr每个位置从左到右的累加和数组sumArr。以[1,2,-1,5,-2]为例,生成的sumArr=[0,1,3,2,7,5]。注意,sumArr中第一个数为0,表示当没有任何一个数时的累加和为0.
2.生成sumArr的左侧最大值数组helpArr,sumArr={0,1,3,2,7,5} -> {0,1,3,3,7,7}。因为我们只关心大于或等于某个值的累加和最早出现的位置,而累加和3出现在2之前,并且大于或等于3必然大于2,所以当前要保留一个更大的、出现更早的累加和。
3.helpArr是sumArr每个位置上的左侧最大值数组,那么它当然是有序的,在这样一个有序的数组中,就可以二分查找大于或等于某一个值的累加和最早出现的位置。例如,在[0,1,3,3,7,7]中查找大于或等于4这个值的位置,就是第一个7的位置

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
public int maxLength(){
int[] h = new int[arr.length+1];
int sum = 0;
h[0] = sum;
for(int i=0;i!=arr.length;i++){
sum += arr[i];
h[i+1] = Math.max(sum, h[i]);
}
sum = 0;
int res = 0;
int pre = 0;
int len = 0;
for(int i=0;i!=arr.length;i++){
sum+=arr[i];
pre = getLessIndex(h, sum-k);
len = pre == -1?0:i-pre+1;
res = Math.max(res, len);
}
return res;
}


public int getLessIndex(int[] arr, int num){
int low = 0;
int high = arr.length-1;
int mid = 0;
int res = -1;
while(low<=high){
mid = (low+high)>>1;
if(arr[mid]>=num){
res = mid;
high = mid -1;
}else{
low = mid+1;
}
}
return res;
}

未排序数组中累加和为给定值的最长子数组系列问题

给定一个无序数组arr,其中元素可正可负可0,给定一个整数k,求arr所有子数组累加和为k的最长子数组长度

s(i)代表子数组arr[0..i]所有元素的累加和,那么子数组arr[j..i](0<=j<=i< arr.length)的累加和为s(i)-s(j-1),解法:
1.设置变量sum=0,表示从0位置开始一直加到i位置所有元素的和,设置变量len=0,表示累加和为k的最长子数组长度,设置哈希表map,key表示从arr最左边开始累加过程中出现过的sum值,对应的value值则表示sum值最早出现的位置。
2.从左到右开始遍历,遍历的当前元素为arr[i].
1) 令sum=sum+arr[i],即之前所有元素的累加和s(i),在map中查看是否存在sum-k.
》如果sum-k存在,从map中取出sum-k对应的value值,记为j,j代表从左到右不断累积啊的过程中第一次出现sum-k这个累加和的位置。根据之前的结论,arr[j+1..i]的累加和为s(i)-s(j),此时s(i)=sum,又有s(j)=sum-k,所以arr[j+1..i]的累加和为k,同时因为map中只记录每一个累加和最早出现的位置,所以此时的arr[j+1..i]是必须以arr[i]结尾的所有子数组中,最长的累加和为k的子数组,如果该子数组的长度大于len,就更新len。
》如果sum-k不存在,说明在必须以arr[i]结尾的情况下没有累加和为k的子数组。
2)检查当前的sum(即s(i))是否在map中,如果不存在,说明此时的sum值是第一次出现
3.继续遍历下一个元素,直到所有元素都遍历完。

大体上过程如下,但还有很重要的问题,根据arr[j+1..i]的累加和为s(i)-s(j).如果从0位置开始累加,会导致j+1>=1,即所有从0位置开始的子数组都没考虑过,所有应该从-1这个位置开始累加,也就是遍历前先把(0,-1)这个记录放进map,这个记录的意义是如果任何一个数也不加时,累加和为0,这样,从0位置开始的子数组就被我们考虑到了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public int maxLen(int[] arr, int k){
if(arr==null || arr.length==0){
return 0;
}
HashMap<Integer, Integer> map = new HashMap<>();
map.put(0,-1)
int sum = 0;
int len = 0;
for(int i=0;i<arr.length;i++){
sum += arr[i];
if(map.containsKey(sum-k)){
len = Math.max(i-map.get(sum-k), len);
}
if(!map.containsKey(sum)){
map.put(sum, i);
}
}
return len;
}

给定一个无序数组arr,其中元素可正可负可0,求arr中所有的子数组中正数与负数个数相等的最长子数组长度

》方法一:
如果遍历到arr[i],0到i中的正数为x个,负数为y个,若0到j中的正数为x1个,负数为y1个。假设arr[j+1..i]之间的正数与负数相等。则有x-x1=y-y1,故有x-y=x1-y1。因此如果遍历到arr[i]处,找到最前面也满足正数-负数=x-y的位置即可。
因此,用一个HashMap,key为x-y,value为第一次出现该key的位置,遍历到arr[i],如果map中有x-y,则len=max(i-map.get(x-y), len)如果没有key,则说明是第一次出现key,则把key和i放入map中

》方法二:
用第一道题的方法,先把数组arr中的正数全变成1,负数全变成-1,0不变,然后求累加和为0的最长子数组长度即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int maxLen2(int[] arr){
int pos = 0; //正数个数
int nag = 0; //负数个数
int len = 0;
HashMap<Integer, Integer> map = new HashMap<>();
map.put(0, -1); //没有数字时,看作正负个数相等,位置为-1
for(int i=0;i<arr.length;i++){
if(arr[i]>0) pos++;
else if(arr[i]<0) nag++;
if(map.containsKey(pos-nag)){
len = Math.max(i-map.get(pos-nag), len);
}else{
map.put(pos-nag, i);
}
}
return len;
}

给定一个无序数组arr,其中元素只是1或0,求arr所有子数组中0和1个数相等的最长子数组长度。

》方法一:
核心思想和上题一样,只不过把记录正负换成记录1和0
》方法二:
用第一道题的方法,先把数组arr中的0全变成-1,1不变,然后求累加和为0的最长子数组长度即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int maxLen3(int[] arr){
int one = 0; //正数个数
int zero = 0; //负数个数
int len = 0;
HashMap<Integer, Integer> map = new HashMap<>();
map.put(0, -1); //没有数字时,看作1和0个数相等,位置为-1
for(int i=0;i<arr.length;i++){
if(arr[i]==1) one++;
else zero++;
if(map.containsKey(one-zero)){
len = Math.max(i-map.get(one-zero), len);
}else{
map.put(one-zero, i);
}
}
return len;
}

不重复打印排序数组中相加和为给定值的所有二元组和三元组

给定排序数组arr和整数k,不重复打印arr中所有相加和为k的不降序二元组。
例如,arr=[-8,-4,-3,0,1,2,4,5,8,9],k=10,打印结果为:
1,9
2,8

1.利用双指针法,设置遍历left=0和right=length-1
2.比较arr[left]+arr[right]的值(sum)与k的大小
如果sum等于k,打印arr[left],arr[right], left++,right–.
如果sum大于k,right–
如果sum小于k,left++
3.如果left< right,则一直重复步骤2,否则过程结束
为了保证不重复打印,只需在打印前增加一个检查:arr[left]是否与它前一个值相等,如果相等就不打印
解释:因为整体过程是两头向中间压缩的过程,如果arr[left]+arr[right]=k,又有arr[left]==arr[left-1],那么之前一定打印过这个二元组,则不用再重复打印
时间复杂度O(N)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void printUniquePair(int[] arr, int k){
if(arr==null || arr.length<2){
return;
}
int left = 0;
int right = arr.length-1;
while(left<right){
if(arr[left]+arr[right]<k){
left++;
}else if(arr[left]+arr[right]>k){
right--;
}else{
if(left==0 || arr[left-1]!=arr[left]){
System.out.println(arr[left]+","+arr[right]);
}
left++;
right--;
}
}
}

三元组的问题类似于二元组的求解过程

例如 arr=[-8,-4,-3,0,1,2,4,5,8,9] k=10
当三元组的第一个值为-8时,寻找-8后面的子数组中所有相加为18的不重复二元组
当三元组的第一个值为-4时,寻找-4后面的子数组中所有相加为14的不重复二元组
当三元组的第一个值为-3时,寻找-3后面的子数组中所有相加为13的不重复二元组
依次类推:
如果不重复打印?首先保证每次寻找过程开始前,选定的三元组中第一个值不重复,其次就是原问题的打印检查一样,保证不重复打印二元组
时间复杂度为O(n^2)

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
public void printUniqueTriad(int[] arr, int k){
if(arr==null || arr.length<2){
return;
}
for(int i=0;i<arr.length-2;i++){
if(i==0 || arr[i]!=arr[i-1]){
printRest(arr, i, i+1, arr.length-1, k-arr[i]);
}
}
}

public void printRest(int[] arr, int f, int l, int r, int k){
while(l<r){
if(arr[l]+arr[r]<k){
l++;
}else if(arr[l]+arr[r]>k){
r--;
}else{
if(l==f+1 || arr[l-1]!=arr[l]){
System.out.println(arr[f]+","+arr[l]+","+arr[r]);
}
l++;
r--;
}
}
}

众数1

给定一个整型数组arr,打印其中出现次数大于一半的数,如果没有这样的数,打印提示信息
用计数器cnt来表示当前出现次数最多的数的次数,tmp表示该数,如果有满足条件的数,那么最终tmp一定是该数,因为其他的数都能被抵消

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
public void getMainNumber(int[] arr){
int tmp = arr[0];
int cnt = 1;
for(int i=1;i<arr.length;i++){
if(tmp==arr[i]){
cnt++;
}else{
if(cnt==1){
tmp = arr[i];
}else{
cnt--;
}
}
}

//验证
cnt = 0;
for(int i=0;i<arr.length;i++){
if(tmp==arr[i])
cnt++;
}
if(cnt>arr.length){
System.out.println(tmp);
}
else
System.out.println("no main number");

}

众数2

给定一个整型数组arr, 再给定一个整数K,打印所有出现次数大于N/K的数,如果没有这样的数,打印这样的数
出现次数大于N/K的数至多只有 K-1个(如果有K个,那么总数一定会多于N个)
摩尔投票法:
一次在数组中删掉K个不同的数,不停地删除,直到剩下的数的种类不足K,那么,如果某些数在数组中出现次数大于N/K,则这些数最后一定会被生下来,具体过程如下:
遍历到arr[i],看arr[i]是否与已经被选出的某一个候选相同。
如果与某一个候选相同,就把属于那个候选的点数统计+1
如果与所有候选都不相同,先看当前的候选是否满了,K-1就是满,否则就是不满
如果不满,把arr[i]作为一个新的候选,属于它的点数初始化为1
如果已满,说明此时已经发现了K个不同的数,arr[i]就是第K个,此时把每一个候选各自的点数都-1,表示每个候选需要付出一个自己的点数,如果某些候选的点数在-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
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

public void findMainNumber2(int[] arr, int k) {
int[] res = new int[k];
//都初始化为第一个元素
for (int i = 0; i < k; i++) {
res[i] = arr[0];
}
int[] cnt = new int[k];

for (int i = 0; i < arr.length; i++) {
boolean flag1 = true;
boolean flag2 = true;
for (int j = 0; j < k; j++) {
if (res[j] == arr[i]) {
cnt[j]++;
flag1 = false;
break;
}
}
if (flag1) {
for (int j = 0; j < k; j++) {
if (cnt[j] == 0) {
res[j] = arr[i];
cnt[j] = 1;
flag2 = false;
break;
}
}
}
if (flag1 && flag2) {
for (int j = 0; j < k; j++) {
cnt[j]--;
}
}
}

//验证res中每个数是不是确实满足条件
int total = 0;
int t = arr.length / k;
for (int i = 0; i < k; i++) {
cnt[i] = 0;
}

for (int i = 0; i < k; i++) {
for (int j = 0; j < arr.length; j++) {
if (arr[j] == res[i]) {
cnt[i]++;
}
}
}
for (int i = 0; i < k; i++) {
if (cnt[i] > t) {
total++;
if (i == 0)
System.out.print(res[i]);
else
System.out.print("," + res[i]);
}
}
if (total == 0) {
System.out.println("no main number");
}
}

找到无序数组中最小的k个数:
给定一个无序的整型数组arr,找到其中最小的k个数
如果数组arr长度为N,排序后自然可以得到最小的k个数,此时时间复杂度与排序的时间复杂度相同,均为O(NlogN)。本题要求读者实现时间复杂度为O(Nlogk)和O(N)的方法

O(Nlogk):

一直维护有k个数的大根堆,这个堆代表目前选出的k个最小的数,在堆里的k个元素中堆顶的元素是最小的k个数里最大的那个。接下来遍历整个数组,遍历的过程中看档期数是否比堆顶元素小,如果是,就把堆顶元素替换成档期的数,然后从堆顶的位置调整整个堆,让替换操作后堆的最大元素继续处在堆顶的位置;如果不是,则不进行任何操作,继续遍历下一个数;在遍历完成后,堆中的k个数就是所有数组中最小的k个数

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
public int[] getMinKNumsByHeap(int[] arr, int k){
if(k<1 || k>arr.length){
return arr;
}
int[] kHeap = new int[k];
for(int i=0;i!=arr.length;i++){
if(arr[i]<kHeap[0]){
kHeap[0] = arr[i];
heapify(kHeap, 0, k);
}
}
return kHeap;
}

public void heapInsert(int[] arr, int value, int index){
arr[index] = value;
while(index!=0){
int parent = (index-1)/2;
if(arr[parent] < arr[index]){
swap(arr, parent, index);
index = parent;
}else{
break;
}
}
}

public void heapify(int[] arr, int index, int heapSize){
int left = index * 2+1;
int right = index * 2+2;
int largest = index;
while(left < heapSize){
if(arr[left] > arr[index]){
largest = left;
}
if(right < heapSize && arr[right] > arr[largest]){
largest = right;
}
if(largest!=index){
swap(arr, largest, index);
}else{
break;
}
index = largest;
left = index * 2+1;
right = index * 2+2;
}
}

public void swap(int[] arr, int index1, int index2){
int tmp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = tmp;
}

O(N):BFPRT算法:

在时间复杂度O(n)内,从无序的数组中找到第k小的数,显而易见的是,如果我们找到了第k小的数,那么想求arr中最小的k个数,就算再遍历一遍数组的而工作量而已。
假设BFPRT算法的函数是int select(int[] arr, k),该函数的功能为在arr中找到第k小的数,然后返回该数,select(arr, k)过程如下:

  1. 将arr中的n个元素划分成n/5组,每组5个元素,如果最后的组不够5个元素,那么最后剩下的元素为一组(n%5个元素)。
  2. 对每个组进行插入排序,只针对每个组最多五个元素之间的组内排序,组与组之间并不排序,排序后找到每个组的中位数,如果组的元素个数为偶数,则规定找到下中位数
  3. 步骤2中一共会找到n/5个中位数,让这些中位数组成一个新的数组,记为mArr,递归调用select(mArr, mArr.length/2),意义是找到mArr这个数组中的中位数,即mArr中第mArr.length/2小的数
  4. 假设步骤3中递归调用select(mArr, mArr.length/2)后,返回的数为x,根据这个x划分整个arr数组(partition过程),划分的过程为:在arr中,比x小的数都在x的左边,大于x的数都在x的右边,x在中间。假设划分完成后,x在arr中的位置记为i:
  5. 如果i==k,说明x为整个数组中第k小的数,直接返回
    如果i< k,说明x处在第k小的数的左边,则应该在x的右边寻找第k小的数,所以递归调用select函数, 在左半区寻找第k小的数。
    如果i>k,说明x处在第k小的数的右边,应该在x的左边寻找第k小的数,所以递归调用select函数,在右半区寻找第(i-k小的数)
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119

public int[] getMinKNumsByBFPRT(int[] arr, int k){
if(k<1 || k > arr.length){
return arr;
}

//找到第k小的数
int minKth = getMinKthByBFPRT(arr, k);
int[] res = new int[k];
int index = 0;
//把所有小于minKth的数放入res中
for(int i=0;i!=arr.length;i++){
if(arr[i]<minKth){
res[index++] = arr[i];
}
}
//如果不够,则说明要填充这个数
for(; index!=res.length;index++){
res[index] = minKth;
}
return res;
}

public int getMinKthByBFPRT(int[] arr, int K){
//为了不修改原数组,在拷贝数组上操作
int[] copyArr = copyArray(arr);
return select(copyArr, 0, copyArr.length-1, K-1);
}

//拷贝数组
public int[] copyArray(int[] arr){
int[] res = new int[arr.length];
for(int i=0;i!=res.length;i++){
res[i] = arr[i];
}
return res;
}

//核心筛选函数
public int select(int[] arr, int begin, int end, int i){
if(begin == end){
return arr[begin];
}
//找到中间数字
int pivot = medianOfMedians(arr, begin, end);
//以这个数字为枢轴,对大于他和小于他的数进行左右分开,也就是说,枢轴会被放置到它应该在的位置上
int[] pivotRange = partition(arr, begin, end ,pivot);
//如果这个枢轴就是要找的数,直接返回
if(i>=pivotRange[0] && i<=pivotRange[1]){
return arr[i];
}else if(i<pivotRange[0]){ //如果i小于这个枢轴索引,说明i对应的数应该在前面,则继续筛选
return select(arr, begin, pivotRange[0]-1, i);
}else{ //如果i大于这个枢轴索引,说明i对应的数应该在后面,则继续筛选
return select(arr, pivotRange[1]+1, end, i);
}
}


public int medianOfMedians(int[] arr, int begin, int end){
//把整个数组5个5个分为一组
int num = end - begin + 1;
int offset = num % 5 ==0?0:1;
//mArr是所有小区间中中间数组成的新数组
int[] mArr = new int[num/5+offset];
for(int i=0;i<mArr.length;i++){
int beginI = begin+i*5;
int endI = beginI + 4;
//mArr[i]是这个小区间内排序后的中间数
mArr[i] = getMedian(arr, beginI, Math.min(end, endI));
}
//继续进行筛选,最终找出的是整个数组中最中间的数(但是却不用把整个数组进行排序)
return select(mArr, 0, mArr.length-1, mArr.length / 2);
}

//对数组进行划分,即找到枢轴值的位置,并且让该值左边都是小于他,右边都是大于他
public int[] partition(int[] arr, int begin, int end, int pivotValue){
int small = begin-1;
int cur = begin;
int big = end +1;
while(cur!=big){
if(arr[cur] < pivotValue){
swap(arr, ++small, cur++);
}else if(arr[cur] > pivotValue){
swap(arr, cur, --big);
}else{
cur++;
}
}
int[] range = new int[2];
range[0] = small + 1;
range[1] = big - 1;
return range;
}

//对arr进行插入排序,然后找到最中间的数,返回
public int getMedian(int[] arr, int begin, int end){
insertionSort(arr, begin, end);
int sum = end + begin;
int mid = (sum/2) + (sum % 2);
return arr[mid];
}

public void insertionSort(int[] arr, int begin, int end){
for(int i=begin+1; i!=end+1; i++){
for(int j=i;j!=begin;j--){
if(arr[j-1]>arr[j]){
swap(arr, j-1, j);
}else{
break;
}
}
}
}

public void swap(int[] arr, int index1, int index2){
int tmp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = tmp;
}

在学习SSM框架中的SpringMVC时常常看到在控制层Controller中有的使用ModelAndView进行数据模型的传输,有的使用Model进行数据模型传输,为何有两种不同度类型进行传输呢?

一.ModelAndView

若返回类型为ModelAndView类型,需要方法结束时,定义ModelAndView,将model和view分别进行设置,代码方法如下:
-数据传递:ModelAndView通过addObject方法向页面传递数据;
-数据获取:JSP页面可以通过el表达式或C标签库的方法获取数据(与Model的获取方式相同);
-return:return返回的是ModelAndView对象;

ModelAndView设置跳转地址有两个方式:
第一种:在new ModelAndView时添加地址参数,如:
ModelAndView mav = new ModelAndView(“test”);
第二种:使用ModelAndView的setViewname(String)方法去设置,如:
      mav.setViewName(“test”);

1
2
3
4
5
6
7
8
@RequestMapping("/products")
public ModelAndView products() {
List<Product> ps = productService.listProducts();
ModelAndView mav = new ModelAndView();
mav.addObject("ps", ps);
mav.setViewName("products"); //视图名称(视图的全名称为products.html 或 products.jsp)
return mav;
}

主要步骤:

1.首先是创建ModelAndView对象,再调用addObject方法,参数一为该数据命名,参数二为传入实际数据列表

2.再调用setViewName方法设置jsp页面的路径,这里的地址前缀和后缀一般是定义好的(就像thymeleaf,前缀是classpath:/templates,后缀是.html, 要是设置的话,如果是springboot可以在配置文件中设置,mvc可以在web.xml中设置),可直接简写如上。如未有定义可以使用上方以注释的路径进行传入

3.最后返回modelAndView数据

二.Model

若方法返回String类型,则要使用Model,表示返回逻辑视图名,真正视图(jsp或html路径)=前缀+逻辑视图名+后缀,代码如下:

-数据传递:Model是通过addAttribute方法向页面传递数据的;
-数据获取:JSP页面可以通过el表达式或C标签库的方法获取数据,当然html页面也可以获取,具体就用thymeleaf的标签来做
-return:return返回的是指定的页面名称;

1
2
3
4
5
6
@RequestMapping("/products")
public Object products(Model m) {
List<Product> ps = productService.listProducts();
m.addAttribute("ps", ps);
return "products"; ////视图名称(视图的全名称为products.html 或 products.jsp)
}

主要步骤:

1.在方法括号中定义Model类型

2.调用addAttribute方法,参数一为给数据命名,参数二为传入上面获取到的数据

3.最后返回jsp页面的路径

三.ModelMap的使用

  ModelMap的使用与Model相同,ModelMap是一种特殊的Model,一般来说,Model可以接收各种类型的数据,如果接收的数据是List,那么这个时候Model实际上是ModelMap。

Model与ModelAndView的区别

  第一点:Model只是用来传输数据的,并不会进行业务的寻址。ModelAndView 却是可以进行业务寻址的;所以Model的返回值是视图名称,而ModelAndView的返回值是ModelAndView对象;

  第二点:Model是每一次请求可以自动创建,但是ModelAndView 是需要我们自己去new的。所以使用Model时Controller方法的参数是Model。

前情
之前的文章已经说明了hexo这一框架的搭建过程,本文对该框架如何配置,以及Next主题的配置来一个说明,按着文章的说明慢慢走一遍,属于自己的博客就可以呈现出来啦。也欢迎到我的博客中观看哦~
配置说明
我个人选择的是Next这一主题,这一主题是由中国人开发,具有中文文档,并且我很喜欢它的设计风格。下面的配置也是围绕这个主题进行的,如果喜欢别的主题,可以到hexo的主题页选择,并根据文档说明尽心配置。

Next主题安装

1
2
cd hexo   # 进入博客根目录
git clone https://github.com/theme-next/hexo-theme-next themes/next

hexo配置
编辑博客根目录下的_config.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
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
101
102
103
# Hexo Configuration
## Docs: https://hexo.io/docs/configuration.html
## Source: https://github.com/hexojs/hexo/

# Site
title: # 博客名称
subtitle: # 博客子标题
description: # 作者描述
keywords: # 站点关键词,用于搜索优化
author: # 博主名
language: zh-CN # 站点语言
timezone: Asia/Shanghai # 时区
# 注意
# 语言这里需要进入 /***/themes/next/languages/ 目录
# 不同版本的语言名称可能有些许差别
# *** 为博客根目录

# URL
## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'
url: http://yoursite.com # 你的域名,如果有
root: /
permalink: :year/:month/:day/:title/
permalink_defaults:

# Directory
source_dir: source
public_dir: public
tag_dir: tags
archive_dir: archives
category_dir: categories
code_dir: downloads/code
i18n_dir: :lang
skip_render:

# Writing # 博文相关
new_post_name: :title.md # 博文的格式,默认markdown编辑
default_layout: post # 默认发布的为post,即默认发布的是文章
# 共有三种类型,具体见hexo文档
titlecase: false # Transform title into titlecase
external_link: true # Open external links in new tab
filename_case: 0
render_drafts: false
post_asset_folder: false
relative_link: false
future: true
highlight:
enable: true
line_number: true
auto_detect: false
tab_replace:

# Home page setting
# path: Root path for your blogs index page. (default = '')
# per_page: Posts displayed per page. (0 = disable pagination)
# order_by: Posts order. (Order by date descending by default)
index_generator:
path: ''
per_page: 10 # 首页每页展示的文章数
order_by: -date # 按日期逆序

# Category & Tag
default_category: uncategorized
category_map:
tag_map:

# Date / Time format
## Hexo uses Moment.js to parse and display date
## You can customize the date format as defined in
## http://momentjs.com/docs/#/displaying/format/
date_format: YYYY-MM-DD
time_format: HH:mm:ss

# Pagination
## Set per_page to 0 to disable pagination
per_page: 10
pagination_dir: page

# Extensions
## Plugins: https://hexo.io/plugins/
## Themes: https://hexo.io/themes/
theme: next # 使用的主题,这里选用的是Next主题

# Deployment # 下面是第三方扩展,根据个人需求设置,也可不修改以下内容
## Docs: https://hexo.io/docs/deployment.html
deploy:
type:

search: # 实现搜索文章的功能
path: search.xml
field: post
format: html
limit: 100

feed: # 实现博客订阅功能
type: atom
path: atom.xml
limit: 20

sitemap: # 生成站点地图用于SEO优化
path: sitemap.xml

baidusitmap: # 同上
path: baidusitemap.xml

Next配置
编辑 //themes/next/_config.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
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# ---------------------------------------------------------------
# Site Information Settings
# ---------------------------------------------------------------

# To get or check favicons visit: https://realfavicongenerator.net
# Put your favicons into `hexo-site/source/` (recommend) or `hexo-site/themes/next/source/images/` directory.

# Default NexT favicons placed in `hexo-site/themes/next/source/images/` directory.
# And if you want to place your icons in `hexo-site/source/` root directory, you must remove `/images` prefix from pathes.

# For example, you put your favicons into `hexo-site/source/images` directory.
# Then need to rename & redefine they on any other names, otherwise icons from Next will rewrite your custom icons in Hexo.

# 以下设置网站的logo,建议用400*400制作好原图
# 然后访问https://realfavicongenerator.net生成各种类型的logo
# 放置于 ***/themes/next/source/images/目录下,***为博客根目录

favicon:
small: /images/favicon-16x16.png
medium: /images/favicon-32x32.png
apple_touch_icon: /images/apple-touch-icon.png
safari_pinned_tab: /images/safari-pinned-tab.svg
#android_manifest: /images/manifest.json
#ms_browserconfig: /images/browserconfig.xml

# Set rss to false to disable feed link.
# Leave rss as empty to use site's feed link, and install hexo-generator-feed: `npm install hexo-generator-feed --save`.
# Set rss to specific value if you have burned your feed already.
rss:

footer:
# Specify the date when the site was setup.
# If not defined, current year will be used.
#since: 2015

# Icon between year and copyright info.
icon:
# Icon name in fontawesome, see: https://fontawesome.com/v4.7.0/icons
# `heart` is recommended with animation in red (#ff0000).
name: user
# If you want to animate the icon, set it to true.
animated: false
# Change the color of icon, using Hex Code.
color: "#808080"

# If not defined, will be used `author` from Hexo main config.
copyright:
# -------------------------------------------------------------
powered:
# Hexo link (Powered by Hexo).
enable: true
# Version info of Hexo after Hexo link (vX.X.X).
version: false

theme:
# Theme & scheme info link (Theme - NexT.scheme).
enable: true
# Version info of NexT after scheme info (vX.X.X).
version: false
# -------------------------------------------------------------
# Any custom text can be defined here.
#custom_text: Hosted by <a target="_blank" rel="external nofollow" href="https://pages.coding.me"><b>Coding Pages</b></a>

# ---------------------------------------------------------------
# SEO Settings
# ---------------------------------------------------------------

# SEO 相关设置

# Canonical, set a canonical link tag in your hexo, you could use it for your SEO of blog.
# See: https://support.google.com/webmasters/answer/139066
# Tips: Before you open this tag, remember set up your URL in hexo _config.yml ( ex. url: http://yourdomain.com )
canonical: true

# Change headers hierarchy on site-subtitle (will be main site description) and on all post/pages titles for better SEO-optimization.
seo: true # 开启seo优化

# If true, will add site-subtitle to index page, added in main hexo config.
# subtitle: Subtitle
index_with_subtitle: true # 网页搜索及标签页显示副标题


# ---------------------------------------------------------------
# Menu Settings
# ---------------------------------------------------------------

# When running the site in a subdirectory (e.g. domain.tld/blog), remove the leading slash from link value (/archives -> archives).
# Usage: `Key: /link/ || icon`
# Key is the name of menu item. If translate for this menu will find in languages - this translate will be loaded; if not - Key name will be used. Key is case-senstive.
# Value before `||` delimeter is the target link.
# Value after `||` delimeter is the name of FontAwesome icon. If icon (with or without delimeter) is not specified, question icon will be loaded.

# 以下为主菜单按键设置,不需要的在行首用#注释掉
# 启用的标签页需要创建相应的目录
# 例如:
# cd *** # 进入微博根目录
# hexo new page about # 创建about页面


menu:
home: / || home
about: /about/ || user
#tags: /tags/ || tags
categories: /categories/ || th
#archives: /archives/ || archive
#schedule: /schedule/ || calendar
#sitemap: /sitemap.xml || sitemap
#commonweal: /404/ || heartbeat

# Enable/Disable menu icons / item badges.
menu_settings:
icons: true
badges: false

# ---------------------------------------------------------------
# Scheme Settings
# ---------------------------------------------------------------

# 主题风格设置
# 选用哪个就去掉哪个的#
# 样例可在next文档中查看

# Schemes
#scheme: Muse
#scheme: Mist
scheme: Pisces
#scheme: Gemini


# ---------------------------------------------------------------
# Sidebar Settings
# ---------------------------------------------------------------

# 主页面菜单栏设置

# Posts / Categories / Tags in sidebar.
site_state: true

# Social Links.
# Usage: `Key: permalink || icon`
# Key is the link label showing to end users.
# Value before `||` delimeter is the target permalink.
# Value after `||` delimeter is the name of FontAwesome icon. If icon (with or without delimeter) is not specified, globe icon will be loaded.
#social:
#GitHub: https://github.com/yourname || github
#E-Mail: mailto:yourname@gmail.com || envelope
#Google: https://plus.google.com/yourname || google
#Twitter: https://twitter.com/yourname || twitter
#FB Page: https://www.facebook.com/yourname || facebook
#VK Group: https://vk.com/yourname || vk
#StackOverflow: https://stackoverflow.com/yourname || stack-overflow
#YouTube: https://youtube.com/yourname || youtube
#Instagram: https://instagram.com/yourname || instagram
#Skype: skype:yourname?call|chat || skype

social_icons:
enable: true
icons_only: false
transition: false
# Dependencies: exturl: true in Tags Settings section below.
# To encrypt links above use https://www.base64encode.org
# Example encoded link: `GitHub: aHR0cHM6Ly9naXRodWIuY29tL3RoZW1lLW5leHQ= || github`
exturl: false

# Follow me on GitHub banner in right-top corner.
# Usage: `permalink || title`
# Value before `||` delimeter is the target permalink.
# Value after `||` delimeter is the title and aria-label name.
#github_banner: https://github.com/yourname || Follow me on GitHub

# Blog rolls
links_icon: link
links_title: Links
links_layout: block
#links_layout: inline
#links:
#Title: http://example.com/

# Sidebar Avatar
# 头像设置
# 将图片放置于 ***/themes/next/source/images/目录下,***为博客根目录
# 图片不要过大,不利于加载

avatar:
# in theme directory(source/images): /images/avatar.gif
# in site directory(source/uploads): /uploads/avatar.gif
# You can also use other linking images.
url: /images/touxiang.jpg
# If true, the avatar would be dispalyed in circle.
#rounded: false
# The value of opacity should be choose from 0 to 1 to set the opacity of the avatar.
opacity: 1
# If true, the avatar would be rotated with the cursor.
rotated: false

# Table Of Contents in the Sidebar
toc:
enable: true

# Automatically add list number to toc.
number: true

# If true, all words will placed on next lines if header width longer then sidebar width.
wrap: false

# Creative Commons 4.0 International License.
# http://creativecommons.org/
# Available: by | by-nc | by-nc-nd | by-nc-sa | by-nd | by-sa | zero
#creative_commons: by-nc-sa
#creative_commons:

# 菜单栏放置于左边还是右边

sidebar:
# Sidebar Position, available value: left | right (only for Pisces | Gemini).
position: left
#position: right

# Manual define the sidebar width.
# If commented, will be default for:
# Muse | Mist: 320
# Pisces | Gemini: 240
#width: 300

# Sidebar Display, available value (only for Muse | Mist):
# - post expand on posts automatically. Default.
# - always expand for all pages automatically
# - hide expand only when click on the sidebar toggle icon.
# - remove Totally remove sidebar including sidebar toggle.
display: post
#display: always
#display: hide
#display: remove

# Sidebar offset from top menubar in pixels (only for Pisces | Gemini).
offset: 12 # 文章索引与顶部的距离

# Back to top in sidebar (only for Pisces | Gemini).
b2t: false # 回到顶部是否置于菜单栏下方

# Scroll percent label in b2t button.
scrollpercent: false

# Enable sidebar on narrow view (only for Muse | Mist).
onmobile: false


# ---------------------------------------------------------------
# Post Settings
# ---------------------------------------------------------------

# Automatically scroll page to section which is under <!-- more --> mark.
scroll_to_more: false # 是否自动进行“查看全文”标记

# Automatically saving scroll position on each post/page in cookies.
save_scroll: false # 是否记录浏览位置

# Automatically excerpt description in homepage as preamble text.
excerpt_description: true # 是否自动摘录作为前言

# Automatically Excerpt. Not recommend.
# Please use <!-- more --> in the post to control excerpt accurately.
auto_excerpt:
enable: false
length: 150

# Post meta display settings
post_meta:
item_text: true
created_at: true
updated_at:
enabled: true
# If true, show updated date label only if `updated date` different from 'created date' (post edited in another day than was created).
# And if post will edited in same day as created, edited time will show in popup title under created time label.
# If false show anyway, but if post edited in same day, show only edited time.
another_day: true
categories: true

# Post wordcount display settings
# Dependencies: https://github.com/theme-next/hexo-symbols-count-time
symbols_count_time:
separated_meta: true
item_text_post: true
item_text_total: false
awl: 4
wpm: 275

codeblock:
# Manual define the border radius in codeblock
# Leave it empty for the default 1
border_radius:
# Add copy button on codeblock
copy_button:
enable: false
# Show text copy result
show_result: false

# 微信订阅相关设置

# Wechat Subscriber
#wechat_subscriber:
#enabled: true
#qcode: /path/to/your/wechatqcode ex. /uploads/wechat-qcode.jpg
#description: ex. subscribe to my blog by scanning my public wechat account

# Reward # 打赏相关设置
#reward_comment: Donate comment here
#wechatpay: /images/wechatpay.jpg
#alipay: /images/alipay.jpg
#bitcoin: /images/bitcoin.png

# Related popular posts
# Dependencies: https://github.com/tea3/hexo-related-popular-posts
related_posts:
enable: false
title: # custom header, leave empty to use the default one
display_in_home: false
params:
maxCount: 5
#PPMixingRate: 0.0
#isDate: false
#isImage: false
#isExcerpt: false

# Declare license on posts
post_copyright:
enable: false
license: <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" rel="external nofollow" target="_blank">CC BY-NC-SA 4.0</a>

# Post edit
# Dependencies: https://github.com/hexojs/hexo-deployer-git
post_edit:
enable: false
url: https://github.com/theme-next/theme-next.org/_posts/tree/master/ # Link for view source.
# url: https://github.com/theme-next/theme-next.org/_posts/edit/master/ # Link for fork & edit.


# ---------------------------------------------------------------
# Misc Theme Settings
# ---------------------------------------------------------------

# Reduce padding / margin indents on devices with narrow width.
mobile_layout_economy: false

# Android Chrome header panel color ($brand-bg / $headband-bg => $black-deep).
android_chrome_color: "#222"

# Custom Logo.
# !!Only available for Default Scheme currently.
# Options:
# enabled: [true/false] - Replace with specific image
# image: url-of-image - Images's url
custom_logo:
enabled: false
image:

# Code Highlight theme
# Available values: normal | night | night eighties | night blue | night bright
# https://github.com/chriskempson/tomorrow-theme
highlight_theme: night eighties # 代码高亮风格

# Enable "cheers" for archive page.
cheers_enabled: true


# ---------------------------------------------------------------
# Font Settings
# - Find fonts on Google Fonts (https://www.google.com/fonts)
# - All fonts set here will have the following styles:
# light, light italic, normal, normal italic, bold, bold italic
# - Be aware that setting too much fonts will cause site running slowly
# - Introduce in 5.0.1
# ---------------------------------------------------------------
# CAUTION! Safari Version 10.1.2 bug: https://github.com/iissnan/hexo-theme-next/issues/1844
# To avoid space between header and sidebar in Pisces / Gemini themes recommended to use Web Safe fonts for `global` (and `logo`):
# Arial | Tahoma | Helvetica | Times New Roman | Courier New | Verdana | Georgia | Palatino | Garamond | Comic Sans MS | Trebuchet MS
# ---------------------------------------------------------------

# 字体相关设置
# 因为上述的谷歌字体中没有中文支持
# 所以下列一般只改字体大小

font:
enable: true

# Uri of fonts host. E.g. //fonts.googleapis.com (Default).
host:

# Font options:
# `external: true` will load this font family from `host` above.
# `family: Times New Roman`. Without any quotes.
# `size: xx`. Use `px` as unit.

# Global font settings used for all elements in <body>.
global:
external: true
family: Lato
size: 18

# Font settings for Headlines (H1, H2, H3, H4, H5, H6).
# Fallback to `global` font settings.
headings:
external: true
family:
size:

# Font settings for posts.
# Fallback to `global` font settings.
posts:
external: true
family:

# Font settings for Logo.
# Fallback to `global` font settings.
logo:
external: true
family:
size:

# Font settings for <code> and code blocks.
codes:
external: true
family:
size: 14

收工部分
在配置过程中,可运行hexo服务器实时观看配置效果,hexo配置的修改需要重启hexo服务器才能生效,主题配置的修改保存后刷新页面即可生效。配置完成后,需要生成静态文件:

cd  xxx/  
hexo generate

生成的静态文件在 /xxx/public 目录下,如果是托管在github上的话,在网上查找相关教程即可,教程极其丰富。