说说.Net与Java中的字符串

Java字符串碰到的问题

在写Java程序碰到一个问题,而正是这个问题引发了我对字符串的思考,Java示例代码如下:

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

public void TestStr(String success)
{

if(success=="Y"){
System.out.println("Equal");
}
else
{
System.out.println("Not Equal");
}

}

```

上面的这个函数很简单,但会随着调用的方式的不同而显示出不同的结果:

``` Java
public void CallMethod()
{
TestStr("Y");//Equal
TestStr("YY".substring(0,1));//Not Equal
}

```

对于这样的一个结果,我们可以先思考一个问题:" == " 这个运算符的作用?

1. 对于基础数据类型而言是比较值是否相同(作用与equal相同)

2. 对于引用类型,则比较地址是否一样


但如果理解上面的代码,我们还要理解Java中字符串的机制。由于字符串是比较常用的类型,为了保证性能,所以在设计字符串的时候会有一个“池”的概念。

- 字符一旦创建成功后,就不再发生变化,字符的运算也都是创建新的字符串对象

- 字符创建前,查找内存中是否已经存在相同的字符串,如果有则直接把地址给当前的对象,没有则直接创建新对象


所以对于上面的代码,因为在开始已经创建的“Y”字符串,所以后面出生现的所有的“Y”都是引用我们当前的“Y”,所以我们就可以理解为什么第一个是打印Equal,另一个是打印Not Equal.


### .Net中如何处理

而对于.Net来说,字符串的原理大致相同,如果是相同的代码,但运算的结果是与Java不一样的:

![dotnet](/img/assets/14/01.png)


我们知道在.Net string也是引用类型,但当“==”作用于两个引用类型的时候,比较则是地址,但在.Net中字符比较时,比较的却是值。这个归功于.Net对“==”的重载,[string源码](http://referencesource.microsoft.com/#mscorlib/system/string.cs,8281103e6f23cb5c){:target="_blank"}。如果想比较地址,则使用 object.ReferenceEquals()这个函数。


``` C#

public static bool operator == (String a, String b)
{
return String.Equals(a, b);
}

```

对于.Net运算符重载的这个动作,个人觉得更贴近日常的使用习惯,因为在编码的过程中,字符串中绝大多数的使用场景都是值,而不是引用。而对Java而言,保证的运算的原汁原味,少了人为的封装的干扰,使用是注意区分,习惯了反而觉得更为合理。

### 几个疑问


#### 字符串是引用类型,为什么不使用new来创建对象?

字符串是一个特殊的引用对象 ,声明就是创建了一个对象,如果使用new,则会重复的创建对象(Java中可以使用new创建,.Net中则直接不允许这样操作),浪费内存,如下:

``` Java    

String str=new String("1234");

String str1="1234";

两种的定义方式相同,但是使用new的时候,又额外分配了内存空间。

字符串是引用类型,但是传参的时候却无法修改它的值?有其它的引用类型有什么不同?

  public void CallMethod(){

    String str="abc";
    AddSuffix(str);

    System.out.println(str);//打印出abc
  }
   
  public void AddSuffix(String x){

      x=x+"123";
  }


当我们去调用这个函数的时候,发现str的值却没有发生改变。 因为在调用AddSuffix 函数时,str把自己作拷贝成一个副本传递给形参x,当对x赋值的时候,系统重新创建了一个字符对象,把引用的地址给x,此处是重新创建对象,而不是修改原来的字符串对象(字符串不可更改)。两种方式示意如下:

字符串

字符串

linux下sublime如何使用中文

原来在使用linux的时候最大的诟病是在sublime text下面不能写中文,各种百度和搜索都没能解决,但现在又重新下linux下面做开发,又要重新面对这个问题,好在问题已经有了很好的解决方案。

使用方法

  • 首先更新你的系统 :

    
    sudo apt-get update && sudo apt-get upgrade     
    
  • 选择一个目录后,用git clone 下面地址:

    
    git clone https://github.com/lyfeyaj/sublime-text-imfix.git
    
  • 使用命令进入sublime- text- imfix 路径 :

    
    cd sublime-text-imfix
    
  • 运行以下脚本

    
    ./sublime-imfix     
    
  • 完成后 重启电脑。

解决Ubuntu下Sublime Text 3无法输入中文

jekyll如何使用中文路径

出现问题

最近在使用jekyll在本地预览自己写的博客无法正常打开,而提交到github上却可以正常解析。看了一下发现是文件写的博客有什么变化,原来是因为博客的markdown文件使用了中文文件名,jekyll无法正常解析出现乱码。

解决方法:

修改安装目录\Ruby22-x64\lib\ruby\2.2.0\webrick\httpservlet下的filehandler.rb文件,建议先备份。找到下列两处,添加一句(+的一行为添加部分)

1
2
3
4
5

path = req.path_info.dup.force_encoding(Encoding.find("filesystem"))
+ path.force_encoding("UTF-8") # 加入编码
if trailing_pathsep?(req.path_info)


	break if base == "/"
	+ base.force_encoding("UTF-8") #加入編碼
	break unless File.directory?(File.expand_path(res.filename + base))    

修改完重新jekyll serve即可支持中文文件名。

多线程如何排队执行

场景

有一个这样场景,程序会有一个非常耗时的操作,但要求耗时的操作完成后,再顺序的执行一个不耗时的操作,而且这个程序的调用,可能存在同时调用的情况。

具体的模型如下:

moxing

从Start开始触发了5个线程,经过一个longTimeJob同时执行,我们不关心longJob的执行时间和先后顺序,根据Start的先后顺序来执行一个ShortJob。下面我们用代码来模拟上面的过程。

举例说明:有ABCD 4个线程,进入的顺序也是ABCD,A耗时3s,B耗时7s,C耗时1s,D耗时3s. 所以如果当4个线程都同时开始执行时,完成的先后顺序为 CADB,但我们要求的顺序是ABCD,也就是说C要等待AB执行完后,才能继续后续的工作。

我们可以用请求bing搜索来模拟longTimeJob,根据传入的序列来决定请求多少次,主要模拟方法如下:

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

private static async Task Test()
{
var arry = new[]
{
10, 9, 8, 7, 6, 5, 4, 3, 2, 1
};
var listTask = new List<Task<int>>();
foreach (var i in arry)
{
var i1 = i;
var task = Task.Run(() => DoJob(i1));

listTask.Add(task);
}

foreach (var task in listTask)
{
Console.WriteLine("输出-->:" + await task);//
}
}

public static Task<int> DoJob(int o)
{
return Task.Run(() =>
{
DoLongTimeThing(o);
return o;
});
}

public static void DoLongTimeThing(int i)
{
Console.WriteLine("执行-->:" + i);
for (int j = 0; j < i; j++)
{
HttpGet("http://cn.bing.com/");
}

Console.WriteLine("执行完毕-->:" + i);
}

public static string HttpGet(string url)
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";

HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
string content = reader.ReadToEnd();
return content;
}
catch (Exception e)
{
return e.Message;
}

}



执行结果:

taskjob

上面的代码大概能解决我们的问题,有一个问题,对于客户的调用我们无法形成一个List,而且list是线程安全的,所以针对上述的方法在实际的业务场景中无法使用。

新思路

我们无法实现一个有序的Task列表,如果换一个角度考虑,当一个任务形成的时间,同时生成一个对应的HashCode,对HashCode进行一个队列的入队操作,当执行完成longTimeJob后,判断是不是队列的第一个Task的HashCode,如果是则执行,如果不是则继续等待,切换线程。 具体如下思路如下图:

queue

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

public static Queue<string> Queue = new Queue<string>();
static void Main(string[] args)
{
var arry = new[]
{
10, 9, 8, 7, 6, 5, 4, 3, 2, 1
};

foreach (var i in arry)
{
Console.WriteLine("进入Job顺序-->:" + i);
Test(i);
}
Console.ReadKey();
}

public static void Test(int i)
{
var taskId = Guid.NewGuid().ToString();
Queue.Enqueue(taskId);
Task.Factory.StartNew(DoJob, new object[] { i, taskId });
}
public static void DoJob(object o)
{
var oArry = (object[])o;
var n = (int)oArry[0];
var currId = oArry[1].ToString();

DoLongTimeThing(n);//

while (currId != Queue.Peek())
{
Thread.Sleep(1);//等线程切换
}

Console.WriteLine("DoShortJob输出-->:" + n);//
//请求数据库
Queue.Dequeue();
}
public static void DoLongTimeThing(int i)
{
Console.WriteLine("LongTimeJob执行-->:" + i);
for (int j = 0; j < i; j++)
{
HttpGet("http://cn.bing.com/");
}
Console.WriteLine("LongTimeJob执行完毕-->:" + i);
}


public static string HttpGet(string url)
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";

HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
string content = reader.ReadToEnd();
return content;
}
catch (Exception e)
{
return e.Message;
}

}


运行结果如下:

queue

虽然执行结果看起来很乱,但仔细比对可以发现最终的DoShortTime是按顺序执行的。

docker 入门与安装

Docker的概念

什么是Docker

Docker是一个开源的引擎,可以轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器。开发者在笔记本上编译测试通过的容器可以批量地在生产环境中部署,包括VMs(虚拟机)、bare metal、OpenStack 集群和其他的基础应用平台。

Docker的优势

对于开发和运维来说,把程序部署到生产的时候,最常见的问题是环境问题,由于服务器单机的差异,可能会导致问题比较众多烦杂。对于这个问题,docker的优势就可以体现出来了。我们假设一个系统有四个要素组成:应用app,app依赖的类库,配置文件和系统环境。

  • 对于传统的部署

我们需要对以上个因素进行单独的考虑和配置,如果集群则面临了大量的工作量,如果使用虚拟机的快照,也过于庞大

  • docker部署 docker本身是跨平台,镜像中包含应用程序中所需要的类库和环境,一次生成多处运行。即使不跨平台的语言,只要能够运行在docker容器中,就能够实现跨平台。

Docker 安装与使用

Docker的安装

对于docker的安装可以使用以下命令:

1
2
3

$ sudo apt-get install docker

安装完成后,执行

1
2
3

$ docker version

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
Client version: 1.6.2
Client API version: 1.18
Go version (client): go1.5.1
Git commit (client): 7c8fca2
OS/Arch (client): linux/amd64

Server version: 1.6.2
Server API version: 1.18
Go version (server): go1.5.1
Git commit (server): 7c8fca2
OS/Arch (server): linux/amd64

如果有以上结果说明docker 已经安装成功

碰到问题:
connect: permission denied. Are you trying to connect to a TLS-enabled daemon without TLS

原因: 是因为当前的用户没有权限导致,把当前用户添加到docker用户组即可

解决办法: 执行以下命令:

1
2
3
4
5
6
$ sudo gpasswd -a ${USER} docker   # 把当前用户添加到docker组

$ groups # 检查没有没添加到当前用户组

$ sudo service docker.io restart # 重启

Docker的使用

对于docker的使用可以参考官方文档{:target=”_blank”} ,也可以通过执行docker –help命令来查看常用命令的使用。下面演示如何从服务器上面下载项目,docker有一个官方{:target=”_blank”}的镜像服务器,但访问速度非常慢,个人建议使用网易镜像{:target=”_blank”}速度比较快。

  1. 使用docker pull 下载hello-world项目

    1
    2
    3

    $ docker pull hub.c.163.com/library/hello-world:latest

  2. 使用docker images查看本地有哪些镜像

    1
    2
    3

    REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
    hub.c.163.com/library/hello-world latest 7a5a2d73abce 4 months ago 1.84 kB
    1
    2
    3
    4
    5
    6

    3. docker run 运行镜像

    ``` bash

    $ docker run hub.c.163.com/library/hello-world
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    	
    运行结果:


    ![dockerRun](/img/assets/11/01.png)


    4. 删除docker容器

    ``` bash

    $ docker rmi -f hub.c.163.com/library/hello-world
    
    

js如何操作本地程序

背景

假设有这样一个产品,一个web和一个winform客户端,在客户在web的网页上面点击启动客户端来处理,这个时候开始调用本地的客户端,来完成指定的工作。这种场景在日常的上网中也比较常见,如使用迅雷下载。当然实现的方式也有很多种,今天我来演示一种用监控Http请求来实现这个功能,思路如下:

jsApp

HttpListener

对于上面的分析,最重要的功能虽实现对Http的监控,而.net中已经封装了我们的需求,下面看下如何具体的实现:

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
static void Main(string[] args)
{
HttpListener listerner = new HttpListener();

try
{
listerner.AuthenticationSchemes = AuthenticationSchemes.Anonymous;//指定身份验证 Anonymous匿名访问
listerner.Prefixes.Add("http://localhost:8080/Service/");
listerner.Start();
}
catch (Exception ex)
{
Console.WriteLine("无法启动监视:" + ex.Message);
}

Task.Factory.StartNew(() => //使用一个线程对监听
{
while (true)
{
HttpListenerContext ctx = listerner.GetContext();
Task.Factory.StartNew(TaskProc, ctx);//回调函数,开启新线程进行调用,不影响下次监听
}
});

Console.ReadKey();
}


实现请求的响应

现在我们可以拿到请求的上下文的信息ctx,先定义一个参数的格式,简单的定义如下:

1
2
3
4
5
6
7
8
public class ReciveInfo
{
public string path { get; set; }//应用程序所在的路径

public string name { get; set; }//应用程序名称
}


下面对ctx的Response数据进行填写.

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

static void TaskProc(object o)
{
HttpListenerContext ctx = (HttpListenerContext)o;
StreamWriter writer = new StreamWriter(ctx.Response.OutputStream, Encoding.UTF8);
try
{
//接收POST参数
Stream stream = ctx.Request.InputStream;
StreamReader reader = new StreamReader(stream, Encoding.UTF8);
String body = HttpUtility.UrlDecode(reader.ReadToEnd());
Console.WriteLine(body);
var reciveInfo = Json.JsonParser.Deserialize<ReciveInfo>(body);
Process.Start(reciveInfo.path);
ctx.Response.Headers.Add("Access-Control-Allow-Origin","*"); //防止出现跨域的问题错误
ctx.Response.StatusCode = 200; //设置返回给客服端http状态代码
writer.Write(reciveInfo.name + "启动成功");
}

catch (ArgumentException e)
{
ctx.Response.StatusCode = 500;
writer.Write("参数有误:" + e.Message);
}
catch (Exception e)
{
ctx.Response.StatusCode = 500;
writer.Write("程序异常:" + e.Message);
}
finally
{
writer.Close();
ctx.Response.Close();
}

}

测试

在测试中我在js中启动我电脑中的QQ,具体的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22


<button id="btnQQ"> start QQ</button>
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/Javascript">
$(function() {
$("#btnQQ").click(function() {

$.ajax({
type: "POST",
url: "http://localhost:8080/Service",
dataType: "json",
data: JSON.stringify({
path: "D:/Program Files/Tencent/QQ/Bin/QQScLauncher.exe",
name: "qq"
})
});
});
});
</script>


启动后,运行截图如下:

StarQQ

win10移除Hyper

win10碰到的问题

win10 自带的Hyper与Vmare冲突,使用控制面板去除Hyper之后,win10会自动更新把Hyper又重新安装上了。。。经历几次折腾最终还是不行。原因:Hyper-V后VMware都要独占基于CPU等底层硬件的 Hypervisor才能运行,所以二者不能在同一台电脑中同时运行

修改启动项

  1. 以管理员身份运行命令提示符

  2. 在命令提示符窗口中输入以下命令

    
     bcdedit /copy {current} /d "Windows 10 (关闭 Hyper-V)"    
    

    运行后会提示你已经创建了另外一个启动菜单项,记下 { } 中的那串代码。

  3. 然后继续输入并运行以下命令

    
    bcdedit /set {你记下的那串代码} hypervisorlaunchtype OFF   
    

在启动的时候选择”Windows 10 (关闭 Hyper-V)” 这个启动选项就可以使用Vmare了

爬取菜鸟裹裹的数据

菜鸟裹裹{:target=”_blank”}是阿里旗下的一个物流数据的整合平台,数据准确、及时.前几天在关注菜鸟和顺丰的争端,因为在前一天我刚刚爬到菜鸟上面的快递数据,第二天看到二者出现了摩擦,在菜鸟上面已经查不到顺丰的信息了,还好有国家邮政局出面了解决,不得不为我们是社会主义点个赞。这次爬数据经历点波折,个人觉得阿里做的安全性还是很专业的。下面开始介绍如何找到突破口把数据拿到的。



​ 声明:此文只做技术交流,请不要恶意攻击,当然我也相信阿里的技术,不可能轻意被攻破的。


本文Demo下载:

菜鸟裹裹Demo(可能已经不能用)
快递100数据Demo

阅读更多

推荐一个jekyll博客模板

本人用的模板是基于Codeboy的博客模板改造模板{:target=”_blank”},(由于本人可能会有很多样式修改,所以不再将修改pullrequst到原项目,在此对codeboy模板表示感谢)。功能改造如下:

添加微信支付宝打赏

这里也是一个开源的项目,项目地址,使用很方便,直接引用到项目中,配置下就可了:

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
window.tctipConfig = {
staticPrefix: "http://static.tctip.com",
buttonImageId: 1,
buttonTip: "zanzhu",
list:{
alipay: {qrimg: "http://blog.laofu.online/img/assets/o_zhifubao.png"},
weixin:{qrimg: "http://blog.laofu.online/img/assets/o_weixin.png"},
}
};
</script>

<script src="http://static.tctip.com/js/tctip.min.js"></script>

weixin

百度流量监控

为了查看个人的博客的人气,添加了百度的统计模块,可以方便的看到博客的pv/uv.注册地址{:target=”_blank”}

static

具体的添加方式如下 :

  1. 在_incluides文件夹下添加一个baidu_analyze.html的文件,内容如下

    
    	{%if site.baidu_analyze %}
    	
    
    	{% endif %}
    	
    
{{site.baidu_analyze}} 是一个变量,可以把你申请的baidu的key值作为_config.yml的配置值存储下来。
  1. 下面只需要保证所有的post生成都能包含这个html就可以了 ,我的选择是在head里面中把baidu_analyze.html包含进去:

head

添加thickbox插件

这个插件是一个图片的浏览插件,thickbox官方网站{:target=”_blank”}。首先先引入对应的js ,具体的使用是在img的标签上包含一个a标签,这个动作可以使用jQuery方便的实现:


	<script type="text/Javascript">
   
 (function(){
       $("article img").each(function(index,value){

           var aDom=$("<a></a>").attr({"href":$(value).attr("src")+"?inlineId=myOnPageContent"}).addClass("thickbox");
           $(value).wrap(aDom);

       });
   })();
	</script>

thickbox的演示后的效果:

thickbox

其它样式问题

  • 图片的样式由由居中改成居左

  • 去除代码行号样式错乱问题

github地址{:target=”_blank”}

使用Sublime+MarkDown快速写博客

前端的开发人员应该都知道sublime的神器,今天就说说如何使用sublime结合markdown快速写博客。

添加Snippets

在使用jekyll写博客的时候开篇都需要去写一个头部,内容如下:

 layout: post 
 title:xxxxx     
 date:xxxxxxx 
 author:xxxx
```        	
 对于这个固定格式我们可以定义一个Snippets,具体的步骤如下:      

 
 1. 在sublime中的** Tools-->Developer-->New Snippets..  **         

 ``` bash  
 <snippet>
	<content><![CDATA[
Hello, ${1:this} is a ${2:snippet}.
]]></content>
	<!-- Optional: Set a tabTrigger to define how to trigger the snippet -->
	<!-- <tabTrigger>hello</tabTrigger> -->
	<!-- Optional: Set a scope to limit where the snippet will trigger -->
	<!-- <scope>source.python</scope> -->
</snippet>

上面代码片段包含了sublime在什么时候插入什么内,详细参考官方文档Snippets{:target=”_blank”}

content

  • Hello, ${1:this} is a ${2:snippet}. 要的显示的文本

    其中的${}符号是tab索引占位,${1:time},说明此处是tab第一个占位,默认值是time

tabTrigger

  • <tabTrigger>hello</tabTrigger> 要触发的版本

scope

  • <scope>source.python</scope> 在那个类型文件触发

下面是我根据我自己的需要来创建的snippets,在markdown和html模式下,输入blog+tab 就直接显示上面的内容.


<snippet>
<content><![CDATA[
---
layout: post
title: ${1}  
date: ${2:time} 
header-img: "img/home-bg.jpg"
tags:
    - ${3}
author: '付威'     
---
${4}    
]]></content>
<!-- Optional: Set a tabTrigger to define how to trigger the snippet -->
<tabTrigger>blog</tabTrigger>
<!-- Optional: Set a scope to limit where the snippet will trigger -->
<scope>text.html.markdown,text.html</scope>
</snippet>

注意:创建完成后,一定要保存成.sublime-snippet,效果如下:

blog

自定义编译系统

当写完一个博客的时候,可以执行jekyll server去在本地查看效果,当文件发生发动的时候,jekyll也会自动重新最新的博客。但如果要把数据上传到github上面,不得不输入以下几个命令:


 git add .

 git commit -m 'update'   

 git push origin gh-pages

当完成上传之后,还要手动打开网站去查看最终的博客效果。下面就把这个过程做成一个sublime编译的系统,首先我先演示下windows下如果自动化完成这个功能。

  1. 首先根据上面的功能创建一个批处理文件,文件为post.bat 结尾:

    
     @echo off       
    
     cd ..  
    
     git add . 
    
     git commit -m 'update'
    
     git push origin gh-pages  
    
     start http://blog.laofu.online
    

在_posts目录下面运行的时候,可以看到,脚本可以自动把脚本 传入到git上面,同时默认的浏览器打开博客。

  1. 新建一个编译系统 Tools–>Build System–>New Build System .. ,sublime会提供一个默认的数据,详细配置参见Build Systems – Configuration{:target=”_blank”} ,此处我们可以修改成如下的配置:

     {
         "cmd": ["处理文件的目录\\post.bat", "$file"],
         "working_dir": "$file_path",
         "selector": "text.html.markdown"
     }
    

    配置修改完成后,保存成.sublime-build文件。当我们写好博客后,按Ctrl+B的时候,sublime会自动调用处理文件,完成上传发布工作。

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×