<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>技术文章/分享 &#8211; Skyline Traveler</title>
	<atom:link href="http://skyline-traveler.com/category/technology/feed/" rel="self" type="application/rss+xml" />
	<link>http://skyline-traveler.com</link>
	<description></description>
	<lastBuildDate>Fri, 17 Mar 2023 10:31:15 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>
	<item>
		<title>【转】浅谈算法和数据结构: 哈希表</title>
		<link>http://skyline-traveler.com/2019/04/30/43/</link>
		
		<dc:creator><![CDATA[Bob]]></dc:creator>
		<pubDate>Tue, 30 Apr 2019 07:26:21 +0000</pubDate>
				<category><![CDATA[技术文章/分享]]></category>
		<category><![CDATA[未分类]]></category>
		<category><![CDATA[哈希]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[数据结构]]></category>
		<category><![CDATA[算法]]></category>
		<guid isPermaLink="false">http://skyline-traveler.com/?p=43</guid>

					<description><![CDATA[作者： yangecnu（yangecn<p><span class="more-link"><a href="http://skyline-traveler.com/2019/04/30/43/" class="readmore">Continue reading<span class="screen-reader-text">【转】浅谈算法和数据结构: 哈希表</span></a></span></p>]]></description>
										<content:encoded><![CDATA[
<p>作者： yangecnu（yangecnu&#8217;s Blog on 博客园） </p>



<p>出处：http://www.cnblogs.com/yangecnu/  http://www.cnblogs.com/yangecnu/p/Introduce-Hashtable.html</p>



<p>基于无序列表的顺序查找，基于有序数组的二分查找，平衡查找树，以及红黑树，下图是他们在平均以及最差情况下的时间复杂度：</p>



<p>Different structure with different efficient</p>



<p>可以看到在时间复杂度上，红黑树在平均情况下插入，查找以及删除上都达到了lgN的时间复杂度。</p>



<p>那么有没有查找效率更高的数据结构呢，答案就是本文接下来要介绍了散列表，也叫哈希表(Hash Table)</p>



<p>什么是哈希表<br>
哈希表就是一种以 键-值(key-indexed) 存储数据的结构，我们只要输入待查找的值即key，即可查找到其对应的值。</p>



<p>哈希的思路很简单，如果所有的键都是整数，那么就可以使用一个简单的无序数组来实现：将键作为索引，值即为其对应的值，这样就可以快速访问任意键的值。这是对于简单的键的情况，我们将其扩展到可以处理更加复杂的类型的键。</p>



<p>使用哈希查找有两个步骤:</p>



<p>使用哈希函数将被查找的键转换为数组的索引。在理想的情况下，不同的键会被转换为不同的索引值，但是在有些情况下我们需要处理多个键被哈希到同一个索引值的情况。所以哈希查找的第二个步骤就是处理冲突<br>
处理哈希碰撞冲突。有很多处理哈希碰撞冲突的方法，本文后面会介绍拉链法和线性探测法。<br>
哈希表是一个在时间和空间上做出权衡的经典例子。如果没有内存限制，那么可以直接将键作为数组的索引。那么所有的查找时间复杂度为O(1)；如果没有时间限制，那么我们可以使用无序数组并进行顺序查找，这样只需要很少的内存。哈希表使用了适度的时间和空间来在这两个极端之间找到了平衡。只需要调整哈希函数算法即可在时间和空间上做出取舍。</p>



<p>哈希函数<br>
哈希查找第一步就是使用哈希函数将键映射成索引。这种映射函数就是哈希函数。如果我们有一个保存0-M数组，那么我们就需要一个能够将任意键转换为该数组范围内的索引（0~M-1）的哈希函数。哈希函数需要易于计算并且能够均匀分布所有键。比如举个简单的例子，使用手机号码后三位就比前三位作为key更好，因为前三位手机号码的重复率很高。再比如使用身份证号码出生年月位数要比使用前几位数要更好。</p>



<p>在实际中，我们的键并不都是数字，有可能是字符串，还有可能是几个值的组合等，所以我们需要实现自己的哈希函数。</p>



<ol class="wp-block-list"><li>正整数<br>
获取正整数哈希值最常用的方法是使用除留余数法。即对于大小为素数M的数组，对于任意正整数k，计算k除以M的余数。M一般取素数。</li><li>字符串<br>
将字符串作为键的时候，我们也可以将他作为一个大的整数，采用保留除余法。我们可以将组成字符串的每一个字符取值然后进行哈希，比如</li></ol>



<p>public int GetHashCode(string str)<br>
{<br>
    char[] s = str.ToCharArray();<br>
    int hash = 0;<br>
    for (int i = 0; i &lt; s.Length; i++)<br>
    {<br>
        hash = s[i] + (31 * hash); <br>
    }<br>
    return hash;<br>
}<br>
上面的哈希值是Horner计算字符串哈希值的方法，公式为:</p>



<p>h = s[0] · 31L–1 + … + s[L – 3] · 312 + s[L – 2] · 311 + s[L – 1] · 310</p>



<p>举个例子，比如要获取”call”的哈希值，字符串c对应的unicode为99，a对应的unicode为97，L对应的unicode为108，所以字符串”call”的哈希值为 3045982 = 99·313 + 97·312 + 108·311 + 108·310 = 108 + 31· (108 + 31 · (97 + 31 · (99)))</p>



<p>如果对每个字符去哈希值可能会比较耗时，所以可以通过间隔取N个字符来获取哈西值来节省时间，比如，可以 获取每8-9个字符来获取哈希值：</p>



<p>public int GetHashCode(string str)<br>
{<br>
    char[] s = str.ToCharArray();<br>
    int hash = 0;<br>
    int skip = Math.Max(1, s.Length / 8);<br>
    for (int i = 0; i &lt; s.Length; i+=skip)<br>
    {<br>
        hash = s[i] + (31 * hash);<br>
    }<br>
    return hash;<br>
}<br>
但是，对于某些情况，不同的字符串会产生相同的哈希值，这就是前面说到的哈希冲突（Hash Collisions），比如下面的四个字符串：</p>



<p>hash code collision</p>



<p>如果我们按照每8个字符取哈希的话，就会得到一样的哈希值。所以下面来讲解如何解决哈希碰撞：</p>



<p>避免哈希冲突<br>
拉链法 (Separate chaining with linked lists)<br>
通过哈希函数，我们可以将键转换为数组的索引(0-M-1)，但是对于两个或者多个键具有相同索引值的情况，我们需要有一种方法来处理这种冲突。</p>



<p>一种比较直接的办法就是，将大小为M 的数组的每一个元素指向一个条链表，链表中的每一个节点都存储散列值为该索引的键值对，这就是拉链法。下图很清楚的描述了什么是拉链法。</p>



<p>seperate chaining with link list</p>



<p>图中，”John Smith”和”Sandra Dee” 通过哈希函数都指向了152 这个索引，该索引又指向了一个链表， 在链表中依次存储了这两个字符串。</p>



<p>该方法的基本思想就是选择足够大的M，使得所有的链表都尽可能的短小，以保证查找的效率。对采用拉链法的哈希实现的查找分为两步，首先是根据散列值找到等一应的链表，然后沿着链表顺序找到相应的键。 我们现在使用我们之前介绍符号表中的使用无序链表实现的查找表SequentSearchSymbolTable 来实现我们这里的哈希表。当然，您也可以使用.NET里面内置的LinkList。</p>



<p>首先我们需要定义一个链表的总数，在内部我们定义一个SequentSearchSymbolTable的数组。然后每一个映射到索引的地方保存一个这样的数组。</p>



<p>public class SeperateChainingHashSet : SymbolTables where TKey : IComparable, IEquatable<br>
{<br>
    private int M;//散列表大小<br>
    private SequentSearchSymbolTable[] st;//</p>



<pre class="wp-block-code"><code>public SeperateChainingHashSet()
    : this(997)
{

}

public SeperateChainingHashSet(int m)
{
    this.M = m;
    st = new SequentSearchSymbolTable&lt;TKey, TValue&gt;&#91;m];
    for (int i = 0; i &lt; m; i++)
    {
        st&#91;i] = new SequentSearchSymbolTable&lt;TKey, TValue&gt;();
    }
}

private int hash(TKey key)
{
    return (key.GetHashCode() &amp; 0x7fffffff) % M;
}

public override TValue Get(TKey key)
{
    return st&#91;hash(key)].Get(key);
}

public override void Put(TKey key, TValue value)
{
    st&#91;hash(key)].Put(key, value);
}</code></pre>



<p>}<br>
可以看到，该实现中使用</p>



<p>Get方法来获取指定key的Value值，我们首先通过hash方法来找到key对应的索引值，即找到SequentSearchSymbolTable数组中存储该元素的查找表，然后调用查找表的Get方法，根据key找到对应的Value。<br>
Put方法用来存储键值对，首先通过hash方法找到改key对应的哈希值，然后找到SequentSearchSymbolTable数组中存储该元素的查找表，然后调用查找表的Put方法，将键值对存储起来。<br>
hash方法来计算key的哈希值， 这里首先通过取与&amp;操作，将符号位去除，然后采用除留余数法将key应到到0-M-1的范围，这也是我们的查找表数组索引的范围。<br>
实现基于拉链表的散列表，目标是选择适当的数组大小M，使得既不会因为空链表而浪费内存空间，也不会因为链表太而在查找上浪费太多时间。拉链表的优点在于，这种数组大小M的选择不是关键性的，如果存入的键多于预期，那么查找的时间只会比选择更大的数组稍长，另外，我们也可以使用更高效的结构来代替链表存储。如果存入的键少于预期，索然有些浪费空间，但是查找速度就会很快。所以当内存不紧张时，我们可以选择足够大的M，可以使得查找时间变为常数，如果内存紧张时，选择尽量大的M仍能够将性能提高M倍。</p>



<p>线性探测法<br>
线性探测法是开放寻址法解决哈希冲突的一种方法，基本原理为，使用大小为M的数组来保存N个键值对，其中M&gt;N，我们需要使用数组中的空位解决碰撞冲突。如下图所示：</p>



<p>open address</p>



<p>对照前面的拉链法，在该图中，”Ted Baker” 是有唯一的哈希值153的，但是由于153被”Sandra Dee”占用了。而原先”Snadra Dee”和”John Smith”的哈希值都是152的，但是在对”Sandra Dee”进行哈希的时候发现152已经被占用了，所以往下找发现153没有被占用，所以存放在153上，然后”Ted Baker”哈希到153上，发现已经被占用了，所以往下找，发现154没有被占用，所以值存到了154上。</p>



<p>开放寻址法中最简单的是线性探测法：当碰撞发生时即一个键的散列值被另外一个键占用时，直接检查散列表中的下一个位置即将索引值加1，这样的线性探测会出现三种结果：</p>



<p>命中，该位置的键和被查找的键相同<br>
未命中，键为空<br>
继续查找，该位置和键被查找的键不同。<br>
实现线性探测法也很简单，我们只需要两个大小相同的数组分别记录key和value。</p>



<p>public class LinearProbingHashSet : SymbolTables where TKey : IComparable, IEquatable<br>
{<br>
    private int N;//符号表中键值对的总数<br>
    private int M = 16;//线性探测表的大小<br>
    private TKey[] keys;<br>
    private TValue[] values;</p>



<pre class="wp-block-code"><code>public LinearProbingHashSet()
{
    keys = new TKey&#91;M];
    values = new TValue&#91;M];
}

private int hash(TKey key)
{
    return (key.GetHashCode() &amp; 0xFFFFFFF) % M;
}

public override TValue Get(TKey key)
{
    for (int i = hash(key); keys&#91;i] != null; i = (i + 1) % M)
    {
        if (key.Equals(keys&#91;i])) { return values&#91;i]; }
    }
    return default(TValue);
}

public override void Put(TKey key, TValue value)
{
    int hashCode = hash(key);
    for (int i = hashCode; keys&#91;i] != null; i = (i + 1) % M)
    {
        if (keys&#91;i].Equals(key))//如果和已有的key相等，则用新值覆盖
        {
            values&#91;i] = value;
            return;
        }
        //插入
        keys&#91;i] = key;
        values&#91;i] = value;
    }
}</code></pre>



<p>}<br>
线性探查（Linear Probing）方式虽然简单，但是有一些问题，它会导致同类哈希的聚集。在存入的时候存在冲突，在查找的时候冲突依然存在。</p>



<p>性能分析<br>
我们可以看到，哈希表存储和查找数据的时候分为两步，第一步为将键通过哈希函数映射为数组中的索引， 这个过程可以认为是只需要常数时间的。第二步是，如果出现哈希值冲突，如何解决，前面介绍了拉链法和线性探测法下面就这两种方法进行讨论：</p>



<p>对于拉链法，查找的效率在于链表的长度，一般的我们应该保证长度在M/8~M/2之间，如果链表的长度大于M/2，我们可以扩充链表长度。如果长度在0~M/8时，我们可以缩小链表。</p>



<p>对于线性探测法，也是如此，但是动态调整数组的大小需要对所有的值从新进行重新散列并插入新的表中。</p>



<p>不管是拉链法还是散列法，这种动态调整链表或者数组的大小以提高查询效率的同时，还应该考虑动态改变链表或者数组大小的成本。散列表长度加倍的插入需要进行大量的探测， 这种均摊成本在很多时候需要考虑。</p>



<p>哈希碰撞攻击<br>
我们知道如果哈希函数选择不当会使得大量的键都会映射到相同的索引上，不管是采用拉链法还是开放寻址法解决冲突，在后面查找的时候都需要进行多次探测或者查找， 在很多时候会使得哈希表的查找效率退化，而不再是常数时间。下图清楚的描述了退化后的哈希表：</p>



<p>hashCollision </p>



<p>哈希表攻击就是通过精心构造哈希函数，使得所有的键经过哈希函数后都映射到同一个或者几个索引上，将哈希表退化为了一个单链表，这样哈希表的各种操作，比如插入，查找都从O(1)退化到了链表的查找操作，这样就会消耗大量的CPU资源，导致系统无法响应，从而达到拒绝服务供给(Denial of Service, Dos)的目的。之前由于多种编程语言的哈希算法的“非随机”而出现了Hash碰撞的DoS安全漏洞，在ASP.NET中也曾出现过这一问题。</p>



<p>在.NET中String的哈希值内部实现中，通过使用哈希值随机化来对这种问题进行了限制，通过对碰撞次数设置阈值，超过该阈值就对哈希函数进行随机化，这也是防止哈希表退化的一种做法。下面是BCL中string类型的GetHashCode方法的实现，可以看到，当碰撞超过一定次数的时候，就会开启条件编译，对哈希函数进行随机化。</p>



<p>[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), SecuritySafeCritical, __DynamicallyInvokable]<br>
public override unsafe int GetHashCode()<br>
{<br>
    if (HashHelpers.s_UseRandomizedStringHashing)<br>
    {<br>
        return InternalMarvin32HashString(this, this.Length, 0L);<br>
    }<br>
    fixed (char* str = ((char<em>) this))
    {
        char</em> chPtr = str;<br>
        int num = 0x15051505;<br>
        int num2 = num;<br>
        int* numPtr = (int*) chPtr;<br>
        int length = this.Length;<br>
        while (length &gt; 2)<br>
        {<br>
            num = (((num &lt;&lt; 5) + num) + (num &gt;&gt; 0x1b)) ^ numPtr[0];<br>
            num2 = (((num2 &lt;&lt; 5) + num2) + (num2 &gt;&gt; 0x1b)) ^ numPtr[1];<br>
            numPtr += 2;<br>
            length -= 4;<br>
        }<br>
        if (length &gt; 0)<br>
        {<br>
            num = (((num &lt;&lt; 5) + num) + (num &gt;&gt; 0x1b)) ^ numPtr[0];<br>
        }<br>
        return (num + (num2 * 0x5d588b65));<br>
    }<br>
}</p>



<p>总结<br>
基于无序列表的顺序查找，基于有序数组的二分查找，平衡查找树，以及红黑树，本篇文章最后介绍了查找算法中的最后一类即符号表又称哈希表,并介绍了哈希函数以及处理哈希冲突的两种方法：拉链法和线性探测法。各种查找算法的最坏和平均条件下各种操作的时间复杂度如下图:</p>



<p>search method efficient conclusion</p>



<p>在实际编写代码中，如何选择合适的数据结构需要根据具体的数据规模，查找效率要求，时间和空间局限来做出合适的选择</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>关于git的一些使用分享</title>
		<link>http://skyline-traveler.com/2019/04/30/%e5%85%b3%e4%ba%8egit%e7%9a%84%e4%b8%80%e4%ba%9b%e4%bd%bf%e7%94%a8%e5%88%86%e4%ba%ab/</link>
		
		<dc:creator><![CDATA[Bob]]></dc:creator>
		<pubDate>Tue, 30 Apr 2019 07:08:18 +0000</pubDate>
				<category><![CDATA[技术文章/分享]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[代码仓库]]></category>
		<category><![CDATA[版本控制]]></category>
		<guid isPermaLink="false">http://skyline-traveler.com/?p=35</guid>

					<description><![CDATA[关于版本控制 什么是版本控制？我为什么要<p><span class="more-link"><a href="http://skyline-traveler.com/2019/04/30/%e5%85%b3%e4%ba%8egit%e7%9a%84%e4%b8%80%e4%ba%9b%e4%bd%bf%e7%94%a8%e5%88%86%e4%ba%ab/" class="readmore">Continue reading<span class="screen-reader-text">关于git的一些使用分享</span></a></span></p>]]></description>
										<content:encoded><![CDATA[
<hr class="wp-block-separator"/>



<p>关于版本控制</p>



<p>什么是版本控制？我为什么要关心它呢？版本控制是一种记录一个或若干文件内容变化，以便将来查阅特定版本修订情况的系统。我们除了可以对软件代码作版本控制管理之外，还可以在其他方面也实行版本控制，可以对任何类型的文件进行版本控制,例如多人协作完成大型的文学作品的时候。</p>



<p>版本控制又分为:</p>



<ul class="wp-block-list"><li>本地版本控制系统</li><li>集中式版本控制系统</li><li>分布式版本控制系统</li></ul>



<p>为什么使用版本控制(好处)</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>使用版本控制,方便多人协作开发时的代码<strong>合并</strong>;<br>
  可以使我们对文件,代码等的修改情况有一个清晰的记录,当新的修改不满意时,可以准确的<strong>回滚</strong>到先前的版本;<br>
  当本地代码因各种原因损坏时,可以迅速的从仓库<strong>还原</strong>至本地;<br>
  同时当多人协作的时候,可以明确成员各自做了什么修改,当代码/文本等产生bug的时候便于<strong>追根溯源</strong>.</p></blockquote>



<p>常用工具</p>



<ul class="wp-block-list"><li>SVN(集中式)</li><li>Git(分布式)<br>
  注意 git ≠ GitHub,一个是工具,一个是平台</li></ul>



<figure class="wp-block-image"><img decoding="async" src="https://git-scm.com/images/logo@2x.png" alt="git-logo"/></figure>



<figure class="wp-block-image"><img decoding="async" src="https://github.githubassets.com/images/modules/open_graph/github-octocat.png" alt="GitHub-logo"/></figure>



<h1 class="wp-block-heading">Git的安装</h1>



<h3 class="wp-block-heading">在 Linux 上安装</h3>



<p>在Linux安装,以CentOS 上为例，一般使用 yum的方式：</p>



<pre class="wp-block-code"><code>  $ sudo yum install git</code></pre>



<p><br>
其他Linux版本参考Git 官方网站上的各种系统的安装步骤，网址为 http://git-scm.com/download/linux。</p>



<h3 class="wp-block-heading">在 Mac 上安装</h3>



<p>在 Mac 上安装 Git 有多种方式。 最简单的方法是安装 Xcode Command Line Tools。 Mavericks （10.9） 或更高版本的系统中，在 Terminal 里尝试首次运行 git 命令即可。 如果没有安装过命令行开发者工具，将会提示你安装。<br>
或者前往下载相关安装包:http://git-scm.com/download/mac。</p>



<h3 class="wp-block-heading">在 Windows 上安装</h3>



<p>在 Windows 上安装 Git 也有几种安装方法。 官方版本可以在 Git 官方网站下载。 打开 http://git-scm.com/download/win，下载会自动开始。 要注意这是一个名为 Git for Windows的项目（也叫做 msysGit），和 Git 是分别独立的项目；更多信息请访问 http://msysgit.github.io/。</p>



<p>另一个简单的方法是安装 GitHub for Windows。 该安装程序包含图形化和命令行版本的 Git。 它也能支持 Powershell，提供了稳定的凭证缓存和健全的 CRLF 设置。 稍后我们会对这方面有更多了解，现在只要一句话就够了，这些都是你所需要的。 你可以在 GitHub for Windows 网站下载，网址为 http://windows.github.com。</p>



<h2 class="wp-block-heading">配置</h2>



<p>用户配置<br>
如下配置一次,避免后面每次操作都需要输入用户信息</p>



<pre class="wp-block-code"><code>$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com</code></pre>



<p>安装完成后,使用Git还需要做一些配置用于向远程仓库拉取和推送代码,其中需要在本地生成私钥和公钥文件,以Mac为例,执行以下命令:</p>



<pre class="wp-block-code"><code>    ssh-keygen -t rsa -C "xxx@hauxi100.com"</code></pre>



<p>最后可以在 <code>~/.ssh</code> 目录下找到生成的 <code>id_rsa</code> 和 <code>id_rsa.pub</code> 文件,即相应的私钥和公钥,用编辑器打开查看id_rsa.pub 复制其中内容到GitHub或者GitLab用户中心相关的设置中,就可以开始使用Git了.</p>



<h1 class="wp-block-heading">开始使用</h1>



<h2 class="wp-block-heading">1.对本地现有项目进行Git管理</h2>



<p>进入项目根目录并使用如下命令进行初始化:</p>



<pre class="wp-block-code"><code>    git init</code></pre>



<p>这样会在项目根目录生成.git目录,其中含有相关的文件<br>
接下来需要将项目中的全部文件加入版本控制</p>



<pre class="wp-block-code"><code>    git add *.c</code></pre>



<p>并提交到第一个版本,加入说明</p>



<pre class="wp-block-code"><code>git commit -m '第一次提交初始化版本'</code></pre>



<h2 class="wp-block-heading">2.从仓库获取项目</h2>



<p>此时需要知晓仓库地址并进行<code>git clone</code>操作,仓库地址在GitHub/Gitlab网站上可以看到 例如成都范儿项目:</p>



<pre class="wp-block-code"><code>    git clone git@**.***.***:****/***.git</code></pre>



<p>等待项目克隆完毕即可开始开发,上述命令后跟上自定义目录名可以将项目克隆到指定目录中<br>
使用<code>git remote -v</code> 列出远程仓库<br>
使用<code>git remote show origin</code>查看远程仓库更多信息</p>



<h2 class="wp-block-heading">3.提交第一次修改</h2>



<p>每次当对项目进行修改,删除,新增后需要提交新版本时,需要执行以下命令:</p>



<pre class="wp-block-code"><code>    git status</code></pre>



<p>用于查看有何变化,这会列出当前项目的变化情况,包含较于上一版本有变化的文件信息</p>



<pre class="wp-block-code"><code>On branch production
Your branch is up to date with 'origin/production'.

Changes not staged for commit:
  (use "git add &lt;file>..." to update what will be committed)
  (use "git checkout -- &lt;file>..." to discard changes in working directory)

    modified:   ../****/***/***/VoteController.php

Untracked files:
  (use "git add &lt;file>..." to include in what will be committed)
    ../***/***/****/laravel-debugbar/
no changes added to commit (use "git add" and/or "git commit -a")</code></pre>



<p>此时我们应该将状态为<code>modified</code>的文件加入新版本中执行:</p>



<pre class="wp-block-code"><code>    git add ../****/***/***/VoteController.php
    git commit -m "这是一次提交,修改了某某文件...."</code></pre>



<p>此时完成了一次提交,但是并没有推送到远程仓库中,还需要:</p>



<pre class="wp-block-code"><code> git push</code></pre>



<p>这样这个版本的代码已经推送到远程仓库,其他用户执行<code>git pull</code>(相当于fetch =&gt; merge)即可拉取到刚刚推送的修改(也可使用<code>git push origin xxxx</code>来推送到指定分支)</p>



<h2 class="wp-block-heading">4.进一步操作</h2>



<p>每次需要向仓库做操作时应该先执行<code>git status</code>查看当前项目有哪些发生变化;</p>



<p>查看尚未<code>add</code>的文件相比之前发生什么变化,使用<code>git diff</code>命令(这个命令本身只显示尚未暂存的改动，而不是自上次提交以来所做的所有改动);</p>



<p>需要查看最近提交过哪些版本时,使用<code>git log</code>查看版本信息(包含版本号,提交者,提交时间等),其中<code>git log -p -2</code> 可以查看最近两次提交的内容差异</p>



<h3 class="wp-block-heading">合并</h3>



<p><code>git merge xxx</code></p>



<h3 class="wp-block-heading">解决冲突</h3>



<p>有时候合并会遇到文件冲突,需要先<code>git status</code>查看冲突情况,并且手动去解决冲突文件,然后使用<code>git add</code>文件</p>



<p>当把错误代码提交到commit时,需要使用<code>git reset --hard xxxx</code>来撤销这条commit</p>



<p>当错误代码已经push到仓库,使用<code>git revert xxxx</code>来撤销push</p>



<ul class="wp-block-list"><li>如果出错内容在私有 branch：在本地把内容修正后，强制 push (push -f）一次就可以解决；</li><li>如果出错内容在 master：不要强制 push，而要用 revert 把写错的 commit 撤销。</li></ul>



<h2 class="wp-block-heading">5.在IDE中使用Git(PHPstorm)</h2>



<p>项目中右键-&gt;git-&gt;各个详细操作</p>



<h1 class="wp-block-heading">其他</h1>



<p>在 Git 中有一个特殊的文本文件：<code>.gitignore</code>。这个文本文件记录了所有你希望被 Git 忽略的目录和文件。</p>



<h2 class="wp-block-heading">常用命令</h2>



<pre class="wp-block-code"><code>git checkout  ##检出 (用于创建/切换分支),**慎重使用**

git status  ##查看当前项目状态(修改,新增,删除等)

git checkout -b ##检出(指定分支)

git commit  ##提交版本

git diff  ##比较文件差异

git add  ##提交指定文件到版本

git clean ##删除所有没有被版本控制过的文件

git fetch --prune  ##从仓库获取内容

git pull --ff-only  ##获取内容并且合并

git branch  ##查看当前分支名
git branch -d xxx ##删除分支

git push  ##推送

git merge xx ##合并分支

git rm  ##删除文件

git mv  ##移动文件 相当于mv => rm => add操作

git rebase ##高级合并

git reset  ##撤销commit 

git revert  ##撤销push

git push origin :yourbranch  ##推送到指定分支</code></pre>



<p>部分内容参考出处 <a href="http://wusibo.com/booklet-git/">Git 原理详解及实用指南</a></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Yii2批量插入/更新数据</title>
		<link>http://skyline-traveler.com/2019/04/23/yii2%e6%89%b9%e9%87%8f%e6%8f%92%e5%85%a5-%e6%9b%b4%e6%96%b0%e6%95%b0%e6%8d%ae/</link>
		
		<dc:creator><![CDATA[Bob]]></dc:creator>
		<pubDate>Tue, 23 Apr 2019 05:58:27 +0000</pubDate>
				<category><![CDATA[技术文章/分享]]></category>
		<category><![CDATA[MYSQL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[YII2]]></category>
		<guid isPermaLink="false">http://skyline-traveler.com/?p=19</guid>

					<description><![CDATA[在使用yii2开发项目时,有时候会遇到这<p><span class="more-link"><a href="http://skyline-traveler.com/2019/04/23/yii2%e6%89%b9%e9%87%8f%e6%8f%92%e5%85%a5-%e6%9b%b4%e6%96%b0%e6%95%b0%e6%8d%ae/" class="readmore">Continue reading<span class="screen-reader-text">Yii2批量插入/更新数据</span></a></span></p>]]></description>
										<content:encoded><![CDATA[
<p>在使用yii2开发项目时,有时候会遇到这样的情况: 向后台发送多条数据,其中一些数据已经存在记录,只需要对其部分字段的值进行修改;而另一部分的数据则需要新添加进去. 这就需要对添加的数据进行判断,其中一些执行update,剩下的执行insert 代码如下,不对的地方请指教:</p>



<pre class="wp-block-preformatted">```<br>....其他代码<br>//批量更新,并将需要批量插入的数据放入数组中<br>foreach($goods&nbsp;as&nbsp;$k&nbsp;=&gt;&nbsp;$v)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;if(yourModel::updateAllCounters(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;['goods_num'&nbsp;=&gt;&nbsp;$v],<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;['goods_id'&nbsp;=&gt;&nbsp;$k,'user_id'&nbsp;=&gt;&nbsp;$id]&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;))<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;continue;//如果已经更新,则跳过此次循环,到下一次<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;$data[]&nbsp;=&nbsp;[<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'user_id'&nbsp;=&gt;&nbsp;$id,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'goods_id'&nbsp;=&gt;&nbsp;$k,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'goods_num'&nbsp;=&gt;&nbsp;$v,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'created_time'&nbsp;=&gt;&nbsp;$time,<br>&nbsp;&nbsp;&nbsp;&nbsp;]<br>}<br>//再执行批量插入<br>if&nbsp;(isset($data))&nbsp;<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;Yii::$app-&gt;db-&gt;createCommand()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-&gt;batchInsert(yourModel::tableName(),['user_id','goods_id','goods_num','created_time'],<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$data)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-&gt;execute();<br>}<br>......其他代码<br>```<br></pre>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
