<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>CoolCode</title>
  <icon>https://www.gravatar.com/avatar/7e062dac4a8449600c9a7feca7e2a529</icon>
  <subtitle>艺术需要千锤百炼，技术需要日积月累</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://coolcode.org/"/>
  <updated>2020-03-10T04:32:00.000Z</updated>
  <id>https://coolcode.org/</id>
  
  <author>
    <name>小马哥</name>
    <email>andot@qq.com</email>
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>媒体广告变现优化之道（七）</title>
    <link href="https://coolcode.org/2020/04/10/ad-monetization-optimization-7/"/>
    <id>https://coolcode.org/2020/04/10/ad-monetization-optimization-7/</id>
    <published>2020-04-10T04:32:00.000Z</published>
    <updated>2020-03-10T04:32:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="如何获取地理位置信息"><a href="#如何获取地理位置信息" class="headerlink" title="如何获取地理位置信息"></a>如何获取地理位置信息</h1><p>OpenRTB 中有一个 <code>geo</code> 字段，该字段对应一个 <code>geo</code> 对象，这个对象中有好多字段，这里只讨论两个关键的字段 <code>lat</code> 和 <code>lon</code> 字段的获取。其中 <code>lat</code> 表示纬度，<code>lon</code> 表示经度。</p><a id="more"></a><h2 id="获取-Android-设备的地理位置信息"><a href="#获取-Android-设备的地理位置信息" class="headerlink" title="获取 Android 设备的地理位置信息"></a>获取 Android 设备的地理位置信息</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">import</span> android.Manifest;</span><br><span class="line"><span class="keyword">import</span> android.content.Context;</span><br><span class="line"><span class="keyword">import</span> android.content.pm.PackageManager;</span><br><span class="line"><span class="keyword">import</span> android.location.Location;</span><br><span class="line"><span class="keyword">import</span> android.location.LocationListener;</span><br><span class="line"><span class="keyword">import</span> android.location.LocationManager;</span><br><span class="line"><span class="keyword">import</span> android.os.Bundle;</span><br><span class="line"><span class="keyword">import</span> android.os.Process;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">GeoInfo</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> LocationListener locationListener;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">volatile</span> Location location;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">(Context context)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">final</span> LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);</span><br><span class="line">        List&lt;String&gt; providers = locationManager.getProviders(<span class="keyword">true</span>);</span><br><span class="line">        String provider = <span class="keyword">null</span>;</span><br><span class="line">        <span class="keyword">if</span> (context.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED) &#123;</span><br><span class="line">            <span class="keyword">if</span> (providers.contains(LocationManager.NETWORK_PROVIDER)) &#123;</span><br><span class="line">                provider = LocationManager.NETWORK_PROVIDER;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (provider == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (context.checkPermission(Manifest.permission.ACCESS_FINE_LOCATION, Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED) &#123;</span><br><span class="line">                <span class="keyword">if</span> (providers.contains(LocationManager.GPS_PROVIDER)) &#123;</span><br><span class="line">                    provider = LocationManager.GPS_PROVIDER;</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (providers.contains(LocationManager.PASSIVE_PROVIDER)) &#123;</span><br><span class="line">                    provider = LocationManager.PASSIVE_PROVIDER;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (provider == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        location = locationManager.getLastKnownLocation(provider);</span><br><span class="line">        <span class="keyword">if</span> (location != <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        locationListener = <span class="keyword">new</span> LocationListener() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onLocationChanged</span><span class="params">(Location location)</span> </span>&#123;</span><br><span class="line">                GeoInfo.location = location;</span><br><span class="line">                <span class="keyword">if</span> (locationListener != <span class="keyword">null</span>) &#123;</span><br><span class="line">                    locationManager.removeUpdates(locationListener);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onStatusChanged</span><span class="params">(String provider, <span class="keyword">int</span> status, Bundle extras)</span> </span>&#123;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onProviderEnabled</span><span class="params">(String provider)</span> </span>&#123;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onProviderDisabled</span><span class="params">(String provider)</span> </span>&#123;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">        <span class="keyword">final</span> String finalProvider = provider;</span><br><span class="line">        <span class="keyword">if</span> (context.checkPermission(Manifest.permission.ACCESS_FINE_LOCATION, Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED &amp;&amp;</span><br><span class="line">                context.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                locationManager.requestLocationUpdates(finalProvider, <span class="number">0</span>, <span class="number">0</span>, locationListener);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">static</span> <span class="keyword">double</span> <span class="title">getLatitude</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (location == <span class="keyword">null</span>) <span class="keyword">return</span> <span class="number">0.0</span>;</span><br><span class="line">        <span class="keyword">return</span> location.getLatitude();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">static</span> <span class="keyword">double</span> <span class="title">getLongitude</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (location == <span class="keyword">null</span>) <span class="keyword">return</span> <span class="number">0.0</span>;</span><br><span class="line">        <span class="keyword">return</span> location.getLongitude();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">static</span> <span class="keyword">float</span> <span class="title">getAccuracy</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (location == <span class="keyword">null</span>) <span class="keyword">return</span> <span class="number">0.0f</span>;</span><br><span class="line">        <span class="keyword">return</span> location.getAccuracy();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里为了不增加依赖，我们直接用了 <code>Context</code> 的 <code>checkPermission</code> 方法来进行权限检测。</p><p>另外，在 <code>AndroidManifest.xml</code> 中还需要加入下面的权限声明：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.ACCESS_COARSE_LOCATION"</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.ACCESS_FINE_LOCATION"</span> /&gt;</span></span><br></pre></td></tr></table></figure><h2 id="获取-iOS-设备的地理位置信息"><a href="#获取-iOS-设备的地理位置信息" class="headerlink" title="获取 iOS 设备的地理位置信息"></a>获取 iOS 设备的地理位置信息</h2><figure class="highlight objc"><table><tr><td class="code"><pre><span class="line"><span class="meta">#import <span class="meta-string">&lt;Foundation/Foundation.h&gt;</span></span></span><br><span class="line"><span class="meta">#import <span class="meta-string">&lt;CoreLocation/CoreLocation.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">GeoInfo</span>()&lt;<span class="title">CLLocationManagerDelegate</span>&gt;</span></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">GeoInfo</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> GeoInfo *delegate;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> <span class="built_in">CLLocationManager</span>* locationManager;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">double</span> latitude = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">double</span> longitude = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">double</span> accuracy = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">+(<span class="keyword">void</span>)load &#123;</span><br><span class="line">    <span class="built_in">dispatch_async</span>(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="number">0</span>), ^&#123;</span><br><span class="line">        delegate = [GeoInfo new];</span><br><span class="line">        locationManager = [<span class="built_in">CLLocationManager</span> new];</span><br><span class="line">        locationManager.delegate = delegate;</span><br><span class="line">        locationManager.desiredAccuracy = kCLLocationAccuracyBest;</span><br><span class="line">        [locationManager requestWhenInUseAuthorization];</span><br><span class="line">        [locationManager startMonitoringSignificantLocationChanges];</span><br><span class="line">        [locationManager startUpdatingLocation];</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)locationManager:(<span class="built_in">CLLocationManager</span> *)manager</span><br><span class="line">     didUpdateLocations:(<span class="built_in">NSArray</span>&lt;<span class="built_in">CLLocation</span> *&gt; *)locations &#123;</span><br><span class="line">    <span class="built_in">CLLocation</span> *location = [locations lastObject];</span><br><span class="line">    latitude = location.coordinate.latitude;</span><br><span class="line">    longitude = location.coordinate.longitude;</span><br><span class="line">    accuracy = manager.desiredAccuracy;</span><br><span class="line">    [locationManager stopUpdatingLocation];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">+ (<span class="keyword">const</span> <span class="keyword">double</span>)latitude &#123;</span><br><span class="line">    <span class="keyword">return</span> latitude;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">+ (<span class="keyword">const</span> <span class="keyword">double</span>)longitude &#123;</span><br><span class="line">    <span class="keyword">return</span> longitude;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">+ (<span class="keyword">const</span> <span class="keyword">double</span>)accuracy &#123;</span><br><span class="line">    <span class="keyword">return</span> accuracy;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure><p>在 iOS 中要获取地理位置信息，除了使用上面的代码之外，还需要在 <code>info.plist</code> 添加 <code>Privacy - Location Always Usage Description</code> 或者 <code>Privacy - Location When In Use Usage Description</code> 权限，类型为 <code>String</code>，<code>value</code> 中一定要有值， 来告诉用户使用定位服务的目的（一直定位/当用户使用时定位）。然后在程序的 <code>Build Phases</code> 的 <code>Link Binary With Libraries</code> 导入 <code>CoreLocation.framework</code> 就可以了。</p>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;如何获取地理位置信息&quot;&gt;&lt;a href=&quot;#如何获取地理位置信息&quot; class=&quot;headerlink&quot; title=&quot;如何获取地理位置信息&quot;&gt;&lt;/a&gt;如何获取地理位置信息&lt;/h1&gt;&lt;p&gt;OpenRTB 中有一个 &lt;code&gt;geo&lt;/code&gt; 字段，该字段对应一个 &lt;code&gt;geo&lt;/code&gt; 对象，这个对象中有好多字段，这里只讨论两个关键的字段 &lt;code&gt;lat&lt;/code&gt; 和 &lt;code&gt;lon&lt;/code&gt; 字段的获取。其中 &lt;code&gt;lat&lt;/code&gt; 表示纬度，&lt;code&gt;lon&lt;/code&gt; 表示经度。&lt;/p&gt;
    
    </summary>
    
      <category term="广告" scheme="https://coolcode.org/categories/%E5%B9%BF%E5%91%8A/"/>
    
    
      <category term="广告" scheme="https://coolcode.org/tags/%E5%B9%BF%E5%91%8A/"/>
    
      <category term="Android" scheme="https://coolcode.org/tags/Android/"/>
    
      <category term="iOS" scheme="https://coolcode.org/tags/iOS/"/>
    
  </entry>
  
  <entry>
    <title>媒体广告变现优化之道（六）</title>
    <link href="https://coolcode.org/2020/04/09/ad-monetization-optimization-6/"/>
    <id>https://coolcode.org/2020/04/09/ad-monetization-optimization-6/</id>
    <published>2020-04-09T11:32:00.000Z</published>
    <updated>2020-03-09T11:32:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="如何获取网络连接类型"><a href="#如何获取网络连接类型" class="headerlink" title="如何获取网络连接类型"></a>如何获取网络连接类型</h1><p>OpenRTB 中还有一个 <code>connectiontype</code> 字段，在 OpenRTB 3.0 里面叫 <code>contype</code>，其实表示的都是网络连接类型，其中包含有 Ethernet，WIFI，2G，3G，4G，5G 和未知移动网络类型。下面我们就来看看如何在 Android 和 iOS 中如何获取网络连接类型。</p><a id="more"></a><h2 id="获取-Android-设备的-ConnectionType"><a href="#获取-Android-设备的-ConnectionType" class="headerlink" title="获取 Android 设备的 ConnectionType"></a>获取 Android 设备的 <code>ConnectionType</code></h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">getConnectionType</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    String connectionType = <span class="string">""</span>;</span><br><span class="line">    NetworkInfo networkInfo = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();</span><br><span class="line">    <span class="keyword">if</span> (networkInfo != <span class="keyword">null</span> &amp;&amp; networkInfo.isConnected()) &#123;</span><br><span class="line">        <span class="keyword">int</span> type = networkInfo.getType();</span><br><span class="line">        <span class="keyword">if</span> (type == ConnectivityManager.TYPE_WIFI) &#123;</span><br><span class="line">            connectionType = <span class="string">"wifi"</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (type == ConnectivityManager.TYPE_ETHERNET) &#123;</span><br><span class="line">            connectionType = <span class="string">"ethernet"</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (type == ConnectivityManager.TYPE_MOBILE) &#123;</span><br><span class="line">            String subTypeName = networkInfo.getSubtypeName();</span><br><span class="line">            <span class="keyword">int</span> networkType = networkInfo.getSubtype();</span><br><span class="line">            <span class="keyword">switch</span> (networkType) &#123;</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_GPRS:</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_EDGE:</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_CDMA:</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_1xRTT:</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_IDEN:</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_GSM:</span><br><span class="line">                    connectionType = <span class="string">"2g"</span>;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_UMTS:</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_EVDO_0:</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_EVDO_A:</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_HSDPA:</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_HSUPA:</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_HSPA:</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_EVDO_B:</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_EHRPD:</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_HSPAP:</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_TD_SCDMA:</span><br><span class="line">                    connectionType = <span class="string">"3g"</span>;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_LTE:</span><br><span class="line">                <span class="keyword">case</span> TelephonyManager.NETWORK_TYPE_IWLAN:</span><br><span class="line">                <span class="keyword">case</span> <span class="number">19</span>: <span class="comment">// TelephonyManager.NETWORK_TYPE_LTE_CA</span></span><br><span class="line">                    connectionType = <span class="string">"4g"</span>;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                <span class="keyword">default</span>:</span><br><span class="line">                    <span class="keyword">if</span> (subTypeName.equalsIgnoreCase(<span class="string">"TD-SCDMA"</span>) ||</span><br><span class="line">                            subTypeName.equalsIgnoreCase(<span class="string">"WCDMA"</span>) ||</span><br><span class="line">                            subTypeName.equalsIgnoreCase(<span class="string">"CDMA2000"</span>)) &#123;</span><br><span class="line">                        connectionType = <span class="string">"3g"</span>;</span><br><span class="line">                    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (subTypeName.equalsIgnoreCase(<span class="string">"NR"</span>)) &#123;</span><br><span class="line">                        connectionType = <span class="string">"5g"</span>;</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        connectionType = <span class="string">"cell_unknown"</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> connectionType;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="获取-iOS-设备的-ConnectionType"><a href="#获取-iOS-设备的-ConnectionType" class="headerlink" title="获取 iOS 设备的 ConnectionType"></a>获取 iOS 设备的 <code>ConnectionType</code></h2><figure class="highlight objc"><table><tr><td class="code"><pre><span class="line">+ (<span class="keyword">const</span> <span class="built_in">NSString</span> *)connectionType &#123;</span><br><span class="line">    <span class="keyword">struct</span> sockaddr_in zeroAddress;</span><br><span class="line">    bzero(&amp;zeroAddress, <span class="keyword">sizeof</span>(zeroAddress));</span><br><span class="line">    zeroAddress.sin_len = <span class="keyword">sizeof</span>(zeroAddress);</span><br><span class="line">    zeroAddress.sin_family = AF_INET;</span><br><span class="line">    <span class="built_in">SCNetworkReachabilityRef</span> defaultRouteReachability = <span class="built_in">SCNetworkReachabilityCreateWithAddress</span>(<span class="literal">NULL</span>, (<span class="keyword">struct</span> sockaddr *)&amp;zeroAddress);</span><br><span class="line">    <span class="built_in">SCNetworkReachabilityFlags</span> flags;</span><br><span class="line">    <span class="built_in">SCNetworkReachabilityGetFlags</span>(defaultRouteReachability, &amp;flags);</span><br><span class="line">    <span class="built_in">CFRelease</span>(defaultRouteReachability);</span><br><span class="line">    <span class="keyword">if</span> ((flags &amp; kSCNetworkReachabilityFlagsReachable) == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">@""</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> ((flags &amp; kSCNetworkReachabilityFlagsConnectionRequired) == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">@"wifi"</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (((flags &amp; kSCNetworkReachabilityFlagsConnectionOnDemand) != <span class="number">0</span>) ||</span><br><span class="line">        (flags &amp; kSCNetworkReachabilityFlagsConnectionOnTraffic) != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> ((flags &amp; kSCNetworkReachabilityFlagsInterventionRequired) == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">@"wifi"</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> ((flags &amp; kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN) &#123;</span><br><span class="line">        <span class="built_in">CTTelephonyNetworkInfo</span> *info = [<span class="built_in">CTTelephonyNetworkInfo</span> new];</span><br><span class="line">        <span class="keyword">if</span> (info) &#123;</span><br><span class="line">            <span class="built_in">NSString</span> *currentStatus = <span class="literal">nil</span>;</span><br><span class="line">            <span class="keyword">if</span> ([<span class="built_in">UIDevice</span> currentDevice].systemVersion.floatValue &gt;= <span class="number">12.1</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> ([info respondsToSelector:<span class="keyword">@selector</span>(serviceCurrentRadioAccessTechnology)]) &#123;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> clang diagnostic push</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> clang diagnostic ignored <span class="meta-string">"-Wunsupported-availability-guard"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> clang diagnostic ignored <span class="meta-string">"-Wunguarded-availability"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> clang diagnostic ignored <span class="meta-string">"-Wunguarded-availability-new"</span></span></span><br><span class="line">                    <span class="built_in">NSDictionary</span> *dict = [info serviceCurrentRadioAccessTechnology];</span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> clang diagnostic pop</span></span><br><span class="line">                    <span class="keyword">if</span> (dict.count) &#123;</span><br><span class="line">                        currentStatus = [dict objectForKey:dict.allKeys[<span class="number">0</span>]];</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> ([info respondsToSelector:<span class="keyword">@selector</span>(currentRadioAccessTechnology)]) &#123;</span><br><span class="line">                    currentStatus = [info currentRadioAccessTechnology];</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="built_in">NSArray</span> *network2G = @[<span class="built_in">CTRadioAccessTechnologyEdge</span>,</span><br><span class="line">                                   <span class="built_in">CTRadioAccessTechnologyGPRS</span>,</span><br><span class="line">                                   <span class="built_in">CTRadioAccessTechnologyCDMA1x</span>];</span><br><span class="line">            <span class="built_in">NSArray</span> *network3G = @[<span class="built_in">CTRadioAccessTechnologyWCDMA</span>,</span><br><span class="line">                                   <span class="built_in">CTRadioAccessTechnologyHSDPA</span>,</span><br><span class="line">                                   <span class="built_in">CTRadioAccessTechnologyHSUPA</span>,</span><br><span class="line">                                   <span class="built_in">CTRadioAccessTechnologyCDMAEVDORev0</span>,</span><br><span class="line">                                   <span class="built_in">CTRadioAccessTechnologyCDMAEVDORevA</span>,</span><br><span class="line">                                   <span class="built_in">CTRadioAccessTechnologyCDMAEVDORevB</span>,</span><br><span class="line">                                   <span class="built_in">CTRadioAccessTechnologyeHRPD</span>];</span><br><span class="line">            <span class="built_in">NSArray</span> *network4G = @[<span class="built_in">CTRadioAccessTechnologyLTE</span>];</span><br><span class="line">            <span class="keyword">if</span> ([network2G containsObject:currentStatus]) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="string">@"2g"</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> ([network3G containsObject:currentStatus]) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="string">@"3g"</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> ([network4G containsObject:currentStatus])&#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="string">@"4g"</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">@"cell_unknown"</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">@""</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的代码已经说明了一切，就不做解释了。</p>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;如何获取网络连接类型&quot;&gt;&lt;a href=&quot;#如何获取网络连接类型&quot; class=&quot;headerlink&quot; title=&quot;如何获取网络连接类型&quot;&gt;&lt;/a&gt;如何获取网络连接类型&lt;/h1&gt;&lt;p&gt;OpenRTB 中还有一个 &lt;code&gt;connectiontype&lt;/code&gt; 字段，在 OpenRTB 3.0 里面叫 &lt;code&gt;contype&lt;/code&gt;，其实表示的都是网络连接类型，其中包含有 Ethernet，WIFI，2G，3G，4G，5G 和未知移动网络类型。下面我们就来看看如何在 Android 和 iOS 中如何获取网络连接类型。&lt;/p&gt;
    
    </summary>
    
      <category term="广告" scheme="https://coolcode.org/categories/%E5%B9%BF%E5%91%8A/"/>
    
    
      <category term="广告" scheme="https://coolcode.org/tags/%E5%B9%BF%E5%91%8A/"/>
    
      <category term="Android" scheme="https://coolcode.org/tags/Android/"/>
    
      <category term="iOS" scheme="https://coolcode.org/tags/iOS/"/>
    
  </entry>
  
  <entry>
    <title>媒体广告变现优化之道（五）</title>
    <link href="https://coolcode.org/2020/03/11/ad-monetization-optimization-5/"/>
    <id>https://coolcode.org/2020/03/11/ad-monetization-optimization-5/</id>
    <published>2020-03-11T07:50:00.000Z</published>
    <updated>2020-03-11T11:31:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="如何获取网络运营商信息"><a href="#如何获取网络运营商信息" class="headerlink" title="如何获取网络运营商信息"></a>如何获取网络运营商信息</h1><p>今天我们来聊聊获取网络运营商信息。</p><p>在 OpenRTB 中，关于网络运营商的信息有三个属性：<code>carrier</code>, <code>mccmnc</code> 和 <code>mccmncsim</code>。</p><p>其中 <code>carrier</code> 是运营商的字符串名称，比如『中国移动』，『中国联通』，当然，用英文或者用数字表示都可以，其取值只要通讯双方规定好就可以了。</p><p><code>mccmnc</code> 是 <code>MCC</code>（移动国家码 Mobile Country Code）和 <code>MNC</code>（移动网络码 Mobile Network Code）的组合，格式为 <code>MCC-MNC</code>。</p><p><code>MCC</code> 的资源由国际电联（ITU）统一分配和管理，唯一识别移动用户所属的国家，共 3 位，中国为 <code>460</code>。</p><p><code>MNC</code> 有 2 位的 也有 3 位的，在中国使用的是 2 位。例如中国移动 TD 系统使用 <code>00</code>，中国联通 GSM 系统使用 <code>01</code>，中国移动 GSM 系统使用 <code>02</code>，中国电信 CDMA 系统使用 <code>03</code> 等。</p><p><code>mccmncsim</code> 是 SIM 卡的 <code>MCC</code> 和 <code>MNC</code>，格式跟 <code>mccmnc</code> 一样。如果两个值都有效，当这两个值不同时，说明用户正处于漫游中。</p><p>另外，我们通常还会使用 <code>PLMN</code>（Public Land Mobile Network）或 <code>HNI</code>（Home network identity）来表示 <code>MCC</code> 和 <code>MNC</code> 的组合，但是在格式上，<code>MCC</code> 和 <code>MNC</code> 中间没有 <code>-</code> 作为分割。</p><p>下面的代码我们统一使用 <code>PLMN</code> 来做说明。</p><a id="more"></a><h2 id="获取-Android-设备的-PLMN"><a href="#获取-Android-设备的-PLMN" class="headerlink" title="获取 Android 设备的 PLMN"></a>获取 Android 设备的 <code>PLMN</code></h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> String <span class="title">getPLMN</span><span class="params">(Context context)</span> </span>&#123;</span><br><span class="line">    TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);</span><br><span class="line">    <span class="keyword">if</span> (telephonyManager != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> telephonyManager.getSimOperator();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">""</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过 <code>getSimOperator</code> 这个方法可以直接获取 <code>PLMN</code>。但是 Android 设备不一定都是手机，所以这里需要判断一下 <code>telephonyManager</code> 是否为空。</p><h2 id="获取-iOS-设备的-PLMN"><a href="#获取-iOS-设备的-PLMN" class="headerlink" title="获取 iOS 设备的 PLMN"></a>获取 iOS 设备的 <code>PLMN</code></h2><figure class="highlight objc"><table><tr><td class="code"><pre><span class="line">+ (<span class="keyword">const</span> <span class="built_in">NSString</span> *)plmn &#123;</span><br><span class="line">    <span class="built_in">CTTelephonyNetworkInfo</span> *info = [<span class="built_in">CTTelephonyNetworkInfo</span> new];</span><br><span class="line">    <span class="keyword">if</span> (info == <span class="literal">nil</span>) <span class="keyword">return</span> <span class="string">@""</span>;</span><br><span class="line">    <span class="built_in">CTCarrier</span> * carrier = <span class="literal">nil</span>;</span><br><span class="line">    <span class="comment">// iOS 12.0 有 bug，这个方法虽然文档中说在 iOS 12.0 中有效，实际上并不能用，在 iOS 12.1 中才修复。</span></span><br><span class="line">    <span class="comment">// 不用 @available 判断是因为 XCode 新版本编译的库在 XCode 旧版本上不支持。</span></span><br><span class="line">    <span class="keyword">if</span> ([<span class="built_in">UIDevice</span> currentDevice].systemVersion.floatValue &gt;= <span class="number">12.1</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> ([info respondsToSelector:<span class="keyword">@selector</span>(serviceSubscriberCellularProviders)]) &#123;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> clang diagnostic push</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> clang diagnostic ignored <span class="meta-string">"-Wunsupported-availability-guard"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> clang diagnostic ignored <span class="meta-string">"-Wunguarded-availability"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> clang diagnostic ignored <span class="meta-string">"-Wunguarded-availability-new"</span></span></span><br><span class="line">            <span class="built_in">NSDictionary</span> *dict = [info serviceSubscriberCellularProviders];</span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> clang diagnostic pop</span></span><br><span class="line">            <span class="keyword">if</span> (dict.count &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                carrier = [dict objectForKey:dict.allKeys[<span class="number">0</span>]];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> ([info respondsToSelector:<span class="keyword">@selector</span>(subscriberCellularProvider)]) &#123;</span><br><span class="line">            carrier = [info subscriberCellularProvider];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (carrier) &#123;</span><br><span class="line">        <span class="built_in">NSString</span> *mcc = [carrier mobileCountryCode];</span><br><span class="line">        <span class="built_in">NSString</span> *mnc = [carrier mobileNetworkCode];</span><br><span class="line">        <span class="keyword">if</span> (mcc != <span class="literal">nil</span> &amp;&amp; mnc != <span class="literal">nil</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> [mcc stringByAppendingString: mnc];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">@""</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>iOS 上，代码看上去虽然有些复杂，但原理上很简单，首先获取 <code>CTCarrier</code> 对象，然后通过该对象上的 <code>mobileCountryCode</code> 和 <code>mobileNetworkCode</code> 属性来分别获取 <code>MCC</code> 和 <code>MNC</code>，最后将它们组合为 <code>PLMN</code> 返回。</p><p><code>CTCarrier</code> 对象在新版本的 iOS 上跟旧版本的获取方式稍有不同，因为新的 iOS 版本增加了对 iPhone 的双卡支持。旧的方法 <code>subscriberCellularProvider</code> 在 iOS 12 之后已经被标记为 <code>Deprecated</code>，所以上面的代码中，做了一下 iOS 版本判断。不过即使采用新的方法，获取到的也不一定是 iPhone 的主卡。</p><p>如果非要获取主卡，也不是没有办法，可以通过获取状态栏的运营商信息来做匹配，但是通过状态栏获取运营商信息的方式太复杂了，在 iOS 13 之后和之前的方式不一样，刘海屏和非刘海屏也有区别，这里就不贴代码了。</p><h2 id="获取运营商的名称"><a href="#获取运营商的名称" class="headerlink" title="获取运营商的名称"></a>获取运营商的名称</h2><p>获取网络运营商名称可以通过跟上面类似的方式来直接获取，也可以根据上面获取到的 PLMN 来查表获取。因此通常只需要获取 PLMN 就可以了，然后在服务器端通过查表的方式来转换成对应的网络运营商名称。</p>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;如何获取网络运营商信息&quot;&gt;&lt;a href=&quot;#如何获取网络运营商信息&quot; class=&quot;headerlink&quot; title=&quot;如何获取网络运营商信息&quot;&gt;&lt;/a&gt;如何获取网络运营商信息&lt;/h1&gt;&lt;p&gt;今天我们来聊聊获取网络运营商信息。&lt;/p&gt;
&lt;p&gt;在 OpenRTB 中，关于网络运营商的信息有三个属性：&lt;code&gt;carrier&lt;/code&gt;, &lt;code&gt;mccmnc&lt;/code&gt; 和 &lt;code&gt;mccmncsim&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;其中 &lt;code&gt;carrier&lt;/code&gt; 是运营商的字符串名称，比如『中国移动』，『中国联通』，当然，用英文或者用数字表示都可以，其取值只要通讯双方规定好就可以了。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mccmnc&lt;/code&gt; 是 &lt;code&gt;MCC&lt;/code&gt;（移动国家码 Mobile Country Code）和 &lt;code&gt;MNC&lt;/code&gt;（移动网络码 Mobile Network Code）的组合，格式为 &lt;code&gt;MCC-MNC&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MCC&lt;/code&gt; 的资源由国际电联（ITU）统一分配和管理，唯一识别移动用户所属的国家，共 3 位，中国为 &lt;code&gt;460&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MNC&lt;/code&gt; 有 2 位的 也有 3 位的，在中国使用的是 2 位。例如中国移动 TD 系统使用 &lt;code&gt;00&lt;/code&gt;，中国联通 GSM 系统使用 &lt;code&gt;01&lt;/code&gt;，中国移动 GSM 系统使用 &lt;code&gt;02&lt;/code&gt;，中国电信 CDMA 系统使用 &lt;code&gt;03&lt;/code&gt; 等。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mccmncsim&lt;/code&gt; 是 SIM 卡的 &lt;code&gt;MCC&lt;/code&gt; 和 &lt;code&gt;MNC&lt;/code&gt;，格式跟 &lt;code&gt;mccmnc&lt;/code&gt; 一样。如果两个值都有效，当这两个值不同时，说明用户正处于漫游中。&lt;/p&gt;
&lt;p&gt;另外，我们通常还会使用 &lt;code&gt;PLMN&lt;/code&gt;（Public Land Mobile Network）或 &lt;code&gt;HNI&lt;/code&gt;（Home network identity）来表示 &lt;code&gt;MCC&lt;/code&gt; 和 &lt;code&gt;MNC&lt;/code&gt; 的组合，但是在格式上，&lt;code&gt;MCC&lt;/code&gt; 和 &lt;code&gt;MNC&lt;/code&gt; 中间没有 &lt;code&gt;-&lt;/code&gt; 作为分割。&lt;/p&gt;
&lt;p&gt;下面的代码我们统一使用 &lt;code&gt;PLMN&lt;/code&gt; 来做说明。&lt;/p&gt;
    
    </summary>
    
      <category term="广告" scheme="https://coolcode.org/categories/%E5%B9%BF%E5%91%8A/"/>
    
    
      <category term="广告" scheme="https://coolcode.org/tags/%E5%B9%BF%E5%91%8A/"/>
    
      <category term="Android" scheme="https://coolcode.org/tags/Android/"/>
    
      <category term="iOS" scheme="https://coolcode.org/tags/iOS/"/>
    
  </entry>
  
  <entry>
    <title>媒体广告变现优化之道（四）</title>
    <link href="https://coolcode.org/2020/03/06/ad-monetization-optimization-4/"/>
    <id>https://coolcode.org/2020/03/06/ad-monetization-optimization-4/</id>
    <published>2020-03-06T07:57:00.000Z</published>
    <updated>2020-03-06T09:54:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="如何获取设备屏幕信息"><a href="#如何获取设备屏幕信息" class="headerlink" title="如何获取设备屏幕信息"></a>如何获取设备屏幕信息</h1><p>今天我们来聊聊获取设备屏幕的分辨率，DPI，屏幕尺寸，缩放因子等信息。</p><p>在 OpenRTB 中，关于设备屏幕的信息有以下四个属性：</p><table><thead><tr><th>Attribute</th><th>Type</th><th>Definition</th></tr></thead><tbody><tr><td>h</td><td>integer</td><td>Physical height of the screen in pixels.</td></tr><tr><td>w</td><td>integer</td><td>Physical width of the screen in pixels.</td></tr><tr><td>ppi</td><td>integer</td><td>Screen size as pixels per linear inch.</td></tr><tr><td>pxratio</td><td>float</td><td>The ratio of physical pixels to device independent pixels.</td></tr></tbody></table><p>其中 <code>w</code>, <code>h</code> 对应屏幕物理宽高的像素值。</p><p><code>ppi</code> 是每英寸的像素个数，不过对于 Android 设备，通常它又被称作 dpi（Dots Per Inch，每英寸点数）。</p><p><code>pxratio</code> 是物理像素与设备无关像素的比率，也被称为缩放因子（scale factor）。</p><p>下面我们来就 Android 和 iOS 设备来分别讨论如何获取这些信息。</p><a id="more"></a><h2 id="获取-Android-设备的屏幕信息"><a href="#获取-Android-设备的屏幕信息" class="headerlink" title="获取 Android 设备的屏幕信息"></a>获取 Android 设备的屏幕信息</h2><p>对于 Android 设备来说，这些信息都可以通过 <code>DisplayMetrics</code> 类来获取。所有首先要获取一个有效的 <code>DisplayMetrics</code> 对象。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> DisplayMetrics <span class="title">getDisplayMetrics</span><span class="params">(Context context)</span> </span>&#123;</span><br><span class="line">    WindowManager wm = (WindowManager) (context.getSystemService(Context.WINDOW_SERVICE));</span><br><span class="line">    DisplayMetrics dm = <span class="keyword">new</span> DisplayMetrics();</span><br><span class="line">    Display display = wm.getDefaultDisplay();</span><br><span class="line">    <span class="keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.JELLY_BEAN_MR1) &#123;</span><br><span class="line">        display.getRealMetrics(dm);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        display.getMetrics(dm);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> dm;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>getRealMetrics</code> 这个方法是在 Android 4.2 之后增加的，跟 <code>getMetrics</code> 相比，通过 <code>getRealMetrics</code> 获取到的屏幕分辨率是屏幕的真正分辨率，而 <code>getMetrics</code> 获取到的屏幕分辨率是去掉了屏幕底部虚拟按键高度的分辨率。在这之前的设备可能也不支持虚拟按键，所以，也就没有这个区分。</p><p>有了 <code>DisplayMetrics</code> 对象之后，我们就可以通过它来获取上面四个属性了，通常我们会这样来写：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">DisplayMetrics dm = getDisplayMetrics(context);</span><br><span class="line"><span class="keyword">int</span> w = dm.widthPixels;</span><br><span class="line"><span class="keyword">int</span> h = dm.heightPixels;</span><br><span class="line"><span class="keyword">int</span> dpi = dm.densityDpi;</span><br><span class="line"><span class="keyword">float</span> pxratio = dm.density;</span><br></pre></td></tr></table></figure><p>但是这样写真的对吗？对于 <code>w</code>, <code>h</code> 和 <code>pxratio</code> 这三个的值来说，没什么问题，但是对于 <code>dpi</code> 来说，通过这种方式获取到的值其实是不对的。</p><p>在 Android 系统中，<code>density</code> 和 <code>densityDpi</code> 之间其实有一个固定的关系：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">densityDpi == (<span class="keyword">int</span>)(density * <span class="number">160</span>)</span><br></pre></td></tr></table></figure><p>所以，它俩相当于同一个概念的两种不同表示方法。而我们实际要获取的 <code>dpi</code> 应该按照如下方式来获取：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">int</span> w = dm.widthPixels;</span><br><span class="line"><span class="keyword">int</span> h = dm.heightPixels;</span><br><span class="line"><span class="keyword">float</span> width = w / dm.xdpi;</span><br><span class="line"><span class="keyword">float</span> height = h / dm.ydpi;</span><br><span class="line"><span class="keyword">float</span> size = Math.sqrt(width * width + height * height);</span><br><span class="line"><span class="keyword">int</span> ppi = (<span class="keyword">int</span>) (Math.sqrt(w * w + h * h) / size);</span><br></pre></td></tr></table></figure><p>上面的计算中，<code>width</code> 是水平英寸数，<code>height</code> 是竖直英寸数，<code>size</code> 是对角线的长度，也就是通常我们说的手机尺寸。</p><p>然后通过对角线的像素个数除以手机尺寸，获得的才是 OpenRTB 中所说的 <code>ppi</code>。</p><h2 id="获取-iOS-设备的屏幕信息"><a href="#获取-iOS-设备的屏幕信息" class="headerlink" title="获取 iOS 设备的屏幕信息"></a>获取 iOS 设备的屏幕信息</h2><p>对于 iOS 系统来说，<code>w</code>, <code>h</code> 和 <code>pxratio</code> 这三个值跟 Android 一样，可以比较方便的直接获取。</p><figure class="highlight objc"><table><tr><td class="code"><pre><span class="line"><span class="built_in">CGSize</span> size = [<span class="built_in">UIScreen</span> mainScreen].nativeBounds.size;</span><br><span class="line"><span class="keyword">int</span> w = (<span class="keyword">int</span>)size.width;</span><br><span class="line"><span class="keyword">int</span> h = (<span class="keyword">int</span>)size.height;</span><br><span class="line"><span class="built_in">CGFloat</span> pxratio = [<span class="built_in">UIScreen</span> mainScreen].nativeScale;</span><br></pre></td></tr></table></figure><p>但需要注意，不要用 <code>bounds</code> 和 <code>scale</code> 这两个代替上面的 <code>nativeBounds</code> 和 <code>nativeScale</code>。</p><p>因为 <code>bounds</code> 获取到的是设备无关像素（在 iOS 中被称为 <code>Point</code>）的分辨率。而 <code>scale</code> 是渲染像素（<code>Rendered Pixels</code>）跟设备无关像素（<code>Point</code>）的比值。</p><p>在 iOS 上获取 <code>ppi</code> 就没有上面那么幸运了，因为 iOS 没有提供直接获取 <code>ppi</code> 的方法。但因为我们前面讲过 iOS 设备可以获取设备型号，所以我们可以通过做一张设备型号与 <code>ppi</code> 的对照表来返回对应设备的 <code>ppi</code>，下面是到目前为止所有 iOS 设备的型号与 <code>ppi</code> 对照表：</p><figure class="highlight objc"><table><tr><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> <span class="built_in">NSDictionary</span> *modelPPIs;</span><br><span class="line"></span><br><span class="line">+ (<span class="keyword">void</span>)load &#123;</span><br><span class="line">    modelPPIs = @&#123;</span><br><span class="line">        <span class="comment">// iPhone 1</span></span><br><span class="line">        <span class="string">@"iPhone1,1"</span>: @<span class="number">163</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 3G</span></span><br><span class="line">        <span class="string">@"iPhone1,2"</span>: @<span class="number">163</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 3GS</span></span><br><span class="line">        <span class="string">@"iPhone2,1"</span>: @<span class="number">163</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 4</span></span><br><span class="line">        <span class="string">@"iPhone3,1"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 4 GSM Rev A</span></span><br><span class="line">        <span class="string">@"iPhone3,2"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 4 CDMA</span></span><br><span class="line">        <span class="string">@"iPhone3,3"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 4S</span></span><br><span class="line">        <span class="string">@"iPhone4,1"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 5 (GSM)</span></span><br><span class="line">        <span class="string">@"iPhone5,1"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 5 (GSM+CDMA)</span></span><br><span class="line">        <span class="string">@"iPhone5,2"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 5C (GSM)</span></span><br><span class="line">        <span class="string">@"iPhone5,3"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 5C (Global)</span></span><br><span class="line">        <span class="string">@"iPhone5,4"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 5S (GSM)</span></span><br><span class="line">        <span class="string">@"iPhone6,1"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 5S (Global)</span></span><br><span class="line">        <span class="string">@"iPhone6,2"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 6 Plus</span></span><br><span class="line">        <span class="string">@"iPhone7,1"</span>: @<span class="number">401</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 6</span></span><br><span class="line">        <span class="string">@"iPhone7,2"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 6s</span></span><br><span class="line">        <span class="string">@"iPhone8,1"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 6s Plus</span></span><br><span class="line">        <span class="string">@"iPhone8,2"</span>: @<span class="number">401</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone SE (GSM+CDMA)</span></span><br><span class="line">        <span class="string">@"iPhone8,3"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone SE (GSM)</span></span><br><span class="line">        <span class="string">@"iPhone8,4"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 7</span></span><br><span class="line">        <span class="string">@"iPhone9,1"</span>: @<span class="number">326</span>,</span><br><span class="line">        <span class="string">@"iPhone9,3"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 7 Plus</span></span><br><span class="line">        <span class="string">@"iPhone9,2"</span>: @<span class="number">401</span>,</span><br><span class="line">        <span class="string">@"iPhone9,4"</span>: @<span class="number">401</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 8</span></span><br><span class="line">        <span class="string">@"iPhone10,1"</span>: @<span class="number">326</span>,</span><br><span class="line">        <span class="string">@"iPhone10,4"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 8 Plus</span></span><br><span class="line">        <span class="string">@"iPhone10,2"</span>: @<span class="number">401</span>,</span><br><span class="line">        <span class="string">@"iPhone10,5"</span>: @<span class="number">401</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone X Global</span></span><br><span class="line">        <span class="string">@"iPhone10,3"</span>: @<span class="number">458</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone X GSM</span></span><br><span class="line">        <span class="string">@"iPhone10,6"</span>: @<span class="number">458</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone XS</span></span><br><span class="line">        <span class="string">@"iPhone11,2"</span>: @<span class="number">458</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone XS Max China</span></span><br><span class="line">        <span class="string">@"iPhone11,4"</span>: @<span class="number">458</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone XS Max</span></span><br><span class="line">        <span class="string">@"iPhone11,6"</span>: @<span class="number">458</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone XR</span></span><br><span class="line">        <span class="string">@"iPhone11,8"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 11</span></span><br><span class="line">        <span class="string">@"iPhone12,1"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 11 Pro</span></span><br><span class="line">        <span class="string">@"iPhone12,3"</span>: @<span class="number">458</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPhone 11 Pro Max</span></span><br><span class="line">        <span class="string">@"iPhone12,5"</span>: @<span class="number">458</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad 1</span></span><br><span class="line">        <span class="string">@"iPad1,1"</span>: @<span class="number">132</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad 3G</span></span><br><span class="line">        <span class="string">@"iPad1,2"</span>: @<span class="number">132</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad 2nd Gen</span></span><br><span class="line">        <span class="string">@"iPad2,1"</span>: @<span class="number">132</span>,</span><br><span class="line">        <span class="string">@"iPad2,2"</span>: @<span class="number">132</span>,</span><br><span class="line">        <span class="string">@"iPad2,3"</span>: @<span class="number">132</span>,</span><br><span class="line">        <span class="string">@"iPad2,4"</span>: @<span class="number">132</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad Mini</span></span><br><span class="line">        <span class="string">@"iPad2,5"</span>: @<span class="number">163</span>,</span><br><span class="line">        <span class="string">@"iPad2,6"</span>: @<span class="number">163</span>,</span><br><span class="line">        <span class="string">@"iPad2,7"</span>: @<span class="number">163</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad 3rd Gen</span></span><br><span class="line">        <span class="string">@"iPad3,1"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad3,2"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad3,3"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad 4th Gen</span></span><br><span class="line">        <span class="string">@"iPad3,4"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad3,5"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad3,6"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad Air 1</span></span><br><span class="line">        <span class="string">@"iPad4,1"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad4,2"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad4,3"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad Mini 2</span></span><br><span class="line">        <span class="string">@"iPad4,4"</span>: @<span class="number">326</span>,</span><br><span class="line">        <span class="string">@"iPad4,5"</span>: @<span class="number">326</span>,</span><br><span class="line">        <span class="string">@"iPad4,6"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad Mini 3</span></span><br><span class="line">        <span class="string">@"iPad4,7"</span>: @<span class="number">326</span>,</span><br><span class="line">        <span class="string">@"iPad4,8"</span>: @<span class="number">326</span>,</span><br><span class="line">        <span class="string">@"iPad4,9"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad Mini 4</span></span><br><span class="line">        <span class="string">@"iPad5,1"</span>: @<span class="number">326</span>,</span><br><span class="line">        <span class="string">@"iPad5,2"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad Air 2</span></span><br><span class="line">        <span class="string">@"iPad5,3"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad5,4"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad Pro 9.7-inch</span></span><br><span class="line">        <span class="string">@"iPad6,3"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad6,4"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad Pro 12.9-inch</span></span><br><span class="line">        <span class="string">@"iPad6,7"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad6,8"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad 5th Gen, 2017</span></span><br><span class="line">        <span class="string">@"iPad6,11"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad6,12"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad Pro 12.9-inch, 2017</span></span><br><span class="line">        <span class="string">@"iPad7,1"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad7,2"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad Pro 10.5-inch, 2017</span></span><br><span class="line">        <span class="string">@"iPad7,3"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad7,4"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad 6th Gen, 2018</span></span><br><span class="line">        <span class="string">@"iPad7,5"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad7,6"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad 7th Gen, 2019</span></span><br><span class="line">        <span class="string">@"iPad7,11"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad7,12"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad Pro 3rd Gen 11-inch, 2018</span></span><br><span class="line">        <span class="string">@"iPad8,1"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad8,3"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad Pro 3rd Gen 11-inch 1TB, 2018</span></span><br><span class="line">        <span class="string">@"iPad8,2"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad8,4"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad Pro 3rd Gen 12.9-inch, 2018</span></span><br><span class="line">        <span class="string">@"iPad8,5"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad8,7"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad Pro 3rd Gen 12.9-inch 1TB, 2018</span></span><br><span class="line">        <span class="string">@"iPad8,6"</span>: @<span class="number">264</span>,</span><br><span class="line">        <span class="string">@"iPad8,8"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad mini 5th Gen (WiFi)</span></span><br><span class="line">        <span class="string">@"iPad11,1"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad mini 5th Gen</span></span><br><span class="line">        <span class="string">@"iPad11,2"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad Air 3rd Gen (WiFi)</span></span><br><span class="line">        <span class="string">@"iPad11,3"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPad Air 3rd Gen</span></span><br><span class="line">        <span class="string">@"iPad11,4"</span>: @<span class="number">264</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPod Touch 1</span></span><br><span class="line">        <span class="string">@"iPod1,1"</span>: @<span class="number">163</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPod Touch 2</span></span><br><span class="line">        <span class="string">@"iPod2,1"</span>: @<span class="number">163</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPod Touch 3</span></span><br><span class="line">        <span class="string">@"iPod3,1"</span>: @<span class="number">163</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPod Touch 4</span></span><br><span class="line">        <span class="string">@"iPod4,1"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPod Touch 5</span></span><br><span class="line">        <span class="string">@"iPod5,1"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPod Touch 6</span></span><br><span class="line">        <span class="string">@"iPod7,1"</span>: @<span class="number">326</span>,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// iPod Touch 7</span></span><br><span class="line">        <span class="string">@"iPod9,1"</span>: @<span class="number">326</span>,</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接下来只要通过我们在<a href="https://coolcode.org/2020/02/17/ad-monetization-optimization-1/">第一篇</a>中介绍的方法先获取设备型号，再来查这个表就可以了。</p><p>不过需要注意，如果是模拟器设备的话，获取到的设备型号是 <code>x86_64</code>，并不在这张表中，所以在查表之前最好先判断一下。</p><p>另外，如果是采用 API 方式来进行广告对接的话，关于 iOS 设备 <code>ppi</code> 的获取，最好是在服务器端通过这种表格的方式来查询，因为如果苹果出了新设备，客户端来不及更新的话，在新设备上就无法获取到正确的 <code>ppi</code>。但是在服务器端是可以做到及时更新生效的。</p>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;如何获取设备屏幕信息&quot;&gt;&lt;a href=&quot;#如何获取设备屏幕信息&quot; class=&quot;headerlink&quot; title=&quot;如何获取设备屏幕信息&quot;&gt;&lt;/a&gt;如何获取设备屏幕信息&lt;/h1&gt;&lt;p&gt;今天我们来聊聊获取设备屏幕的分辨率，DPI，屏幕尺寸，缩放因子等信息。&lt;/p&gt;
&lt;p&gt;在 OpenRTB 中，关于设备屏幕的信息有以下四个属性：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;h&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;Physical height of the screen in pixels.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;w&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;Physical width of the screen in pixels.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ppi&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;Screen size as pixels per linear inch.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pxratio&lt;/td&gt;
&lt;td&gt;float&lt;/td&gt;
&lt;td&gt;The ratio of physical pixels to device independent pixels.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;其中 &lt;code&gt;w&lt;/code&gt;, &lt;code&gt;h&lt;/code&gt; 对应屏幕物理宽高的像素值。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ppi&lt;/code&gt; 是每英寸的像素个数，不过对于 Android 设备，通常它又被称作 dpi（Dots Per Inch，每英寸点数）。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pxratio&lt;/code&gt; 是物理像素与设备无关像素的比率，也被称为缩放因子（scale factor）。&lt;/p&gt;
&lt;p&gt;下面我们来就 Android 和 iOS 设备来分别讨论如何获取这些信息。&lt;/p&gt;
    
    </summary>
    
      <category term="广告" scheme="https://coolcode.org/categories/%E5%B9%BF%E5%91%8A/"/>
    
    
      <category term="广告" scheme="https://coolcode.org/tags/%E5%B9%BF%E5%91%8A/"/>
    
      <category term="Android" scheme="https://coolcode.org/tags/Android/"/>
    
      <category term="iOS" scheme="https://coolcode.org/tags/iOS/"/>
    
  </entry>
  
  <entry>
    <title>媒体广告变现优化之道（三）</title>
    <link href="https://coolcode.org/2020/02/27/ad-monetization-optimization-3/"/>
    <id>https://coolcode.org/2020/02/27/ad-monetization-optimization-3/</id>
    <published>2020-02-27T01:46:00.000Z</published>
    <updated>2020-02-27T13:46:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="如何获取-User-Agent"><a href="#如何获取-User-Agent" class="headerlink" title="如何获取 User Agent"></a>如何获取 User Agent</h1><p>书接上回，前面两篇文章分别讲解了<a href="https://coolcode.org/2020/02/17/ad-monetization-optimization-1/">如何获取 make，brand 和 model</a>和<a href="https://coolcode.org/2020/02/21/ad-monetization-optimization-2/">如何获取移动设备唯一标识</a>。今天我们来讲讲 User Agent 的获取方法。</p><h2 id="User-Agent-是什么"><a href="#User-Agent-是什么" class="headerlink" title="User Agent 是什么"></a>User Agent 是什么</h2><p>User Agent 中文名为用户代理，简称 UA，它是一个特殊的字符串，它其中包含了客户端的设备型号，操作系统及版本、浏览器及版本、浏览器渲染引擎等信息。服务器可以通过它来对客户端进行各种分类统计，因此，对于移动广告来说，它是一个十分重要的请求参数，如果没有提交正确 User Agent，将会严重影响广告的正常充填。</p><p>User Agent 虽然包含了这么多信息，但是它并不需要用户自己来构造。对于浏览器来说，每个浏览器都有自己默认的 User Agent，比如对于 Chrome 来说，它的 User Agent 看上去是下面这个样子：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1</span><br></pre></td></tr></table></figure><p>从上面两个 User Agent 中，我们可以看出第一个是来自 Android 4.0.4 系统的 Chrome 浏览器的，甚至我们还能知道它是来自 Galaxy Nexus 这样一台 Android 设备的。而第二个是来自 iPhone 系统上的 Chrome 浏览器的，系统的版本号是 10.3。</p><p>也就是说，如果是在网站上投放广告（不管是普通网站，还是面向移动设备的网站）的话，User Agent 都可以自动通过浏览器的请求头部发送，当然也可以通过 JavaScript 脚本来获取 User Agent 并通过单独的请求发送。</p><p>但是对于移动广告来说，广告请求通常是使用系统自带或来自第三方的网络库发送请求的，而网络库在发送请求时，通常不会自带有效的 User Agent 信息。因此，在这种情况下，我们需要自己从 <code>WebView</code> 中来获取一个有效的 User Agent。</p><a id="more"></a><h2 id="Android-系统下的获取方法"><a href="#Android-系统下的获取方法" class="headerlink" title="Android 系统下的获取方法"></a>Android 系统下的获取方法</h2><p>Android 下获取 User Agent 非常简单，不废话，直接先上代码：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">DeviceInfo</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> String ua = <span class="string">""</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Context context = ...</span><br><span class="line">            ua = <span class="keyword">new</span> WebView(context).getSettings().getUserAgentString();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">static</span> String <span class="title">getUserAgent</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> ua;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的代码中，<code>context</code> 获取的方式有很多，而且因为这不是重点，所以代码省略了，请大家自行脑补。</p><h2 id="iOS-系统下的获取方法"><a href="#iOS-系统下的获取方法" class="headerlink" title="iOS 系统下的获取方法"></a>iOS 系统下的获取方法</h2><p>在 iOS 8.0 之前，可以通过 <code>UIWebView</code> 来获取：</p><figure class="highlight objc"><table><tr><td class="code"><pre><span class="line"><span class="built_in">NSString</span>* userAgent = [[<span class="built_in">UIWebView</span> new] stringByEvaluatingJavaScriptFromString:<span class="string">@"navigator.userAgent"</span>];</span><br></pre></td></tr></table></figure><p>但是从 iOS 8.0 开始，<code>UIWebView</code> 已经被标记为过期，不再推荐使用了，因此最好是使用 <code>WKWebView</code> 来获取：</p><figure class="highlight objc"><table><tr><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> <span class="built_in">NSString</span>* userAgent;</span><br><span class="line"></span><br><span class="line">+(<span class="keyword">void</span>)load &#123;</span><br><span class="line">    <span class="built_in">WKWebView</span> *webView = [[<span class="built_in">WKWebView</span> alloc] initWithFrame:<span class="built_in">CGRectZero</span>];</span><br><span class="line">    [webView evaluateJavaScript:<span class="string">@"navigator.userAgent"</span> completionHandler:^(<span class="keyword">id</span> result, <span class="built_in">NSError</span> *error) &#123;</span><br><span class="line">        <span class="keyword">if</span> (error == <span class="literal">nil</span> &amp;&amp; result != <span class="literal">nil</span>) &#123;</span><br><span class="line">            userAgent = [<span class="built_in">NSString</span> stringWithFormat:<span class="string">@"%@"</span>, result];</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            userAgent = <span class="string">@""</span>;</span><br><span class="line">            <span class="built_in">NSLog</span>(<span class="string">@"Can't get the userAgent"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;];</span><br><span class="line">    <span class="keyword">while</span> (userAgent == <span class="literal">nil</span>) &#123;</span><br><span class="line">        [[<span class="built_in">NSRunLoop</span> currentRunLoop] runMode:<span class="built_in">NSDefaultRunLoopMode</span> beforeDate:[<span class="built_in">NSDate</span> distantFuture]];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">+ (<span class="keyword">const</span> <span class="built_in">NSString</span> *)userAgent &#123;</span><br><span class="line">    <span class="keyword">return</span> userAgent;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>因为 <code>WKWebView</code> 的 <code>evaluateJavaScript</code> 方法是异步的，没办法直接得到结果，因此我们这里用 <code>load</code> 方法在 app 加载时就执行的方式，提前缓存 User Agent 的结果。并且为了保证一定可以拿到结果，这里用了 <code>NSRunLoop</code> 来保证拿到 <code>userAgent</code> 之后才会退出 <code>load</code> 方法。</p><p>除了使用 <code>load</code> 方法以外，使用 <code>initialize</code> 方法也可以实现提前加载，只是执行时机靠后一些。</p><p>但如果你所使用的是 Swift 语言，而不是 Objective-C 的话，那就没办法直接使用 <code>load</code> 或 <code>initialize</code> 方法了，因为 Swift 语言中砍掉了这两个方法。但可以使用跟 Objective-C 混编的方式来实现。</p><h2 id="User-Agent-的合法性验证"><a href="#User-Agent-的合法性验证" class="headerlink" title="User Agent 的合法性验证"></a>User Agent 的合法性验证</h2><p>通过 Android 的 <code>WebView</code> 获取到的 User Agent 大概有一下几种形式。</p><h3 id="在-Android-4-4-KitKat-之前"><a href="#在-Android-4-4-KitKat-之前" class="headerlink" title="在 Android 4.4 (KitKat) 之前"></a>在 Android 4.4 (KitKat) 之前</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30</span><br></pre></td></tr></table></figure><h3 id="从-Android-4-4-KitKat-到-Android-5-x（Lollipop）"><a href="#从-Android-4-4-KitKat-到-Android-5-x（Lollipop）" class="headerlink" title="从 Android 4.4 (KitKat) 到 Android 5.x（Lollipop）"></a>从 Android 4.4 (KitKat) 到 Android 5.x（Lollipop）</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Mozilla/5.0 (Linux; Android 4.4.4; vivo Y27 Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/33.0.0.0 Mobile Safari/537.36</span><br></pre></td></tr></table></figure><h3 id="Android-5-x-Lollipop-之后"><a href="#Android-5-x-Lollipop-之后" class="headerlink" title="Android 5.x (Lollipop) 之后"></a>Android 5.x (Lollipop) 之后</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Mozilla/5.0 (Linux; Android 10; ELE-AL00 Build/HUAWEIELE-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.186 Mobile Safari/537.36</span><br></pre></td></tr></table></figure><p>从上面的划分来看，在 Android 4.4 之前的 User Agent 的格式跟 Android 4.4 之后的格式化差别有点大。但是从 Android 4.4 开始，User Agent 就都是以 <code>Mozilla/5.0 (Linux; Android</code> 作为开头的啦，考虑到 Android 4.4 之前的设备基本上已经被淘汰，因此我们可以通过匹配这段文字来对 Android 的 User Agent 的合法性进行简单的验证。</p><p>Android 5.1 之后的 User Agent 主要是增加了 <code>wv</code> 标志，而它其实是跟 <code>Chrome</code> 后面的版本号有关的，如果是 <code>3x.x.x.x</code> 版本的 <code>Chrome</code> 就没有 <code>wv</code>，如果是 <code>4x.x.x.x</code> 或者更高版本的 <code>Chrome</code>，则有 <code>wv</code>。Android 5.x 系统这两种情况都存在，这跟具体设备中内置的 <code>WebView</code> 版本有关，这里不做深究。</p><p>在 iOS 系统中，通过 <code>WebView</code> 获取的 User Agent 有下面几种形式。</p><h3 id="iPhone"><a href="#iPhone" class="headerlink" title="iPhone"></a>iPhone</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148</span><br></pre></td></tr></table></figure><h3 id="iPad"><a href="#iPad" class="headerlink" title="iPad"></a>iPad</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Mozilla/5.0 (iPad; CPU OS 10_3_3 like Mac OS X) AppleWebKit/603.3.8 (KHTML, like Gecko) Mobile/14G60</span><br></pre></td></tr></table></figure><h3 id="iPod-Touch"><a href="#iPod-Touch" class="headerlink" title="iPod Touch"></a>iPod Touch</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Mozilla/5.0 (iPod Touch; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko)  Mobile/15E148</span><br></pre></td></tr></table></figure><p>我们会发现这三种设备的 User Agent 中都包含有 <code>like Mac OS X</code> 这段共同特征的文字，并且都是以 <code>Mozilla/5.0 (iP</code> 来开头的，因此我们可以用这两个特征来对 iOS 的 User Agent 的合法性进行简单的验证。</p><p>另外，通过 User Agent 还可以提取设备类型，操作系统版本号等信息，因为只要做简单的文字匹配就可以做到，这里就不贴代码了。</p>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;如何获取-User-Agent&quot;&gt;&lt;a href=&quot;#如何获取-User-Agent&quot; class=&quot;headerlink&quot; title=&quot;如何获取 User Agent&quot;&gt;&lt;/a&gt;如何获取 User Agent&lt;/h1&gt;&lt;p&gt;书接上回，前面两篇文章分别讲解了&lt;a href=&quot;https://coolcode.org/2020/02/17/ad-monetization-optimization-1/&quot;&gt;如何获取 make，brand 和 model&lt;/a&gt;和&lt;a href=&quot;https://coolcode.org/2020/02/21/ad-monetization-optimization-2/&quot;&gt;如何获取移动设备唯一标识&lt;/a&gt;。今天我们来讲讲 User Agent 的获取方法。&lt;/p&gt;
&lt;h2 id=&quot;User-Agent-是什么&quot;&gt;&lt;a href=&quot;#User-Agent-是什么&quot; class=&quot;headerlink&quot; title=&quot;User Agent 是什么&quot;&gt;&lt;/a&gt;User Agent 是什么&lt;/h2&gt;&lt;p&gt;User Agent 中文名为用户代理，简称 UA，它是一个特殊的字符串，它其中包含了客户端的设备型号，操作系统及版本、浏览器及版本、浏览器渲染引擎等信息。服务器可以通过它来对客户端进行各种分类统计，因此，对于移动广告来说，它是一个十分重要的请求参数，如果没有提交正确 User Agent，将会严重影响广告的正常充填。&lt;/p&gt;
&lt;p&gt;User Agent 虽然包含了这么多信息，但是它并不需要用户自己来构造。对于浏览器来说，每个浏览器都有自己默认的 User Agent，比如对于 Chrome 来说，它的 User Agent 看上去是下面这个样子：&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;从上面两个 User Agent 中，我们可以看出第一个是来自 Android 4.0.4 系统的 Chrome 浏览器的，甚至我们还能知道它是来自 Galaxy Nexus 这样一台 Android 设备的。而第二个是来自 iPhone 系统上的 Chrome 浏览器的，系统的版本号是 10.3。&lt;/p&gt;
&lt;p&gt;也就是说，如果是在网站上投放广告（不管是普通网站，还是面向移动设备的网站）的话，User Agent 都可以自动通过浏览器的请求头部发送，当然也可以通过 JavaScript 脚本来获取 User Agent 并通过单独的请求发送。&lt;/p&gt;
&lt;p&gt;但是对于移动广告来说，广告请求通常是使用系统自带或来自第三方的网络库发送请求的，而网络库在发送请求时，通常不会自带有效的 User Agent 信息。因此，在这种情况下，我们需要自己从 &lt;code&gt;WebView&lt;/code&gt; 中来获取一个有效的 User Agent。&lt;/p&gt;
    
    </summary>
    
      <category term="广告" scheme="https://coolcode.org/categories/%E5%B9%BF%E5%91%8A/"/>
    
    
      <category term="广告" scheme="https://coolcode.org/tags/%E5%B9%BF%E5%91%8A/"/>
    
      <category term="Android" scheme="https://coolcode.org/tags/Android/"/>
    
      <category term="iOS" scheme="https://coolcode.org/tags/iOS/"/>
    
  </entry>
  
  <entry>
    <title>媒体广告变现优化之道（二）</title>
    <link href="https://coolcode.org/2020/02/21/ad-monetization-optimization-2/"/>
    <id>https://coolcode.org/2020/02/21/ad-monetization-optimization-2/</id>
    <published>2020-02-21T09:46:00.000Z</published>
    <updated>2020-02-21T09:46:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="如何获取移动设备唯一标识"><a href="#如何获取移动设备唯一标识" class="headerlink" title="如何获取移动设备唯一标识"></a>如何获取移动设备唯一标识</h1><p>在移动广告领域，设备唯一标识是用来追踪用户的最重要的标识。</p><p>对于精准广告和个性化推荐而言，可以通过设备唯一标识，进行千人千面的精准投放。</p><p>既然移动设备唯一标识如此的重要，那我们今天就来说一说如何获取设备的唯一标识。</p><a id="more"></a><h2 id="IMEI-MEID"><a href="#IMEI-MEID" class="headerlink" title="IMEI/MEID"></a>IMEI/MEID</h2><p>IMEI (International Mobile Equipment Identity) 是国际移动设备识别码，即通常所说的手机“串号”，用于在移动电话网络中识别每一部独立的手机等移动通信设备，相当于移动电话的身份证。IMEI 适用于 GSM、WCDMA、LTE 制式的移动设备。</p><p>MEID (Mobile Equipment IDentifier) 是移动设备识别码，它也是一个全球唯一识别移动设备的号码。但它适用于 CDMA 制式手机。</p><p>对于全网通的移动设备 IMEI 和 MEID 标识都存在，对于支持双卡的设备，还会有 2 个 IMEI/MEID。</p><p>因为这两个标识的作用相同，所以对于移动广告行业来说，一般不对 IMEI/MEID 做严格区分，在广告请求中一般作为同一个字段（比如：<code>DeviceID</code> 或缩写成 <code>did</code>）进行传输。</p><p>对于 iOS 设备来说，苹果官方在 iOS 5.0 之后就屏蔽了获取 IMEI/MEID 的接口，因此现在的苹果设备都无法获取到 IMEI/MEID。</p><p>对于 Android 设备来说，可以通过下面的方法来获取 IMEI/MEID：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> String <span class="title">getDeviceId</span><span class="params">(Context context)</span> </span>&#123;</span><br><span class="line">    TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);</span><br><span class="line">    <span class="keyword">if</span> (tm != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            String id = tm.getDeviceId();</span><br><span class="line">            <span class="keyword">if</span> (id != <span class="keyword">null</span>) <span class="keyword">return</span> id;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (SecurityException e) &#123;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">""</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于非手机设备，如 Android 平板电脑，电视等，这些设备没有通话的硬件功能，系统中也就没有 <code>TELEPHONY_SERVICE</code>，所以这里要判断一下 <code>tm</code> 这个返回值是否为空。</p><p>在 Android 8.0 之后，<code>TelephonyManager</code> 上的这个 <code>getDeviceId</code> 方法尽管被标记为已过时，并提供了 <code>getImei</code> 和 <code>getMeid</code> 来取代它。但 <code>getDeviceId</code> 又不是不能用，而且我们也不需要区分获取到的究竟是 <code>Imei</code> 还是 <code>Meid</code>，所以这里我们不需要通过判断版本号的方式来替换这个方法。</p><p>获取 IMEI/MEID 的需要 <code>READ_PHONE_STATE</code> 权限，否则会发生 <code>SecurityException</code> 异常。如果应用以 Android 10 或更高版本为目标平台，在应用没有 <code>READ_PRIVILEGED_PHONE_STATE</code> 权限时，也会发生 <code>SecurityException</code> 异常，而且普通开发者开发的应用是不可能获取到该权限的。所以，调用 <code>tm.getDeviceId()</code> 时，我们加了 <code>try/catch</code> 语句，避免崩溃。</p><h2 id="IMSI"><a href="#IMSI" class="headerlink" title="IMSI"></a>IMSI</h2><p>IMSI (International Mobile Subscriber Identity) 是国际移动用户识别码。通俗的讲，它是相对手机卡而言的唯一识别码。</p><p>在 iOS 设备上，跟 IMEI/MEID 一样，是无法获取到 IMSI 的。</p><p>对于 Android 设备来说，获取 IMSI 的方法跟获取 IMEI/MEID 的方法类似：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> String <span class="title">getIMSI</span><span class="params">(Context context)</span> </span>&#123;</span><br><span class="line">    TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);</span><br><span class="line">    <span class="keyword">if</span> (tm != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            String id = tm.getSubscriberId();</span><br><span class="line">            <span class="keyword">if</span> (id != <span class="keyword">null</span>) <span class="keyword">return</span> id;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (SecurityException e) &#123;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">""</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 Android 上获取 IMSI 的权限要求跟 IMEI/MEID 一样，这里就不在重复。</p><h2 id="Android-ID"><a href="#Android-ID" class="headerlink" title="Android ID"></a>Android ID</h2><p>Android ID 是 Android 设备里不依赖于硬件的一种「半永久标识符」，在系统生命周期内不会改变，但系统重置或刷机后可能会发生变化，其作用域为一组有关联的应用。</p><p>Android ID 获取的方式很简单：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> String <span class="title">getAndroidID</span><span class="params">(Context context)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>但是在 Android 8.0 以后，签名不同的 App 所获取的 Android ID 是不一样的，但同一个开发者可以根据自己的数字签名，将所开发的不同 App 进行关联。</p><h2 id="Android-AdID"><a href="#Android-AdID" class="headerlink" title="Android AdID"></a>Android AdID</h2><p>Android AdID (简称 AAID) 是 Android 平台专为广告跟踪提供的唯一标识。但是 AAID 依赖 Google 服务框架，但国内手机基本上都没有内置 Google 服务框架，这种情况下，就无法获取 AAID。因此，这里就不做讨论了。</p><h2 id="OAID"><a href="#OAID" class="headerlink" title="OAID"></a>OAID</h2><p>上面在介绍 IMEI/MEID 时谈到过，在 Android 10 以后，IMEI/MEID、IMSI 这些设备标识都被限制读取了。没有了 IMEI/MEID、IMSI 这些设备唯一标识，对于国内广告行业来说，简直是灭顶之灾。不过正所谓道高一尺魔高一丈，为了拯救国内移动广告市场，<a href="http://msa-alliance.cn/col.jsp?id=120" target="_blank" rel="noopener">移动安全联盟(MSA)</a> 推出了一套“移动智能终端补充设备标识体系”，并根据这套体系的技术要求，联盟开发并发布了一套支持多厂商的统一的补充设备标识调用 SDK。</p><p>这套体系中包含了四种标识符：UDID（设备唯一标识符）, OAID（匿名设备标识符）, VAID（开发者匿名设备标识符）, AAID（应用匿名设备标识符）。</p><p>这四种标识符中，UDID 相当于 IMEI/MEID，它具有无法重置，始终不变的特性。但是它不是提供给广告业务使用的，并且在统一 SDK v1.0.10 版本之后，这个标识符的获取接口被移除了。</p><p>OAID（匿名设备标识符）是用于广告业务的标识符，它在系统首次启动后立即生成，它虽然允许用户手动重置，在刷机、恢复出厂设置等特殊情况下也会重置，但是对于同一台设备上的不同 App，只要没有被重置的情况下，获取到的值都是一致的。因此，虽然它的跟踪特性跟 IMEI/MEID 相比稍微差了点，但是比 Android ID 还是要好一些的。</p><p>关于如何获取 OAID，在 <a href="http://msa-alliance.cn/col.jsp?id=120" target="_blank" rel="noopener">移动安全联盟(MSA)</a> 官网的文档里有详细说明，在我们公司的<a href="https://github.com/adtalos/android-xy-sdk-demo" target="_blank" rel="noopener">广告 SDK 演示实例</a>中也给出了具体的代码实例，由于代码较长，这里就不再单独贴出来了。</p><p>另外多说一句，如果想要在 Flutter 开发的 Android 应用中获取 OAID 的话，可以使用 <a href="https://pub.dev/packages/flutter_msa_sdk" target="_blank" rel="noopener">flutter_msa_sdk</a> 这个插件，这个插件也是我们公司开发的，不过没有什么技术难度，只是对<a href="http://msa-alliance.cn/col.jsp?id=120" target="_blank" rel="noopener">移动安全联盟(MSA)</a> 的统一 SDK 做了一下封装，不过用起来要比 Android 原版容易的多。</p><h2 id="IDFA-IDFV"><a href="#IDFA-IDFV" class="headerlink" title="IDFA/IDFV"></a>IDFA/IDFV</h2><p>前面我们说了，iOS 设备虽然也有 IMEI、IMSI，但是苹果说要保护用户隐私，于是在 iOS 5.0 之后，就把这些接口取消了，不再允许开发者来获取它们了。</p><p>可是隐私归隐私，广告还是要做的嘛，于是苹果在 iOS 6.0 之后，推出了 IDFA 这样一个专门用于广告的标识符。同时还提供了一个 IDFV，它是同一个组织下的唯一标识符。</p><p>IDFA 允许用户在设置中进行重置，刷机或重置设备时，该标识符也会重置，但是在不同的 App 之间它是同一个值。它相当于上面谈到的 OAID 或 Android AdID。</p><p>IDFV 是给 Vendor 标识用户用的，对于隶属于同一个组织的 App，在同一台设备上获取到的 IDFV 都是同一个值。假设有两个应用，它们的 BundleID 分别是 com.adtalos.demo1 和 com.adtalos.demo2，那么它们的就是同一个 Vendor，这两个应用在同一台设备中获取到的 IDFV 就是相同的。它相当于 Android ID，只是表示组织的方式不同而已。</p><p>IDFA 和 IDFV 的获取方式非常简单：</p><figure class="highlight objc"><table><tr><td class="code"><pre><span class="line"><span class="built_in">NSString</span> *idfa = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];</span><br><span class="line"><span class="built_in">NSString</span> *idfv = [[[<span class="built_in">UIDevice</span> currentDevice] identifierForVendor] UUIDString];</span><br></pre></td></tr></table></figure><p>IDFA 和 IDFV 都可能获取不到值，获取不到时，返回值为 <code>nil</code>。</p><h2 id="Mac"><a href="#Mac" class="headerlink" title="Mac"></a>Mac</h2><p>除了以上这些 ID 标识符以外，Mac 地址通常也会用于广告追踪。</p><p>但是很不幸，从 iOS 7.0 之后，苹果就禁止开发者获取 Mac 地址了。</p><p>对于 Android 来说，情况稍微好一点，虽然从 Android 6.0 开始，通过 <code>WifiInfo</code> 获取到的 Mac 地址永远是 <code>02:00:00:00:00:00</code> 这样一个固定值。但是通过下面这个方法还是可以在大多数情况下获取到 Mac 地址的：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> String <span class="title">getMac</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        List&lt;NetworkInterface&gt; all = Collections.list(NetworkInterface.getNetworkInterfaces());</span><br><span class="line">        <span class="keyword">for</span> (NetworkInterface nif : all) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!nif.getName().equalsIgnoreCase(<span class="string">"wlan0"</span>) &amp;&amp; !nif.getName().equalsIgnoreCase(<span class="string">"eth0"</span>)) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">byte</span>[] macBytes = nif.getHardwareAddress();</span><br><span class="line">            <span class="keyword">if</span> (macBytes == <span class="keyword">null</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="string">""</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            StringBuilder sb = <span class="keyword">new</span> StringBuilder();</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">byte</span> b : macBytes) &#123;</span><br><span class="line">                sb.append(String.format(<span class="string">"%02X:"</span>, b));</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (sb.length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                sb.deleteCharAt(sb.length() - <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> sb.toString();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">""</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>原理是扫描各个网络接口，当网络接口是 <code>wlan0</code> 或者 <code>eth0</code> 时，返回它的 Mac 地址。</p>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;如何获取移动设备唯一标识&quot;&gt;&lt;a href=&quot;#如何获取移动设备唯一标识&quot; class=&quot;headerlink&quot; title=&quot;如何获取移动设备唯一标识&quot;&gt;&lt;/a&gt;如何获取移动设备唯一标识&lt;/h1&gt;&lt;p&gt;在移动广告领域，设备唯一标识是用来追踪用户的最重要的标识。&lt;/p&gt;
&lt;p&gt;对于精准广告和个性化推荐而言，可以通过设备唯一标识，进行千人千面的精准投放。&lt;/p&gt;
&lt;p&gt;既然移动设备唯一标识如此的重要，那我们今天就来说一说如何获取设备的唯一标识。&lt;/p&gt;
    
    </summary>
    
      <category term="广告" scheme="https://coolcode.org/categories/%E5%B9%BF%E5%91%8A/"/>
    
    
      <category term="广告" scheme="https://coolcode.org/tags/%E5%B9%BF%E5%91%8A/"/>
    
      <category term="Android" scheme="https://coolcode.org/tags/Android/"/>
    
      <category term="iOS" scheme="https://coolcode.org/tags/iOS/"/>
    
  </entry>
  
  <entry>
    <title>媒体广告变现优化之道（一）</title>
    <link href="https://coolcode.org/2020/02/17/ad-monetization-optimization-1/"/>
    <id>https://coolcode.org/2020/02/17/ad-monetization-optimization-1/</id>
    <published>2020-02-17T12:10:00.000Z</published>
    <updated>2020-02-17T12:10:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="如何获取-make，brand-和-model"><a href="#如何获取-make，brand-和-model" class="headerlink" title="如何获取 make，brand 和 model"></a>如何获取 make，brand 和 model</h1><p>如今，媒体与广告平台之间的对接方式除了直接使用广告平台提供的 SDK 以外，还有很多是采用 API 方式来对接的。</p><p>在 API 对接方式下，媒体需要自己通过 API 将广告请求发送给广告主，广告主根据媒体发来的广告请求来返回响应的广告。</p><p>在通过 API 发送的广告请求中，通常会包含一些设备的信息，例如设备唯一标识，制造商，品牌，型号，联网方式，网络运营商，地理位置，UserAgent 等等，广告主会通过这些设备信息来更好优化返回的广告内容，提高变现率。</p><p>今天我们先来说一下三个最基本的设备信息 make，brand 和 model 的获取方法。</p><a id="more"></a><p>目前，大部分广告 API 的对接协议都是以 OpenRTB 为基础进行设计的。</p><p>在原版的 OpenRTB 中，上面三个设备信息中包含有两个，它们分别是 make 和 model。</p><p>其中 make 表示设备的生产制造商（比如 “Xiaomi”），而 model 表示设备的具体型号（比如 “Redmi K30 5G”）。</p><p>brand 表示设备的品牌（比如 “Redmi”），虽然在 OpenRTB 中没有定义，但是通常的 API 对接协议中也包含有它。</p><p>下面我们分别就 Android 和 iOS 这两种系统来说一下这三个设备信息如何获取。</p><h2 id="Android-系统下的获取方法"><a href="#Android-系统下的获取方法" class="headerlink" title="Android 系统下的获取方法"></a>Android 系统下的获取方法</h2><p>对于 Android 系统来说，这三个信息在 <code>android.os.Build</code> 类中都有明确的定义，获取方式非常简单直接：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> android.os.Build;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">DeviceInfo</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">static</span> String <span class="title">getMake</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> Build.MANUFACTURER;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">static</span> String <span class="title">getBrand</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> Build.BRAND;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">static</span> String <span class="title">getModel</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> Build.MODEL;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里就不在详细展开说明了。</p><h2 id="iOS-系统下的获取方法"><a href="#iOS-系统下的获取方法" class="headerlink" title="iOS 系统下的获取方法"></a>iOS 系统下的获取方法</h2><p>对于 iOS 系统来说，在 OpenRTB 中，make 和 model 的取值都有明确的说明。</p><p>其中 make 的取值就是 “Apple”。</p><p>model 的取值最好是使用 “iPhone10,1” 这样的具体型号，也可以使用 “iPhone” 这种比较笼统的型号。</p><p>brand 没有具体的说明，它的取值可以是 “Apple”，也可以使用 “iPhone”, “iPad” 作为其取值。</p><p>我个人认为，brand 的取值最好不要使用 “Apple”，因为这样跟 make 的取值一样，没有实际意义。</p><p>而如果 model 采用 “iPhone10,1” 这样的具体型号，而 brand 采用 “iPhone” 这样的取值，会让这三个信息都有各自存在的意义。</p><p>例如，如果 model 的取值是 “x86_64”，而 brand 的取值是 “iPhone”，这种情况下，就可以判断这个设备是 iPhone 模拟器。</p><p>下面是在 iOS 下获取这三个设备信息的代码：</p><figure class="highlight objc"><table><tr><td class="code"><pre><span class="line"><span class="meta">#import <span class="meta-string">&lt;Foundation/Foundation.h&gt;</span></span></span><br><span class="line"><span class="meta">#import <span class="meta-string">&lt;UIKit/UIKit.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#import <span class="meta-string">"sys/utsname.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">DeviceInfo</span> : <span class="title">NSObject</span></span></span><br><span class="line"></span><br><span class="line">+ (<span class="keyword">const</span> <span class="built_in">NSString</span> *)model;</span><br><span class="line">+ (<span class="keyword">const</span> <span class="built_in">NSString</span> *)make;</span><br><span class="line">+ (<span class="keyword">const</span> <span class="built_in">NSString</span> *)brand;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">DeviceInfo</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> <span class="built_in">NSString</span>* deviceModel;</span><br><span class="line"></span><br><span class="line">+ (<span class="keyword">const</span> <span class="built_in">NSString</span> *)model &#123;</span><br><span class="line">    <span class="keyword">if</span> (deviceModel == <span class="literal">nil</span>) &#123;</span><br><span class="line">        <span class="keyword">struct</span> utsname systemInfo;</span><br><span class="line">        uname(&amp;systemInfo);</span><br><span class="line">        deviceModel = [<span class="built_in">NSString</span> stringWithCString:systemInfo.machine encoding:<span class="built_in">NSUTF8StringEncoding</span>] ?: [<span class="built_in">UIDevice</span> currentDevice].model;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> deviceModel;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">+ (<span class="keyword">const</span> <span class="built_in">NSString</span> *)make &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">@"Apple"</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">+ (<span class="keyword">const</span> <span class="built_in">NSString</span> *)brand &#123;</span><br><span class="line">    <span class="keyword">return</span> [<span class="built_in">UIDevice</span> currentDevice].model;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure><p>model 的取值对于特定的设备来说是不会变的，因此上面的代码中，对获取 model 值做了一下缓存优化。</p>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;如何获取-make，brand-和-model&quot;&gt;&lt;a href=&quot;#如何获取-make，brand-和-model&quot; class=&quot;headerlink&quot; title=&quot;如何获取 make，brand 和 model&quot;&gt;&lt;/a&gt;如何获取 make，brand 和 model&lt;/h1&gt;&lt;p&gt;如今，媒体与广告平台之间的对接方式除了直接使用广告平台提供的 SDK 以外，还有很多是采用 API 方式来对接的。&lt;/p&gt;
&lt;p&gt;在 API 对接方式下，媒体需要自己通过 API 将广告请求发送给广告主，广告主根据媒体发来的广告请求来返回响应的广告。&lt;/p&gt;
&lt;p&gt;在通过 API 发送的广告请求中，通常会包含一些设备的信息，例如设备唯一标识，制造商，品牌，型号，联网方式，网络运营商，地理位置，UserAgent 等等，广告主会通过这些设备信息来更好优化返回的广告内容，提高变现率。&lt;/p&gt;
&lt;p&gt;今天我们先来说一下三个最基本的设备信息 make，brand 和 model 的获取方法。&lt;/p&gt;
    
    </summary>
    
      <category term="广告" scheme="https://coolcode.org/categories/%E5%B9%BF%E5%91%8A/"/>
    
    
      <category term="广告" scheme="https://coolcode.org/tags/%E5%B9%BF%E5%91%8A/"/>
    
      <category term="Android" scheme="https://coolcode.org/tags/Android/"/>
    
      <category term="iOS" scheme="https://coolcode.org/tags/iOS/"/>
    
  </entry>
  
  <entry>
    <title>我在新义互联一年来的远程工作体验</title>
    <link href="https://coolcode.org/2020/02/04/my-experience-of-remote-working-in-adtalos-for-one-year/"/>
    <id>https://coolcode.org/2020/02/04/my-experience-of-remote-working-in-adtalos-for-one-year/</id>
    <published>2020-02-04T04:00:00.000Z</published>
    <updated>2020-02-05T11:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>今天看到<a href="https://segmentfault.com" target="_blank" rel="noopener">思否</a>有关于<a href="https://segmentfault.com/a/1190000021660147" target="_blank" rel="noopener">远程办公经验分享的征文活动</a>，而我们公司从成立之初就采用了远程工作这种形式，所以我想借着这个机会，谈一谈自己这一年来的远程工作体验，希望能够对想要尝试远程办公的朋友们有所帮助。</p> <a id="more"></a><h1 id="入职"><a href="#入职" class="headerlink" title="入职"></a>入职</h1><p>新义互联是一个年轻的移动互联网广告公司，刚刚成立两年，我是在去年愚人节入职的。</p><p>我入职前的整个面试过程都是远程进行的。事先通过邮件或电话约定好的时间，通过电话，微信视频，QQ视频等方式进行，非常灵活。</p><p>我入职时，公司规模还比较小，仅经过了四轮面试就通过了，前三轮是技术面试，第四轮是老板亲自面试。</p><p>面试通过后，签入职合同及岗前培训还是需要到北京总部跑一趟的。</p><p>在北京总部上班的是业务部门的同事，我签入职合同时，北京总部只有三个同事，现在已经壮大成十几个人的队伍了。技术开发人员都是分布在全国各地的，现在还有在国外的。</p><p>因为最近的新型冠状病毒肺炎问题，从春节假期之前开始，公司的全部业务人员也都开始在家远程办公了。</p><h1 id="每天日常"><a href="#每天日常" class="headerlink" title="每天日常"></a>每天日常</h1><p>我们家有三个孩子，老大平时是上学的，老二还没到上学的年龄，我们没有送他去幼儿园，他在家自己玩，倒也挺省心。老三去年还在襁褓之中，还好我老婆也在家，平时基本上都是她来照顾。</p><p>每天早上 7 点左右，我会起床做饭，做好之后，把老大叫起来一起吃饭。我老婆继续陪两个小的睡觉。吃过早饭，我会赶在 8 点之前送孩子去上学，回到家之后，就一头扎进书房开始工作。平时工作时，很少会被打扰。不过擦屁股、换纸尿裤、洗澡之类的活，即使在工作时间，如果需要我帮忙，我还是会离开椅子一会儿的，所以倒也不用担心长时间久坐得痔疮。</p><p>午饭时间正常来说是 12 点之后，不过灵感来了，写代码停不下来，等想起吃中午饭，可能已经下午 2 - 3 点的时候也有。</p><p>下午 5 点我会去接老大放学，到家之后跟老婆一起做饭，等吃过晚饭，会继续工作一会儿。</p><p>晚上有时候孩子睡得早，夜深人静正好可以专心写代码，一不小心也会写到半夜 12 点之后。不过最近已经尽量减少熬夜行为了，争取每天晚上 9 点之后就洗漱睡觉，毕竟我们领导一直强调身体健康才是最重要的。</p><p>说起身体健康，就不得不提一下锻炼身体。公司的其他同事，每天都会做各种各样的运动，还会在微信群里做运动打卡。我就比较懒了，平时孩子上学放学，我步行去接送孩子就算最大的运动了。之前家里有一个 Xbox360，带 Kinect 体感的，但是现在住的地方房间有点小，不适合用它玩体感运动游戏，就送人了。后来买个 WiiU，又买了个 WiiU 的平衡板，有时候早上起的早，会玩一玩上面的体感运动游戏，就算是锻炼身体了。不过说实话，我平时坚持的不好，一个星期能通过它运动两次已经算多的啦。不过看到同事们这么积极，我觉得我也应该在这方面再多多努力一点，不知道买个 Switch 的《健身环大冒险》会不会让自己的锻炼能够坚持的好一些😂。</p><h1 id="每周日常"><a href="#每周日常" class="headerlink" title="每周日常"></a>每周日常</h1><p>每周的周一上午和周五下午我们都会开一次全体的视频会议，每次会议通常控制在半个小时左右。技术组的会议是每两周进行一次，也是周一上午和周五下午，技术组的会议比较短，通常在十分钟左右。</p><p>我们公司是双休，从我入职以来还没有遇到过周末加班的情况。周末时间可以自由支配。有的周末会跟老婆一起带孩子们出去玩，不过最近是不可能出门了，原因你懂的。不过说实话，我更喜欢周末待在家，孩子们会自己联机玩游戏，很少需要我陪，我可以安安静静的做自己的开源项目。</p><h1 id="每季度日常"><a href="#每季度日常" class="headerlink" title="每季度日常"></a>每季度日常</h1><p>公司每个季度会制定一次 OKR，并总结上一季度 OKR 的完成情况。年底还会做一次全年 OKR 的制定和总结。</p><p>公司每个季度还会举行一次团建活动，大约一周时间。一般会选在一些比较好玩的地方，最近两次团建的地点是黄山和泰国普吉岛。因为平时大家都在家远程办公，很少有机会见面，团建就成了大家相互交流，促进感情的一个很好的机会，有时候 OKR 的制定和总结也会在团建中进行，这样可以更好的面对面沟通。</p><h1 id="所用到的工具"><a href="#所用到的工具" class="headerlink" title="所用到的工具"></a>所用到的工具</h1><p>我们在远程工作中，主要用到了下面一些工具：Github，Slack，Confluence，微信，钉钉，Teambition 和企业邮件。</p><p>Github 是我们的代码托管的平台。我们所有的代码都是放在 Github 上的，有私有项目，也有开源项目。为了方便国内用户访问，部分开源项目我们还在 Gitee 上做了镜像。我们的 Code Review 都是在 Github 上完成的，一部分开发文档，也是放在 Github 项目的 Wiki 中的。</p><p>Slack 是技术组日常交流、项目部署，和对系统进行监控的工具。Slack 提供了很多集成工具，比如某个同事在 Github 上提交了代码，在 Slack 上就可以收到需要 Review 的消息，Jenkins 同时也会把提交代码的构建状态报告到 Slack 里面。项目部署时，在 Slack 里面直接敲入部署命令，机器人就会进行部署，并把部署状态反馈在 Slack 中。每个人当前在做什么，做了什么，在 Slack 里面都可以清楚的看到，并且 Slack 还保留了所有记录。另外，系统如果出现异常情况，也会把报警信息发送到 Slack 里面。有了它，使我们的远程工作变得非常方便。</p><p>Confluence 用来记录会议记录，内部文档和个人知识分享等内容。Confluence 提供了很好的分类管理的功能，还可以多人共同编辑文档，这让远程多人合作变得非常方便。不过缺点是它不支持 md 格式的文档编辑，因此，一些用 md 格式编写的开发文档我们放在了 Github 上，然后在 Confluence 中仅作了一个指向 Github 中的文档的链接。</p><p>Teambition 用来进行 SPRINT 任务管理。微信是公司同事以及合作伙伴之间的沟通工具。钉钉用于公司日常管理和视频会议。企业邮件用于内部通知和一些系统监控的集成。这些工具想必大家也都在用，就不详细展开讲了。</p><p>另外，我们也用过 Zoom，Telegram，虽然很不错，但是因为环境原因，这些工具在国内香不起来，只能放弃。</p><h1 id="感受"><a href="#感受" class="headerlink" title="感受"></a>感受</h1><p>其实从 2008 年开始，我就已经有过远程工作的经历了。但是跟现在相比，那时没有现在这么多方便的工具。所以，从感受上说，在现在公司远程工作的体验要比之前好的多。</p><p>我现在在家里配一台性能强劲的台式机，配了两个显示器，其中主显示器是 43 寸的 4K 显示器，写代码非常爽，副显示器是 23 寸的 1080p 显示器，用来放 Slack 和微信聊天窗口，可以及时的看到同事们发的消息。有了这样的设备，工作效率相当高。</p><p>至于时间管理上，我没有使用番茄钟之类的工具，虽然曾经尝试过，但是感觉不适合我。一方面因为它会屏蔽消息，这样就会导致我不能及时看到同事们发的消息，不能做出及时的响应，另一方面它会影响我写代码的专注力，比如我代码刚写到兴头上，它突然提醒我该休息了，就会把我思路打断。</p><p>现在远程工作时，任务都是写在 Teambition 里面的，每天工作之前，打开 Teambition 来找到自己今天要做的任务，标记上进行中，完成之后，将标记改为已完成，并标记上完成用了多少时间，这样每完成一个任务就像在游戏中打败 BOSS 通过一关一样，让我感觉很有挑战性，也很有成就感。这种方式感觉比用番茄钟之类的高效的多。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>我很享受现在在家办公的这种工作方式，这种工作方式能够很好的平衡工作和家庭之间的关系，既不耽误工作，又能照顾好家，也不用把时间浪费在上下班的路上，感觉真是棒极了。</p><p>真的感觉自己能够加入新义互联这个大家庭是一件非常幸运的事。毕竟现在国内像我们这样全部技术开发人员都可以在家工作，没有 996，拥有双休，每个季度有一次国内团建旅游，每年还有一次国外团建旅游的公司真是太少了，而且同事们都非常的优秀，在一起工作真的特别开心。</p><p>最后，打个广告，如果你对我们的工作感兴趣，想尝试远程工作，欢迎加入。</p><p>公司名称：新义互联（北京）科技有限公司<br>公司官网：<a href="https://adtalos.com" target="_blank" rel="noopener">https://adtalos.com</a><br>公司总部：北京市朝阳区北土城东路4号院1号楼1层3009室<br>Email：<a href="mailto:hr@adtalos.com" target="_blank" rel="noopener">hr@adtalos.com</a></p>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;今天看到&lt;a href=&quot;https://segmentfault.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;思否&lt;/a&gt;有关于&lt;a href=&quot;https://segmentfault.com/a/1190000021660147&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;远程办公经验分享的征文活动&lt;/a&gt;，而我们公司从成立之初就采用了远程工作这种形式，所以我想借着这个机会，谈一谈自己这一年来的远程工作体验，希望能够对想要尝试远程办公的朋友们有所帮助。&lt;/p&gt;
    
    </summary>
    
      <category term="远程工作" scheme="https://coolcode.org/categories/%E8%BF%9C%E7%A8%8B%E5%B7%A5%E4%BD%9C/"/>
    
    
      <category term="远程工作" scheme="https://coolcode.org/tags/%E8%BF%9C%E7%A8%8B%E5%B7%A5%E4%BD%9C/"/>
    
  </entry>
  
  <entry>
    <title>将 GPD P2 Max 变身为 Macbook Nano</title>
    <link href="https://coolcode.org/2019/12/07/my-new-macbook-nano/"/>
    <id>https://coolcode.org/2019/12/07/my-new-macbook-nano/</id>
    <published>2019-12-07T14:46:00.000Z</published>
    <updated>2019-12-08T05:32:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近一直在家工作，用的都是台式机，这个月公司要组织去泰国旅游团建，正愁手里没有个便携的笔记本，手机淘宝就给我发了一条 GPD P2 Max 的推送广告，让我知道了这个宝贝。在跟多款同类产品经过了一番比较之后，决定就买这一款了，趁着双 12 期间有优惠活动，最后从京东上买了这台超级本。</p><p>先说一下为啥选它而不是其它的几款同类产品的理由。首先从大小上来说，这款是同类产品中最大的，其它几款都是 6寸，7寸，8寸，8.4寸，只有这一款是 8.9 寸。同时它在笔记本产品中又足够小，比主流的 13 寸，14 寸笔记本要小的多，甚至比 10 寸的 iPad 还要小，它只比 iPad Mini 大一点点。所以带出去很方便，屏幕又足够可以用。</p><p>它还是 2560x1600 的 16：10 的高分屏，色彩，可视角度都很棒。现在万元以下几乎找不到一款这么高分辨率的笔记本电脑，比如华为的 MateBook 13，号称 2K 屏，实际上分辨率只有 2160 x 1440，只比 1080p 分辨率高一点而已。当然，对于 Windows 系统来说，是不是真正的 2K 屏并不重要，又不是不能看。但是我选择它还有一个理由，就是我要用它装 MacOS 系统，当然这是工作需要，而不是单纯为了装逼。</p><p>对于 MacOS 系统来说，笔记本如果不能开 HiDPI，那屏幕看起来模糊的就跟眼瞎了一样。所以，一般的 1280x768，1920x1080 这种显示器的笔记本如果用来装 MacOS 系统，体验那是相当的差。而华为那款 2160 x 1440 分辨率的 MateBook 13，如果开了 HiDPI 的话，显示分辨率只有 1080x720，屏幕上能显示的东西很有限，甚至有些软件的界面都可能显示不全。所以，2560x1600 这个分辨率就相当重要了，低于这个分辨率的笔记本，装 MacOS 体验都不会好到哪儿去。当然，如果你的要求不高，只要满足『又不是不能用』这个条件就能接受的话，那你就随便选了。</p><p>满足这个分辨率，尺寸又足够小的同类产品当然也有几款，比如壹号本系列有几款：One Mix 3，One Mix 3S, One Mix 3 Pro。壹号本这几款，尺寸是 8.4 寸，比 GPD P2 Max 稍微小一点点，但分辨率同样是 2560x1600 高分屏，所以这个尺寸还算可以接受。</p><p>壹号本这几款的屏幕还支持 360 度翻转，可以变身为平板电脑，看上去更高大上一些，而 GPD P2 Max 就是普通的笔记本设计，虽然也带触摸屏，但没有 360 度旋转变平板的功能。不过我又不是做商务的，也不是做设计的，变身平板这一点我好像用不到。</p><p>而 GPD P2 Max 带一个高清摄像头，壹号本这几款都不带摄像头，而我们远程办公开视频会议的时候，有时候会用到摄像头，因为这个摄像头的存在，反而让我更看好 GPD P2 Max。</p><p>壹号本内置的固态硬盘是板载固态硬盘，没办法更换，在没有确定这块内置固态硬盘能否安装 MacOS 之前，我也不太想冒这个风险。而 GPD P2 Max 的固态硬盘就是普通的 NVMe m.2 的固态硬盘，可以很方便的更换，而且在我购买之前，我已经确认过，这款 GPD P2 Max 自带的这块固态硬盘是支持安装 MacOS 系统的，这一点反而让我省心了。</p><p>最后是接口方面，GPD P2 Max 和壹号本都一个 Type-C 接口，而 GPD P2 Max 比壹号本多了一个 Type-A 的 USB 3.0 接口。两个 Type-A 的 USB 接口实在是太重要了。因为要装 MacOS 系统，内置的无线网卡就不能用了，除非自己更换，但难度较大，因为内置的 Wifi 模块是焊死在主板上的，要把它弄下来的话，需要高超的手艺，否则一不小心可能主板都报废了。而便宜又省心的方法，就是买个几十块钱的 USB 无线网卡来代替。如果用这个方案的话，就需要占用一个 USB 接口了。而如果笔记本只带一个 Type-A 的 USB 接口，又被无线网卡占用了的话，那就没地方插鼠标了。虽然说可以通过 Type-C 来外接一个扩展坞解决，但是为了插个鼠标，还要接个扩展坞，在方便性上就大打折扣了。总之，有 2 个 Type-A 的 USB 接口的话，在便利性上会方便很多。而 GPD P2 Max 刚好满足这一点。</p><p>壹号本相对于 GPD P2 Max 来说多了一个非常鸡肋的读卡器接口，这个接口只支持 A1 卡，而且如果安装了 MacOS 系统的话，这个接口也是废掉的，无法使用。</p><p>价格上，壹号本比内存和硬盘容量相同配置的 GPD P2 Max 还要贵 1000 块钱，既然有便宜又好用的，干嘛要选贵的呢。所以，比较之后，就只剩下 GPD P2 Max 这一款可以选择了。</p><p>说了这么多，其实最终让我下决心买这款 GPD P2 Max 的决定性因素是我发现了这个项目：<a href="https://github.com/Azkali/GPD-P2-MAX-Hackintosh" target="_blank" rel="noopener">https://github.com/Azkali/GPD-P2-MAX-Hackintosh</a> ，一个可以在 GPD P2 Max 上完美安装 MacOS 的解决方案。</p><p>买来之后，我按照这个项目中的说明一次就安装成功了，最终结果也果然没有让我失望，体验非常好。</p><p>在性能上，这款装了 MacOS 的 GPD P2 Max 跟最新的 2018，2019 款的 MacBook Air 差不多，CPU 单核跑分甚至胜过这两款最新的 MacBook Air，多核跑分上比这两款最新的 MacBook Air 稍低，不过我这是在 GPD P2 Max 的 BIOS 中设置为风扇静音的节能模式下测试的结果，如果打开性能模式，可能跑分会更高一些。</p><p>而在便利性上，这款 GPD P2 Max 可比 MacBook Air 轻便多了，毕竟 MacBook Air 现在最小的也有 13 寸，跟 MacBook Pro 比起来只能算低配，而不能算轻便。而价格上 MacBook Air 也不美丽，在内存和硬盘大小相同的配置下，最新的 MacBook Air 比这款 GPD P2 Max 可是贵了整整 10000 块钱呢，而 GPD P2 Max 只有 MacBook Air 三分之一的价格。</p><p>另外，这款 GPD P2 Max 在装 MacOS 选择 SMBIOS 机型时，除了原作者写的 MacBook10,1 这个机型以外，其实还可以选择上面那两款最新的 MacBook Air 机型（MacBookAir8,1 和 MacBookAir8,2），这样显得更时尚一些，逼也可以装的更足。</p><p>当然，这款装了 MacOS 的 GPD P2 Max，也不是完全没有缺点。在测试了几天之后，我也在追求完美的过程中，努力发现了以下几个原本可以忽略的问题：</p><p>1、电池容量。官方标示的是 9200mAh，但是实际上是两节 4600mAh 的电池串联，而两节电池串联只是电压会加倍，电流并不会加倍。在 MacOS 中，可以看到电池容量显示的是 4600mAh，开始我以为是 MacOS 显示错了。后来进 Windows 系统查看，发现 Windows 上使用的是 35000mWh 这样的单位，后来又去查了一下官方的宣传图片，上面写的电池容量是 35Wh( <a href="mailto:9200mAh@7.6V" target="_blank" rel="noopener">9200mAh@7.6V</a> )，如果按照 <a href="mailto:9200mAh@7.6V" target="_blank" rel="noopener">9200mAh@7.6V</a> 来计算的话，应该是 70Wh 的容量才对，而官方宣传上写的和实际测试的明明又都是 35Wh，所以只有一个可能，那就是官方标示的这个 9200mAh 是错的，后来跟官方技术开发人员确认了一下，确实这个宣传写的是错的。</p><p>2、硬盘通电次数。在使用了 2 天之后，打算测试一下硬盘，测试结果吓我一跳，使用时间是 32 个小时，这个看上去没有问题，跟我实际的开机使用时间基本相符，至少也没有多出太多，但是通电次数居然是 570 多次，我就算安装系统时反复重启，加一起也不过只有几十次，怎么会多出 500 多次呢？询问官方技术开发人员，得到的答复是那个检测结果不准确，因为机器生产时，有老化这个阶段，这个可能影响了通电次数的测试结果。所以，如果真的不影响硬盘寿命的话，这个问题也可以忽略，就算用几年之后，硬盘真的坏了，到时候再换一个更好的就是了，反正现在硬盘是越来越便宜了。而且对我来说，这个作为旅游外出用的笔记本，上面的数据都是些临时的数据，就算丢了，也会在其他地方有备份，所以这个倒也没什么好担心的。</p><p>3、HDMI 接口。这个本自带的那个 micro HDMI 接口实际是 1.4 的，而不是宣传图上写的 2.0 接口，官方技术人员已经确认那个是市场部因为有误解标错了。所以就不要想通过这个接口来外接什么 4K@60Hz 的显示器了，这个接口只能正常外接 1080p 或 2K@60Hz 的显示器。如果真想连 4K@60Hz 显示器的话，官方技术人员说通过 Type-C 转 DP 也许可以实现，不过我还没试过，因为我没有 Type-C 转 DP 的扩展坞，而且暂时也没有这个迫切的需求。另外，这个 Type-C 口不支持雷电。</p><p>4、在 MacOS 下，蓝牙只支持一部分设备的连接。比如罗技蓝牙键盘和鼠标在 MacOS 下都不能通过蓝牙方式进行连接，只能通过优联接收器来连接使用。但是其它一些蓝牙设备却可以正常连接，比如可以通过蓝牙给小米手机发送文件，也可以连接小米手机通过蓝牙共享的网络。也就是说，在外面只要手机能上网，这个本就可以直接通过蓝牙连接手机上网，不需要 USB 的 Wifi 接收器也能上网，这一点倒是挺便利的。其它的蓝牙设备暂时还没有测试。不过像隔空投送之类的苹果无线蓝牙特有的这些功能肯定是没有的。</p><p>5、在 MacOS 下睡眠和唤醒有时候正常，有时候又不正常。大概跟外接鼠标键盘有关。外接鼠标或键盘的时候，盒盖无法睡眠，点了睡眠之后，外接的鼠标和键盘稍微一碰，就会唤醒。关于休眠唤醒我也没做过多测试。总之，不用的时候，还是关机最安全。</p><p>我遇到的大概就这么多问题，而且这些问题对我来说都是无关紧要的，都可以忽略。</p><p>总之，这个价格能买到一个可以跑 Windows 和 MacOS 双系统的高分屏的超便携笔记本，我已经很知足了。</p><p>另外，我 fork 了一份上面那个项目，并且做了一些修改和升级，这是我 fork 的项目地址：<a href="https://github.com/andot/GPD-P2-MAX-Hackintosh" target="_blank" rel="noopener">https://github.com/andot/GPD-P2-MAX-Hackintosh</a> ，当然我也给原作者提交 pull request 了。如果原作者过些日子合并了的话，就直接参考他那个地址就可以了。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;最近一直在家工作，用的都是台式机，这个月公司要组织去泰国旅游团建，正愁手里没有个便携的笔记本，手机淘宝就给我发了一条 GPD P2 Max 的推送广告，让我知道了这个宝贝。在跟多款同类产品经过了一番比较之后，决定就买这一款了，趁着双 12 期间有优惠活动，最后从京东上买了这
      
    
    </summary>
    
      <category term="操作系统" scheme="https://coolcode.org/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    
      <category term="Mac" scheme="https://coolcode.org/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/Mac/"/>
    
    
      <category term="mac" scheme="https://coolcode.org/tags/mac/"/>
    
  </entry>
  
  <entry>
    <title>新装的黑苹果</title>
    <link href="https://coolcode.org/2019/06/26/my-new-hackintosh/"/>
    <id>https://coolcode.org/2019/06/26/my-new-hackintosh/</id>
    <published>2019-06-26T11:28:00.000Z</published>
    <updated>2019-06-29T13:09:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>去年年底买了个 NUC8i7HVK，打算把现在用 iMac 27 卖掉，然后装个 Mac 和 Windows 的双系统做开发用，但是 iMac 装了 10.14 之后，发现 APFS 也支持 Fusion Drive 了，感觉还挺好用的。就一直没卖。所以 NUC8i7HVK 上最后只装了一个 Windows 10，没装 Mac 系统。用了半年，发现 27 寸的 iMac 虽然是 5K 屏，但是不够大，还是 NUC8i7HVK 上接的那个飞利浦 43 寸 4K 显示器比较爽。但是 iMac 接在 43 寸显示器上用的话，有两个显示器，而 43 寸的显示器本身已经够大了，两个显示器根本看不过来，所以 iMac 自带的屏幕就有点浪费了，于是再次打算装个黑苹果，把 iMac 卖掉。</p><p>于是又买了一块三星 SM961 的 M2. NVMe 硬盘，插到 NUC8i7HVK 中装黑苹果。最后安装倒是安装好了，可是发现显卡驱动不完美，很多地方显示是花屏，感觉效果不如意。所以打算重新自己卖配件，自己组装一台黑苹果，然后把 NUC8i7HVK 和 iMac 全都卖掉。</p><p>经过上网搜索各种资料，最后从淘宝买了下面这一堆配件：</p><table><thead><tr><th>硬件</th><th>型号</th><th>价格</th></tr></thead><tbody><tr><td>CPU + 主板</td><td>Intel Core i7-8700 + 微星 B360M 迫击炮</td><td>2639</td></tr><tr><td>显卡</td><td>蓝宝石 RX580 8G 2304SP 白金版 1366MHz</td><td>728</td></tr><tr><td>硬盘</td><td>三星 SM961 512G M2 NVMe</td><td>549</td></tr><tr><td>硬盘2</td><td>东芝 TR200 960G SSD SATA</td><td>759.8</td></tr><tr><td>内存</td><td>英睿达镁光 DDR4 2666Mhz 16G*2 (共 32G)</td><td>958</td></tr><tr><td>无线 + 蓝牙</td><td>BCM943602CS 双频 BT4.1 无线网卡 PCI-E</td><td>184.24</td></tr><tr><td>摄像头 + 麦克风</td><td>蓝色妖姬 480p USB2 摄像头，内置麦克风</td><td>38.71</td></tr><tr><td>机箱 + 电源 + 风扇</td><td>酷冷至尊Q300L机箱，MWE500电源，风扇 * 3</td><td>620</td></tr><tr><td>USB3.1 Type-C</td><td>Type-E 转为 Type-C 的 USB 3.1 的挡板线</td><td>32</td></tr></tbody></table><p>算下来总价格差不多是：6509 元。</p><p>这个价格比最新的 Mac mini 最低配（6331 元）只贵了不到 180 元。但是在配置上那已经是天差地别了。</p><p>下面是跟 Mac mini 最低配的对比：</p><table><thead><tr><th>Mac mini</th><th>黑苹果</th><th>对比</th></tr></thead><tbody><tr><td>Intel Core i3-8100</td><td>Intel Core i7-8700</td><td>Mac mini 要换这个 i7 CPU 需要加 2334 元</td></tr><tr><td>8GB 2666MHz DDR4</td><td>32G 2666Mhz DDR4</td><td>Mac mini 要换 32G 内存需要加 4404 元</td></tr><tr><td>Intel 核显</td><td>Intel 核显 + RX580</td><td>Mac mini 没有独显，加钱也没有</td></tr><tr><td>128GB 固态硬盘</td><td>512G + 960G 固态硬盘</td><td>Mac mini 就算换 1T 固态硬盘也需要加 5872 元</td></tr><tr><td>USB 3.0 x 2 + USB 3.1 x 4</td><td>USB 2.0 x 4 + USB 3.0 x 2 + USB 3.1 x 3</td><td>Mac mini Type-C 接口虽多，但 Type-A 的不够用</td></tr></tbody></table><p>单纯对比这几项，我们就会发现，即使 Mac mini 再加上 12610 元，把配置升级了，还是比这台黑苹果的配置差劲，因为这还没算上显卡。</p><p>也就是说，一台升级了配置之后，仍然比黑苹果还差劲的 Mac mini 的价钱已经相当于三台黑苹果了。</p><p>所以，上面黑苹果对配置跟 Mac mini 比起来可以算是性价比极高了。</p><p>当然我还有一些配件没有算上，比如我还有一块三星 970 Pro 1T 的 M2 NVMe 硬盘，是 NUC8i7HVK 上的，现在的价格差不多是 2779 元，原来的价格肯定比这个高。键盘鼠标是以前买的罗技 K780 + M590，入手价是 534 元。显示器是去年跟 NUC8i7HVK 一起买的飞利浦 43 寸的 4K 显示器，入手价是 2977 元。这些加一起也有 6290 元。</p><p>要是都算在一起的话，总价格差不多有 12800 元吧。看上去有点贵了，可是跟 iMac 对比一下：</p><table><thead><tr><th>iMac 27</th><th>黑苹果</th><th>对比</th></tr></thead><tbody><tr><td>Intel Core i5-9600K</td><td>Intel Core i7-8700</td><td>i7-8700 不论性能还是价格都更高一些</td></tr><tr><td>32GB 2666MHz DDR4</td><td>32G 2666Mhz DDR4</td><td>这俩可以配置一样</td></tr><tr><td>Radeon Pro 580X 8G</td><td>蓝宝石 RX580 8G</td><td>iMac 这个显卡名字很牛逼，其实跟 RX580 一样</td></tr><tr><td>2T SSD</td><td>512G + 960G +1T SSD</td><td>黑苹果的硬盘容量更大一些</td></tr><tr><td>苹果键盘鼠标</td><td>罗技 K780 + M590</td><td>苹果键盘说实话，手感和耐用程度比罗技差远了</td></tr><tr><td>27寸 5K 显示器</td><td>43寸 4K 飞利浦显示器</td><td>各有各的好，我更喜欢大的</td></tr></tbody></table><p>按照上面 iMac 27 这个配置的话，官网价格是 30206 元，差不多是黑苹果两倍半的价格。所以，这台黑苹果的性价比仍然是极高的。更何况，我这几样多出来的是之前的配件，并不需要重新购买，跟新买一台 iMac 27 来比的话，就更省钱了。</p><p>这个配置安装黑苹果也是相当简单，硬件安装就不详细说了，按照说明书插起来就行了，自己动手大不了多花点时间，并不是很难。</p><p>这里说几点需要注意的地方，显卡插在第一个 PCI-E x16 插槽上，显卡会占两个 PCI-E 挡板的位置，所以紧挨着第一个 PCI-E x16 的那个 PCI-E x1 插槽被挡起来了，用不了。BCM943602CS 无线网卡插在第三个挡板位置对应的 PCI-E x1 插槽里面，跟显卡紧挨着。因为第四个 PCI-E 插槽不能用，它跟第二个 M2 插槽冲突，我两个 M2 插槽都要插硬盘，所以，第四个 PCI-E 插槽只能空着，不能用来插无线网卡，我最开始就把无线网卡插在那里了，最后发现不能用。但是这个插槽空着，挡板位置还是不能浪费的，正好主板上还有一个 USB 3.1 的 Type-E 的接口，我专门买了一个 Type-E 转为 Type-C 的 USB 3.1 的挡板线，把它引出来，正好可以把挡板插在第四个 PCI-E 插槽的位置上，这样四个挡板的位置就全利用起来啦，后面板上还多了一个 Type-C 接口，还可以防止空着进灰了，一举两得。</p><p>主板上有一个 CPU 风扇接口，直接接 CPU 的风扇，我的 CPU 是 i7-7800，非超频版本，功耗只有 65w，用盒装自带的风扇完全镇的住它，不需要单独买个 CPU 散热器了，更不需要上什么水冷。</p><p>机箱后面自带一个风扇，接在 SysFan3 上面，离得近。顶部的风扇接在 SysFan1 上，前面的风扇接在 SysFan2 上。风扇多的可以分组串联，比如我前面装了2个风扇，是串联在一起接在 SysFan2 上的。</p><p>这样做，之后可以在系统的 BIOS 里面对每组风扇单独控制。</p><p>我买的电源是直出的，跟全模组的相比，便宜一半，缺点就是用不着的线一大堆，用不着的线我塞到机箱背面捆起来，这样就不会影响美观和散热了。</p><p>关于内存条，我只买了 2 条 16G 的，日后如果需要，还可以再加 2 条扩充到 64G。但是如果只插两条内存的话，应该插在 2、4 位置上，不然开机之后会有提示告诉你，你所插的内存位置没有优化。</p><p>接下来是安装系统。</p><p>我要安装的是双系统，安装之前先进 BIOS 进行以下设置：</p><p>首先用 F6 载入默认优化的配置。接下来调节风扇转速，把智能调节都打开，然后按照自己的喜好来设置每一档的风扇转速。如果不设置的话，机箱的风扇声音会全速运行，非常吵，但是温度却不一定很低。设置好了，会很静音，温度也不高。</p><p>接下来是开启双显卡支持，核显加速。打开高级设置，找到调整整合式显卡的设置（Integrated Graphics Configuration），第一项设置为 PEG，然后把下面一项开启多显示器输出功能打开，这时中间会出现一项集成显卡分配的内存容量，保持默认值 64M 就可以了。这样设置之后，就可以打开双显卡支持，核显加速了。如果不这么设置，不管是 Windows 还是黑苹果都会有问题。</p><p>如果你希望可以用USB鼠标或键盘唤醒电脑的话，可以在唤醒事件设置中，把由 USB 设备唤醒一项打开。</p><p>剩下其他的设置，就都不要动了。都保持默认值就好。否则，安装黑苹果时可能会遇到各种奇怪的问题。</p><p>先安装 Windows 10，安装好之后，从<a href="https://cn.msi.com/Motherboard/support/B360M-MORTAR" target="_blank" rel="noopener">微星官网</a>下载 BIOS 和驱动安装升级。</p><p>然后从<a href="https://www.samsung.com/semiconductor/minisite/ssd/download/tools/" target="_blank" rel="noopener">三星官网</a>下载硬盘驱动，从<a href="https://ssd.toshiba-memory.com/cn-apac/download/" target="_blank" rel="noopener">东芝官网</a>下载硬盘固件管理程序，之后安装升级就行了。</p><p>独显驱动从 AMD 官网下载，无线网卡和蓝牙驱动是跟卖家要的。</p><p>一切都没问题的话，就可以安装黑苹果了。</p><p>Mac 安装盘镜像从<a href="https://blog.daliansky.net/" target="_blank" rel="noopener">果黑小兵的部落阁</a>下载。然后下载对应的 <a href="https://github.com/SuperNG6/MSI-B360-10.14.5-EFI/releases" target="_blank" rel="noopener">EFI</a>。安装盘制作好之后，用下载的 EFI 替换安装盘的 EFI 分区中的 EFI 目录中的内容。然后下载 Clover Configurator，按照<a href="https://sleele.com/2019/03/21/smbios/" target="_blank" rel="noopener">这篇文章</a>中所写内容生成 SMBIOS 信息，按照我的黑苹果硬件配置来说，选择 iMac19,2 就可以了，注意一定要更新 Clover Configurator 到最新版，不然没有这个选项可选。更新完了记得保存，不然就白改了。</p><p>接下来，把安装 U 盘插到机箱，在 BIOS 里把启动改成 U 盘优先，然后剩下的基本上就是下一步下一步，跟在白苹果上安装基本上没有什么区别。安装过程中会重启几次，重启之后也是先通过 U 盘引导，进入 Clover 界面，选择已安装的苹果系统盘启动，两次之后就进入安装完成界面了，然后一路设置完成，就可以使用了。</p><p>接下来，用 Clover Configurator 把安装盘上的 EFI 目录复制的安装好的系统的 EFI 盘的 EFI 目录中。之后就不需要 U 盘，直接可以通过硬盘启动了。</p><p>接下来对系统的优化可以参考：<a href="https://github.com/SuperNG6/MSI-B360-10.14.5-EFI/wiki" target="_blank" rel="noopener">https://github.com/SuperNG6/MSI-B360-10.14.5-EFI/wiki</a> ，这里就不啰嗦了。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;去年年底买了个 NUC8i7HVK，打算把现在用 iMac 27 卖掉，然后装个 Mac 和 Windows 的双系统做开发用，但是 iMac 装了 10.14 之后，发现 APFS 也支持 Fusion Drive 了，感觉还挺好用的。就一直没卖。所以 NUC8i7HVK
      
    
    </summary>
    
      <category term="操作系统" scheme="https://coolcode.org/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    
      <category term="Mac" scheme="https://coolcode.org/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/Mac/"/>
    
    
      <category term="mac" scheme="https://coolcode.org/tags/mac/"/>
    
  </entry>
  
  <entry>
    <title>Hprose 3.0 for .NET 支持哪些平台</title>
    <link href="https://coolcode.org/2019/02/26/which-platforms-are-supported-by-hprose-3.0-for-dotnet/"/>
    <id>https://coolcode.org/2019/02/26/which-platforms-are-supported-by-hprose-3.0-for-dotnet/</id>
    <published>2019-02-26T12:47:00.000Z</published>
    <updated>2020-01-01T07:59:00.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://github.com/hprose/hprose-dotnet" target="_blank" rel="noopener">Hprose 3.0 for .NET</a> 采用模块化设计。目前共分 7 个包，它们分别是：</p><ul><li><a href="https://www.nuget.org/packages/Hprose.IO" target="_blank" rel="noopener">Hprose.IO</a></li><li><a href="https://www.nuget.org/packages/Hprose.RPC" target="_blank" rel="noopener">Hprose.RPC</a></li><li><a href="https://www.nuget.org/packages/Hprose.RPC.Plugins" target="_blank" rel="noopener">Hprose.RPC.Plugins</a></li><li><a href="https://www.nuget.org/packages/Hprose.RPC.Codec.JSONRPC" target="_blank" rel="noopener">Hprose.RPC.Codec.JSONRPC</a></li><li><a href="https://www.nuget.org/packages/Hprose.RPC.Owin" target="_blank" rel="noopener">Hprose.RPC.Owin</a></li><li><a href="https://www.nuget.org/packages/Hprose.RPC.AspNet" target="_blank" rel="noopener">Hprose.RPC.AspNet</a></li><li><a href="https://www.nuget.org/packages/Hprose.RPC.AspNetCore" target="_blank" rel="noopener">Hprose.RPC.AspNetCore</a></li></ul><a id="more"></a><p><a href="https://www.nuget.org/packages/Hprose.IO" target="_blank" rel="noopener">Hprose.IO</a> 是 Hprose 的序列化和反序列化库。支持：</p><ul><li>.NET Framework 3.5+</li><li>.NET Framework Client Profile 3.5 - 4.0</li><li>.NET Core 2.0+</li><li>.NET Compact Framework 3.5</li><li>.NET Standard 2.0+</li></ul><p><a href="https://www.nuget.org/packages/Hprose.RPC" target="_blank" rel="noopener">Hprose.RPC</a> 是 Hprose RPC 的核心库，提供了 Hprose RPC 的 Http、WebSocket、Tcp、Udp 的服务器和客户端的实现。跟 <a href="https://www.nuget.org/packages/Hprose.IO" target="_blank" rel="noopener">Hprose.IO</a> 相比，该模块除了不支持 .NET Framework 3.5 和 .NET Framework Client Profile 3.5 以外，其它支持的 .NET 环境与 <a href="https://www.nuget.org/packages/Hprose.IO" target="_blank" rel="noopener">Hprose.IO</a> 相同。特别要强调的是，<a href="https://www.nuget.org/packages/Hprose.RPC" target="_blank" rel="noopener">Hprose.RPC</a> 是支持 .NET Compact Framework 3.5 的，但是在 .NET Compact Framework 3.5 下，不支持 Http、WebSocket 服务器和 WebSocket 客户端，仅支持 Tcp、Udp 服务器和 Http、Tcp、Udp 客户端。另外，在 .NET Framework 4.0 环境下，也不支持 WebSocket 服务器和客户端。</p><p><a href="https://www.nuget.org/packages/Hprose.RPC.Plugins" target="_blank" rel="noopener">Hprose.RPC.Plugins</a> 是 Hprose RPC 的插件库，提供了 Hprose RPC 的一些现成的通用插件。其中包括负载均衡插件，集群容错插件，熔断降级插件，限流插件，推送插件，反向调用插件，单向调用插件和日志插件。其支持的 .NET 环境与 <a href="https://www.nuget.org/packages/Hprose.RPC" target="_blank" rel="noopener">Hprose.RPC</a> 相同。</p><p><a href="https://www.nuget.org/packages/Hprose.RPC.Codec.JSONRPC" target="_blank" rel="noopener">Hprose.RPC.Codec.JSONRPC</a> 是 Hprose RPC 的 JSONRPC 编码库，通过它，可以让 Hprose 服务器和客户端变身为 JSONRPC 2.0 的服务器和客户端，而且对于服务器来说，可以同时提供 JSONRPC 2.0 服务和 Hprose 3.0 服务。其支持的 .NET 环境与 <a href="https://www.nuget.org/packages/Hprose.RPC" target="_blank" rel="noopener">Hprose.RPC</a> 相同。</p><p><a href="https://www.nuget.org/packages/Hprose.RPC.Owin" target="_blank" rel="noopener">Hprose.RPC.Owin</a> 是 Hprose RPC 在 Owin 上的服务模块。如果需要在支持 Owin 的 .NET 服务器上发布 Hprose 服务，可以使用该库。因为 Owin 是基于 Http 的 Web 服务，而 .NET Compact Framework 3.5 环境并没有提供 Http 服务，因此该模块不支持 .NET Compact Framework 3.5 环境下使用，除此之外，其支持的环境与 <a href="https://www.nuget.org/packages/Hprose.RPC" target="_blank" rel="noopener">Hprose.RPC</a> 相同。</p><p><a href="https://www.nuget.org/packages/Hprose.RPC.AspNet" target="_blank" rel="noopener">Hprose.RPC.AspNet</a> 是 Hprose RPC 在 ASP.NET 上的服务模块。因为 ASP.NET 仅支持 .NET Framework 环境，不支持 .NET Framework Client Profile、.NET Core 和 .NET Compact Framework 环境。因此该模块仅支持在 .NET Framework 环境下使用。</p><p><a href="https://www.nuget.org/packages/Hprose.RPC.AspNetCore" target="_blank" rel="noopener">Hprose.RPC.AspNetCore</a> 是 Hprose RPC 在 ASP.NET Core 上的服务模块。因为 ASP.NET Core 仅支持 .NET Core，因此该模块也仅支持在 .NET Core 下使用。</p><p>Hprose 3.0 for .NET 支持的操作系统有：</p><ul><li>Windows XP - Windows 10 ( .NET Standard, .NET Core, .NET Framework )</li><li>Linux ( .NET Standard, .NET Core )</li><li>Mac OS X ( .NET Standard, .NET Core )</li><li>iOS ( .NET Standard )</li><li>TvOS ( .NET Standard )</li><li>WatchOS ( .NET Standard )</li><li>Android ( .NET Standard )</li><li>Windows CE ( .NET Compact Framework )</li></ul><p>也就是说，Hprose 3.0 for .NET 支持目前 .NET 支持的所有主流的操作系统和平台。</p><p>因为 SliverLight 和 Windows Phone 已死，所以 Hprose 3.0 for .NET 不再提供对它们的支持。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;a href=&quot;https://github.com/hprose/hprose-dotnet&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hprose 3.0 for .NET&lt;/a&gt; 采用模块化设计。目前共分 7 个包，它们分别是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nuget.org/packages/Hprose.IO&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hprose.IO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nuget.org/packages/Hprose.RPC&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hprose.RPC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nuget.org/packages/Hprose.RPC.Plugins&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hprose.RPC.Plugins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nuget.org/packages/Hprose.RPC.Codec.JSONRPC&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hprose.RPC.Codec.JSONRPC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nuget.org/packages/Hprose.RPC.Owin&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hprose.RPC.Owin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nuget.org/packages/Hprose.RPC.AspNet&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hprose.RPC.AspNet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nuget.org/packages/Hprose.RPC.AspNetCore&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hprose.RPC.AspNetCore&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
      <category term="编程" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/"/>
    
      <category term="Hprose" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/Hprose/"/>
    
    
      <category term="c#" scheme="https://coolcode.org/tags/c/"/>
    
      <category term="hprose" scheme="https://coolcode.org/tags/hprose/"/>
    
      <category term=".net" scheme="https://coolcode.org/tags/net/"/>
    
  </entry>
  
  <entry>
    <title>如何在 VS2019 下编译 Hprose 3.0 for .NET</title>
    <link href="https://coolcode.org/2019/02/22/how-to-compile-hprose-3.0-for-dotnet-on-vs2019/"/>
    <id>https://coolcode.org/2019/02/22/how-to-compile-hprose-3.0-for-dotnet-on-vs2019/</id>
    <published>2019-02-22T12:47:00.000Z</published>
    <updated>2020-01-01T07:49:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>经过一年的开发，Hprose 3.0 for .NET 终于基本上完成了。</p><p>这次升级对 Hprose for .NET 进行了重新设计。去掉了一些不常用的功能，新增插件机制，提升了可扩展性，并提供了许多常用插件，取消了对一些过时的 .NET 平台的支持。仅保留了对 .NET 3.5 Compact Framework、.NET 4.0+、.NET Core 2.0+、.NETStandard 2.0+（包含 Android、iOS、Mac 平台）的支持。</p><p>这次升级后的代码，使用了最新版本的 C# 的语法来编写，代码在可读性和性能上较之之前的版本都有了极大的改进。</p><p>下面我们就来看看 Hprose 3.0 for .NET 在 VS2019 下该如何编译。</p><p>首先，操作系统我使用的是当下的最新版本的 Windows 10（1809-17763.316），其他旧版本的 Windows 操作系统不保证一定可以成功。</p><p>从 <a href="https://github.com/hprose/hprose-dotnet" target="_blank" rel="noopener">hprose/hprose-dotnet</a> 下载最新版本的代码。如果你不打算提交你的修改，最好不要使用 git clone 来下载整个项目，因为使用 git clone 下载的内容有 300 多 M。直接点 Download ZIP 来下载，只有 300 多 K。</p><p>然后下载 <a href="https://visualstudio.microsoft.com/zh-hans/free-developer-offers/" target="_blank" rel="noopener">VS2019</a> 并安装，免费的社区版就可以，专业版和企业版应该也没问题。</p><p>之后，下载最新的 <a href="https://dotnet.microsoft.com/download" target="_blank" rel="noopener">.NET Core 3.1 和 .NET Framework 4.8 开发包</a> 安装。</p><p>接下来，下载 <a href="//download.microsoft.com/download/c/b/e/cbe1c611-7f2f-4bcf-921d-2df718591e1e/NETCFSetupv35.msi">.NET Compact Framework 3.5 Redistributable</a> 并安装。</p><p>.NET Compact Framework 3.5 安装之后位置在：<code>C:\Program Files (x86)\Microsoft.NET\SDK\CompactFramework\v3.5\WindowsCE</code>，将其中的文件复制到：<code>C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\CompactFramework</code>。</p><p>在该目录中新建目录 <code>RedistList</code>，在目录 <code>RedistList</code> 中创建文件 <code>FrameworkList.xml</code>，内容为：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">FileList</span> <span class="attr">Redist</span>=<span class="string">"Net35-CF"</span> <span class="attr">Name</span>=<span class="string">".NET Compact Framework 3.5"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">FileList</span>&gt;</span></span><br></pre></td></tr></table></figure><p>接下来，就可以用 VS2019 打开 <code>Hprose.sln</code> 进行编译了。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;经过一年的开发，Hprose 3.0 for .NET 终于基本上完成了。&lt;/p&gt;
&lt;p&gt;这次升级对 Hprose for .NET 进行了重新设计。去掉了一些不常用的功能，新增插件机制，提升了可扩展性，并提供了许多常用插件，取消了对一些过时的 .NET 平台的支持。仅保留
      
    
    </summary>
    
      <category term="编程" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/"/>
    
      <category term="Hprose" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/Hprose/"/>
    
    
      <category term="c#" scheme="https://coolcode.org/tags/c/"/>
    
      <category term="hprose" scheme="https://coolcode.org/tags/hprose/"/>
    
      <category term=".net" scheme="https://coolcode.org/tags/net/"/>
    
  </entry>
  
  <entry>
    <title>Hprose 3.0 for .NET 的序列化为何这么快</title>
    <link href="https://coolcode.org/2018/05/05/why-the-serialization-of-hprose-3.0-for-dotnet-is-so-fast/"/>
    <id>https://coolcode.org/2018/05/05/why-the-serialization-of-hprose-3.0-for-dotnet-is-so-fast/</id>
    <published>2018-05-05T03:33:00.000Z</published>
    <updated>2019-02-22T12:41:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>经过一年的开发，Hprose 3.0 for .NET 的序列化反序列化部分终于基本上完成了。</p><p>这次升级是完全重写了 Hprose for .NET 的代码。</p><p>之前的 Hprose 1.x for .NET 兼容 .NET 所有的平台版本，包括 .NET Framework 、.NET Compact Framework、.NET Micro Framework、SilverLight、Windows Phone、Mono、.NET Core 等。</p><p>这次升级取消了对一些过时的 .NET 平台的支持。仅保留了对 .NET 3.5 Compact Framework、.NET 4.0+、.NET Core 2.0+、.NETStandard 2.0（包含 Android、iOS、Mac 平台）的支持。</p><p>这次升级后的代码，使用了最新版本的 C# 的语法来编写，代码在可读性和性能上较之之前的版本都有了极大的改进。</p><p>下面我们就来看看 Hprose 3.0 for .NET 序列化究竟有多快。</p><p>首先来看一下对象数组序列化反序列化性能对比，测试代码为：<a href="https://github.com/hprose/hprose-dotnet/blob/master/tests/Hprose.Benchmark/IO/BenchmarkObjectSerialize.cs" target="_blank" rel="noopener">BenchmarkObjectSerialize.cs</a>，测试结果如下表所示：</p><div id="echarts3040" style="width: 85%;height: 400px;margin: 0 auto"></div><script type="text/javascript">var myChart = echarts.init(document.getElementById('echarts3040'));myChart.setOption({    tooltip: {        trigger: "axis",        axisPointer: {            type: "shadow"        }    },    legend: {        data: [".NET Framework 4.7.1 序列化", ".NET Framework 4.7.1 反序列化", ".NET Core 2.0.7 序列化", ".NET Core 2.0.7 反序列化", "Mono 5.4.0 序列化", "Mono 5.4.0 反序列化"],        x: "center",        y: "top",        selectedMode: "multiple"    },    toolbox: {        feature: {            dataView: {                readOnly: true,                show: false            },            magicType: {                type: ["line", "bar", "stack", "tiled"],                show: false            }        }    },    calculable: false,    xAxis: [        {            type: "value",            name: "时间（us）"        }    ],    yAxis: [        {            type: "category",            data: ["Hprose", "Newton", "DataContract"]        }    ],    series: [        {            name: ".NET Framework 4.7.1 序列化",            type: "bar",            data: [1.482, 2.352, 2.955],            stack: ".NET Framework 4.7.1"        },        {            name: ".NET Framework 4.7.1 反序列化",            type: "bar",            data: [1.34, 3.747, 9.119],            stack: ".NET Framework 4.7.1"        },        {            name: ".NET Core 2.0.7 序列化",            type: "bar",            data: [1.434, 2.241, 3.017],            stack: ".NET Core 2.0.7"        },        {            name: ".NET Core 2.0.7 反序列化",            type: "bar",            data: [1.248, 3.711, 9.686],            stack: ".NET Core 2.0.7"        },        {            name: "Mono 5.4.0 序列化",            type: "bar",            data: [3.581, 4.178, 42.502],            stack: "Mono 5.4.0"        },        {            name: "Mono 5.4.0 反序列化",            type: "bar",            data: [3.688, 5.758, 67.104],            stack: "Mono 5.4.0"        }    ],    title: {        text: "对象数组序列化反序列化性能对比",        x: "center",        y: "bottom"    }});</script><p>Hprose 3.0 相对于 1.x 相比，增加了对 DataSet、DataTable 序列化和反序列化的支持。下面是 DataSet 序列化反序列化性能对比，测试代码为：<a href="https://github.com/hprose/hprose-dotnet/blob/master/tests/Hprose.Benchmark/IO/BenchmarkDataSetSerialize.cs" target="_blank" rel="noopener">BenchmarkDataSetSerialize.cs</a>，测试结果如下表所示：</p><div id="echarts2490" style="width: 85%;height: 400px;margin: 0 auto"></div><script type="text/javascript">var myChart = echarts.init(document.getElementById('echarts2490'));myChart.setOption({    tooltip: {        trigger: "axis",        axisPointer: {            type: "shadow"        }    },    legend: {        data: [".NET Framework 4.7.1 序列化", ".NET Framework 4.7.1 反序列化", ".NET Core 2.0.7 序列化", ".NET Core 2.0.7 反序列化", "Mono 5.4.0 序列化", "Mono 5.4.0 反序列化"],        x: "center",        y: "top",        selectedMode: "multiple"    },    toolbox: {        feature: {            dataView: {                readOnly: true,                show: false            },            magicType: {                type: ["line", "bar", "stack", "tiled"],                show: false            }        }    },    calculable: false,    xAxis: [        {            type: "value",            name: "时间（us）"        }    ],    yAxis: [        {            type: "category",            data: ["Hprose", "Newton", "DataContract"]        }    ],    series: [        {            name: ".NET Framework 4.7.1 序列化",            type: "bar",            data: [7.843, 11.061, 131.137],            stack: ".NET Framework 4.7.1"        },        {            name: ".NET Framework 4.7.1 反序列化",            type: "bar",            data: [23.729, 40.371, 442.490],            stack: ".NET Framework 4.7.1"        },        {            name: ".NET Core 2.0.7 序列化",            type: "bar",            data: [7.776, 11.192, 164.812],            stack: ".NET Core 2.0.7"        },        {            name: ".NET Core 2.0.7 反序列化",            type: "bar",            data: [26.891, 42.013, 483.147],            stack: ".NET Core 2.0.7"        },        {            name: "Mono 5.4.0 序列化",            type: "bar",            data: [14.637, 19.407, 243.184],            stack: "Mono 5.4.0"        },        {            name: "Mono 5.4.0 反序列化",            type: "bar",            data: [43.068, 77.235, 641.725],            stack: "Mono 5.4.0"        }    ],    title: {        text: "DataSet 序列化反序列化性能对比",        x: "center",        y: "bottom"    }});</script><p>从上面两个图表可以看出，虽然 Newton Json 的序列化反序列化性能跟 .NET 自带的 DataContract 相比已经高出很多，但是 Hprose 比 Newton Json 还要快 1 倍左右。这是怎么做到的呢？下面我们就来详细剖析一下。</p><a id="more"></a><h1 id="泛型序列化器和反序列化器"><a href="#泛型序列化器和反序列化器" class="headerlink" title="泛型序列化器和反序列化器"></a>泛型序列化器和反序列化器</h1><p>在 Hprose 1.x for .NET 中，序列化和反序列化的代码主要是在 <code>HproseWriter</code> 和 <code>HproseReader</code> 两个类中实现的。</p><p>而 Hprose 3.0 for .NET 中，序列化和反序列化的代码则分别放在 <code>Hprose.IO.Serializers</code> 和 <code>Hprose.IO.Deserializers</code> 两个名称空间下面，并且定义了两个抽象的泛型类 <code>Serializer&lt;T&gt;</code> 和 <code>Deserializer&lt;T&gt;</code> 来负责序列化和反序列化。</p><p>每种具体的数据类型的序列化都由一个具体的序列化器来实现，反序列化则由一个具体的反序列化器来实现。</p><p>具体的序列化器和反序列化器通过 <code>Serializer&lt;T&gt;</code> 和 <code>Deserializer&lt;T&gt;</code> 的 <code>Instance</code> 属性来获得。</p><p>基本类型和几个常用类型的序列化器、反序列化器被注册在 <code>Serializer</code> 和 <code>Deserializer</code> 这两个非泛型类的静态初始化方法中，当它们第一次被调用时会自动初始化。</p><p>而对于数组、枚举、容器和自定义类型，则会在泛型序列化器和反序列化器的 <code>Instance</code> 属性第一次被调用时初始化。</p><p>通过这种方式，实现代码不但变得更清晰易懂，而且更便于扩展。</p><p>另外还有一个附加的好处，就是当知道要序列化或反序列化的具体类型时，序列化器和反序列化器可以直接通过泛型类的 <code>Instance</code> 属性获取到，从而省去了判断查找的时间。</p><p>序列化器和反序列化器除了直接缓存在泛型类的 <code>Instance</code> 属性中以外，还在非泛型的  <code>Serializer</code> 和 <code>Deserializer</code> 类中通过 <code>ConcurrentDictionary</code> 静态字段容器做了缓存。</p><p>虽然通过 <code>ConcurrentDictionary</code> 这种缓存方式要比直接通过泛型类的 <code>Instance</code> 属性来获取序列化器和反序列化器在速度上慢几十纳秒，但是对于无法在编译期就能获取到具体类型的数据来说，这仍然是最快速的获取序列化器和反序列化器的方式。</p><p>除了对序列化器和反序列化器采用了这种特化泛型类 + <code>ConcurrentDictionary</code> 的双缓存模式以外，Hprose 在属性字段存取器、类型转换器等实现上也采用了这种方式。</p><p>这是 Hprose 3.0 for .NET 序列化和反序列化性能提高的最主要原因之一。</p><h1 id="通过表达式树来存取字段和属性"><a href="#通过表达式树来存取字段和属性" class="headerlink" title="通过表达式树来存取字段和属性"></a>通过表达式树来存取字段和属性</h1><p>在 Hprose 1.x for .NET 中，对于自定义类型的字段和属性的存取，根据不同的平台采用了直接反射和 Emit 生成代码两种方式。</p><p>在 Hprose 3.0 for .NET 中，则统一使用了表达式树生成代码的方式。表达式树生成的代码跟使用 Emit 生成的代码，在执行效率上是没有差别的。但是在实现上，表达式树实现的代码具有更好的可读性。</p><p>另外，对于表达式树生成的代码也做了双缓冲，因此序列化反序列化自定义对象的执行效率几乎可以达到甚至超过硬编码的效率。</p><h1 id="通过表达式树来创建对象"><a href="#通过表达式树来创建对象" class="headerlink" title="通过表达式树来创建对象"></a>通过表达式树来创建对象</h1><p>在 C# 中创建一个对象，可以通过 <code>new</code> 关键字来创建，也可以通过反射的方式来创建。跟通过反射创建对象相比，<code>new</code> 一个对象显然要快的多。</p><p>但是创建泛型对象是个特例。例如：</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> T New&lt;T&gt;() <span class="keyword">where</span> T : <span class="keyword">new</span>() =&gt; <span class="keyword">new</span> T();</span><br></pre></td></tr></table></figure><p>这个方法，它在调用时，<code>new T()</code> 生成的 IL 代码实际上跟：</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line">Activator.CreateInstance&lt;T&gt;();</span><br></pre></td></tr></table></figure><p>是差不多的。</p><p>也就是说，虽然代码中写的是 <code>new T()</code>，但是实际上调用的却是 <code>Activator.CreateInstance&lt;T&gt;()</code>。</p><p>Hprose 中为了更快的创建泛型对象，使用了下面这个泛型对象创建工厂：</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line">public static class Factory&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">readonly</span> Func&lt;T&gt; constructor = GetConstructor();</span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Func&lt;T&gt; <span class="title">GetConstructor</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> Expression.Lambda&lt;Func&lt;T&gt;&gt;(Expression.New(<span class="keyword">typeof</span>(T))).Compile();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">catch</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> () =&gt; (T)Activator.CreateInstance(<span class="keyword">typeof</span>(T), <span class="literal">true</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> T <span class="title">New</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> constructor();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该工厂类通过表达式树来生成创建对象的代码，表达式树生成的代码跟直接 <code>new</code> 具体类型是一样的，速度上比 <code>Activator.CreateInstance&lt;T&gt;()</code> 要快 2 - 3 倍（在 Mono 平台上甚至会快几十倍）。只有当表达式树创建失败时，才会使用 <code>Activator.CreateInstance</code> 作为代替方案。另外，这里使用的是 <code>Activator.CreateInstance(typeof(T), true)</code>，这样不但在性能上比 <code>Activator.CreateInstance&lt;T&gt;()</code> 快几纳秒，而且它还可以创建只有非 <code>public</code> 无参构造器的类的对象。</p><p>最新版本的代码可以在 github 的 <a href="https://github.com/hprose/hprose-dotnet" target="_blank" rel="noopener">hprose/hprose-dotnet</a> 中查看。如果大家有更好的改进方式，欢迎大家提交修改。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;经过一年的开发，Hprose 3.0 for .NET 的序列化反序列化部分终于基本上完成了。&lt;/p&gt;
&lt;p&gt;这次升级是完全重写了 Hprose for .NET 的代码。&lt;/p&gt;
&lt;p&gt;之前的 Hprose 1.x for .NET 兼容 .NET 所有的平台版本，包括 .NET Framework 、.NET Compact Framework、.NET Micro Framework、SilverLight、Windows Phone、Mono、.NET Core 等。&lt;/p&gt;
&lt;p&gt;这次升级取消了对一些过时的 .NET 平台的支持。仅保留了对 .NET 3.5 Compact Framework、.NET 4.0+、.NET Core 2.0+、.NETStandard 2.0（包含 Android、iOS、Mac 平台）的支持。&lt;/p&gt;
&lt;p&gt;这次升级后的代码，使用了最新版本的 C# 的语法来编写，代码在可读性和性能上较之之前的版本都有了极大的改进。&lt;/p&gt;
&lt;p&gt;下面我们就来看看 Hprose 3.0 for .NET 序列化究竟有多快。&lt;/p&gt;
&lt;p&gt;首先来看一下对象数组序列化反序列化性能对比，测试代码为：&lt;a href=&quot;https://github.com/hprose/hprose-dotnet/blob/master/tests/Hprose.Benchmark/IO/BenchmarkObjectSerialize.cs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BenchmarkObjectSerialize.cs&lt;/a&gt;，测试结果如下表所示：&lt;/p&gt;
&lt;div id=&quot;echarts3040&quot; style=&quot;width: 85%;height: 400px;margin: 0 auto&quot;&gt;&lt;/div&gt;&lt;script type=&quot;text/javascript&quot;&gt;var myChart = echarts.init(document.getElementById(&#39;echarts3040&#39;));myChart.setOption({
    tooltip: {
        trigger: &quot;axis&quot;,
        axisPointer: {
            type: &quot;shadow&quot;
        }
    },
    legend: {
        data: [&quot;.NET Framework 4.7.1 序列化&quot;, &quot;.NET Framework 4.7.1 反序列化&quot;, &quot;.NET Core 2.0.7 序列化&quot;, &quot;.NET Core 2.0.7 反序列化&quot;, &quot;Mono 5.4.0 序列化&quot;, &quot;Mono 5.4.0 反序列化&quot;],
        x: &quot;center&quot;,
        y: &quot;top&quot;,
        selectedMode: &quot;multiple&quot;
    },
    toolbox: {
        feature: {
            dataView: {
                readOnly: true,
                show: false
            },
            magicType: {
                type: [&quot;line&quot;, &quot;bar&quot;, &quot;stack&quot;, &quot;tiled&quot;],
                show: false
            }
        }
    },
    calculable: false,
    xAxis: [
        {
            type: &quot;value&quot;,
            name: &quot;时间（us）&quot;
        }
    ],
    yAxis: [
        {
            type: &quot;category&quot;,
            data: [&quot;Hprose&quot;, &quot;Newton&quot;, &quot;DataContract&quot;]
        }
    ],
    series: [
        {
            name: &quot;.NET Framework 4.7.1 序列化&quot;,
            type: &quot;bar&quot;,
            data: [1.482, 2.352, 2.955],
            stack: &quot;.NET Framework 4.7.1&quot;
        },
        {
            name: &quot;.NET Framework 4.7.1 反序列化&quot;,
            type: &quot;bar&quot;,
            data: [1.34, 3.747, 9.119],
            stack: &quot;.NET Framework 4.7.1&quot;
        },
        {
            name: &quot;.NET Core 2.0.7 序列化&quot;,
            type: &quot;bar&quot;,
            data: [1.434, 2.241, 3.017],
            stack: &quot;.NET Core 2.0.7&quot;
        },
        {
            name: &quot;.NET Core 2.0.7 反序列化&quot;,
            type: &quot;bar&quot;,
            data: [1.248, 3.711, 9.686],
            stack: &quot;.NET Core 2.0.7&quot;
        },
        {
            name: &quot;Mono 5.4.0 序列化&quot;,
            type: &quot;bar&quot;,
            data: [3.581, 4.178, 42.502],
            stack: &quot;Mono 5.4.0&quot;
        },
        {
            name: &quot;Mono 5.4.0 反序列化&quot;,
            type: &quot;bar&quot;,
            data: [3.688, 5.758, 67.104],
            stack: &quot;Mono 5.4.0&quot;
        }
    ],
    title: {
        text: &quot;对象数组序列化反序列化性能对比&quot;,
        x: &quot;center&quot;,
        y: &quot;bottom&quot;
    }
});&lt;/script&gt;
&lt;p&gt;Hprose 3.0 相对于 1.x 相比，增加了对 DataSet、DataTable 序列化和反序列化的支持。下面是 DataSet 序列化反序列化性能对比，测试代码为：&lt;a href=&quot;https://github.com/hprose/hprose-dotnet/blob/master/tests/Hprose.Benchmark/IO/BenchmarkDataSetSerialize.cs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BenchmarkDataSetSerialize.cs&lt;/a&gt;，测试结果如下表所示：&lt;/p&gt;
&lt;div id=&quot;echarts2490&quot; style=&quot;width: 85%;height: 400px;margin: 0 auto&quot;&gt;&lt;/div&gt;&lt;script type=&quot;text/javascript&quot;&gt;var myChart = echarts.init(document.getElementById(&#39;echarts2490&#39;));myChart.setOption({
    tooltip: {
        trigger: &quot;axis&quot;,
        axisPointer: {
            type: &quot;shadow&quot;
        }
    },
    legend: {
        data: [&quot;.NET Framework 4.7.1 序列化&quot;, &quot;.NET Framework 4.7.1 反序列化&quot;, &quot;.NET Core 2.0.7 序列化&quot;, &quot;.NET Core 2.0.7 反序列化&quot;, &quot;Mono 5.4.0 序列化&quot;, &quot;Mono 5.4.0 反序列化&quot;],
        x: &quot;center&quot;,
        y: &quot;top&quot;,
        selectedMode: &quot;multiple&quot;
    },
    toolbox: {
        feature: {
            dataView: {
                readOnly: true,
                show: false
            },
            magicType: {
                type: [&quot;line&quot;, &quot;bar&quot;, &quot;stack&quot;, &quot;tiled&quot;],
                show: false
            }
        }
    },
    calculable: false,
    xAxis: [
        {
            type: &quot;value&quot;,
            name: &quot;时间（us）&quot;
        }
    ],
    yAxis: [
        {
            type: &quot;category&quot;,
            data: [&quot;Hprose&quot;, &quot;Newton&quot;, &quot;DataContract&quot;]
        }
    ],
    series: [
        {
            name: &quot;.NET Framework 4.7.1 序列化&quot;,
            type: &quot;bar&quot;,
            data: [7.843, 11.061, 131.137],
            stack: &quot;.NET Framework 4.7.1&quot;
        },
        {
            name: &quot;.NET Framework 4.7.1 反序列化&quot;,
            type: &quot;bar&quot;,
            data: [23.729, 40.371, 442.490],
            stack: &quot;.NET Framework 4.7.1&quot;
        },
        {
            name: &quot;.NET Core 2.0.7 序列化&quot;,
            type: &quot;bar&quot;,
            data: [7.776, 11.192, 164.812],
            stack: &quot;.NET Core 2.0.7&quot;
        },
        {
            name: &quot;.NET Core 2.0.7 反序列化&quot;,
            type: &quot;bar&quot;,
            data: [26.891, 42.013, 483.147],
            stack: &quot;.NET Core 2.0.7&quot;
        },
        {
            name: &quot;Mono 5.4.0 序列化&quot;,
            type: &quot;bar&quot;,
            data: [14.637, 19.407, 243.184],
            stack: &quot;Mono 5.4.0&quot;
        },
        {
            name: &quot;Mono 5.4.0 反序列化&quot;,
            type: &quot;bar&quot;,
            data: [43.068, 77.235, 641.725],
            stack: &quot;Mono 5.4.0&quot;
        }
    ],
    title: {
        text: &quot;DataSet 序列化反序列化性能对比&quot;,
        x: &quot;center&quot;,
        y: &quot;bottom&quot;
    }
});&lt;/script&gt;
&lt;p&gt;从上面两个图表可以看出，虽然 Newton Json 的序列化反序列化性能跟 .NET 自带的 DataContract 相比已经高出很多，但是 Hprose 比 Newton Json 还要快 1 倍左右。这是怎么做到的呢？下面我们就来详细剖析一下。&lt;/p&gt;
    
    </summary>
    
      <category term="编程" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/"/>
    
      <category term="Hprose" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/Hprose/"/>
    
    
      <category term="c#" scheme="https://coolcode.org/tags/c/"/>
    
      <category term="hprose" scheme="https://coolcode.org/tags/hprose/"/>
    
      <category term=".net" scheme="https://coolcode.org/tags/net/"/>
    
  </entry>
  
  <entry>
    <title>将阿里云服务器升级到 debian 9 的全过程记录</title>
    <link href="https://coolcode.org/2018/04/03/upgrade-to-debian9-on-aliyun/"/>
    <id>https://coolcode.org/2018/04/03/upgrade-to-debian9-on-aliyun/</id>
    <published>2018-04-03T14:33:00.000Z</published>
    <updated>2018-04-03T19:04:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>今天装了一台阿里云上的服务器，打算安装 nginx、php 和 mysql。系统一开始是 debian 8，所以需要先升级一下。</p><p>首先进入 <code>/etc/apt/sources.list.d</code> 目录，里面有个阿里云安装源的配置文件，把它内容删除，然后改成：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">deb http://mirrors.aliyun.com/debian stretch main contrib non-free</span><br><span class="line">deb-src http://mirrors.aliyun.com/debian stretch main contrib non-free</span><br><span class="line">deb http://mirrors.aliyun.com/debian stretch-updates main contrib non-free</span><br><span class="line">deb-src http://mirrors.aliyun.com/debian stretch-updates main contrib non-free</span><br><span class="line">deb http://mirrors.aliyun.com/debian stretch-backports main non-free contrib</span><br><span class="line">deb-src http://mirrors.aliyun.com/debian stretch-backports main non-free contrib</span><br><span class="line">deb http://mirrors.aliyun.com/debian-security stretch/updates main contrib non-free</span><br><span class="line">deb-src http://mirrors.aliyun.com/debian-security stretch/updates main contrib non-free</span><br></pre></td></tr></table></figure><p>接下来执行：</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo apt update</span><br><span class="line">sudo apt dist-upgrade</span><br></pre></td></tr></table></figure><p>然后确认一下，就是漫长的等待了。下载完所有的安装包之后，会有一个升级列表说明，看不看无所谓了，直接输入 <kbd>q</kbd> 就可以退出继续了。后面会遇到几处确认，因为是新装的系统，所以没什么顾及，都选 <kbd>Y</kbd> 就可以了。</p><p>安装完成后，再执行：</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo apt autoremove --purge</span><br></pre></td></tr></table></figure><p>把没有用的包清理掉就可以了。</p><a id="more"></a><h1 id="装逼是必须的"><a href="#装逼是必须的" class="headerlink" title="装逼是必须的"></a>装逼是必须的</h1><p>接下来，安装装逼工具 neofetch。</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo apt install neofetch</span><br></pre></td></tr></table></figure><p>安装之后执行一下 <code>neofetch</code> 就可以看到如下装逼画面了：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/04/03/upgrade-to-debian9-on-aliyun/neofetch.png" alt="neofetch" title>                </div>                <div class="image-caption">neofetch</div>            </figure><h1 id="vim-是不可少的"><a href="#vim-是不可少的" class="headerlink" title="vim 是不可少的"></a>vim 是不可少的</h1><p>接下来安装 <code>vim</code>，不然编辑个配置文件都麻烦。执行下面的命令：</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo apt install vim-nox vim-addon-manager vim-syntastic vim-youcompleteme vim-fugitive</span><br><span class="line">wget https://mirrors.aliyun.com/debian/pool/main/v/vim-airline-themes/vim-airline-themes_0%2bgit.20170710.5d75d76-1_all.deb</span><br><span class="line">wget https://mirrors.aliyun.com/debian/pool/main/v/vim-airline/vim-airline_0.9-1_all.deb</span><br><span class="line">sudo dpkg -i vim-airline-themes_0+git.20170710.5d75d76-1_all.deb vim-airline_0.9-1_all.deb</span><br><span class="line">vim-addons install youcompleteme</span><br><span class="line">vim-addons</span><br></pre></td></tr></table></figure><p>上面装的是 <code>nox</code> 版本（就是没有 X，即非图形界面的版本）的 <code>vim</code>，这个版本跟普通的 <code>vim</code> 包比，多了 <code>python</code> 插件的支持，不然的话，很多插件没法用。</p><p>另外因为 <code>airline</code> 目前只在 <code>sid</code> 里面有，在 <code>stretch</code> 里面没有，所以需要单独下载。不过还好这两个包 <code>vim-airline</code> 和 <code>vim-airline-themes</code> 没有什么依赖，直接从 <code>sid</code> 仓库里使用 <code>wget</code> 下载下来，用 <code>dpkg</code> 安装就可以了。</p><p>执行完上面的命令之后，会看到下面的输出：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"># Name                     User Status  System Status</span><br><span class="line">airline                     removed       installed</span><br><span class="line">airline-themes              removed       installed</span><br><span class="line">editexisting                removed       removed</span><br><span class="line">fugitive                    removed       installed</span><br><span class="line">justify                     removed       removed</span><br><span class="line">matchit                     removed       removed</span><br><span class="line">syntastic                   removed       installed</span><br><span class="line">systemtap                   removed       removed</span><br><span class="line">youcompleteme               installed     removed</span><br></pre></td></tr></table></figure><p>说明 <code>airline</code>, <code>fugitive</code>, <code>syntasic</code> 和 <code>youcompleteme</code> 这几个插件装好了。</p><p>只是装好还不够，还需要配置一下，首先打开 <code>/etc/vim/vimrc</code> 文件。找到：</p><figure class="highlight vim"><table><tr><td class="code"><pre><span class="line"><span class="comment">"syntax on</span></span><br><span class="line"></span><br><span class="line"><span class="comment">"set background=dark</span></span><br></pre></td></tr></table></figure><p>这两句，然后把它们前面的 <code>&quot;</code> 去掉，保存，然后再用 <code>vim</code> 打开就能看到彩色文字了。</p><p>接下来，把下面这一段保存到 <code>/etc/vim/vimrc.local</code> 文件中。</p><figure class="highlight vim"><figcaption><span>vimrc.local</span><a href="/2018/04/03/upgrade-to-debian9-on-aliyun/vimrc.local">下载</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment">"Ctrl+C, Ctrl+V, Ctrl+X, Ctrl+A, Ctrl+Y, Ctrl+Z, Ctrl+S 跟 windows 一样</span></span><br><span class="line">runtime! mswin.<span class="keyword">vim</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:airline_powerline_fonts</span>=<span class="number">1</span></span><br><span class="line"><span class="keyword">set</span> laststatus=<span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:airline</span>#extensions#tabline#enabled=<span class="number">1</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:airline</span>#extensions#tabline#buffer_nr_show=<span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:airline</span>#extensions#ycm#enabled=<span class="number">1</span></span><br><span class="line"></span><br><span class="line">autocmd! bufwritepost .vimrc <span class="keyword">source</span> % <span class="comment">"vimrc文件修改之后自动加载。 linux。</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">set</span> completeopt=longest,<span class="keyword">menu</span> <span class="comment">"自动补全配置,让Vim的补全菜单行为与一般IDE一致</span></span><br><span class="line"><span class="keyword">set</span> cursorcolumn             <span class="comment">"突出显示当前列，可用Ctrl+m切换是否显示</span></span><br><span class="line"><span class="keyword">set</span> cursorline               <span class="comment">"突出显示当前行，可用Ctrl+m切换是否显示</span></span><br><span class="line"><span class="keyword">set</span> <span class="keyword">history</span>=<span class="number">3000</span>             <span class="comment">"history存储长度</span></span><br><span class="line"><span class="keyword">set</span> <span class="keyword">nu</span>                       <span class="comment">"显示行数</span></span><br><span class="line"><span class="keyword">set</span> <span class="built_in">shiftwidth</span>=<span class="number">4</span>             <span class="comment">"换行时行间交错使用4空格</span></span><br><span class="line"><span class="keyword">set</span> <span class="built_in">cindent</span> <span class="built_in">shiftwidth</span>=<span class="number">4</span>     <span class="comment">"自动缩进4空格</span></span><br><span class="line"><span class="keyword">set</span> tabstop=<span class="number">4</span>                <span class="comment">"让一个tab等于4个空格</span></span><br><span class="line"><span class="keyword">set</span> showmatch                <span class="comment">"显示括号配对情况</span></span><br><span class="line"><span class="keyword">set</span> autoread                 <span class="comment">"当文件在外部被改变时，Vim自动更新载入</span></span><br><span class="line"><span class="keyword">set</span> noswapfile               <span class="comment">"关闭交换文件</span></span><br><span class="line"><span class="keyword">set</span> showmode                 <span class="comment">"开启模式显示</span></span><br><span class="line"><span class="keyword">set</span> cmdheight=<span class="number">1</span>              <span class="comment">"命令部分高度为1</span></span><br><span class="line"><span class="keyword">set</span> shortmess=atI            <span class="comment">"启动的时候不显示那个援助索马里儿童的提示</span></span><br><span class="line"><span class="keyword">set</span> t_ti= t_te=              <span class="comment">"退出vim后，内容显示在终端屏幕</span></span><br><span class="line"><span class="keyword">set</span> novisualbell             <span class="comment">"don't beep</span></span><br><span class="line"><span class="keyword">set</span> noerrorbells             <span class="comment">"don't beep</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">set</span> wildmode=lis<span class="variable">t:longest</span></span><br><span class="line"><span class="keyword">set</span> ttyfast</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">set</span> wildignore=*.swp,*.bak,*.pyc,*.class</span><br><span class="line"><span class="keyword">set</span> scrolloff=<span class="number">3</span>              <span class="comment">"至少有3行在光标所在行上下</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">set</span> selection=old</span><br><span class="line"><span class="keyword">set</span> selectmode=mouse,key</span><br><span class="line"><span class="keyword">set</span> viminfo^=%               <span class="comment">"Remember info about open buffers on close</span></span><br><span class="line"><span class="keyword">set</span> viminfo+=!               <span class="comment">"保存全局变量</span></span><br><span class="line"><span class="keyword">set</span> magic                    <span class="comment">"正则表达式匹配形式</span></span><br><span class="line"></span><br><span class="line"><span class="string">"autocmd InsertEnter * se cul "</span>用浅色高亮当前行</span><br><span class="line"><span class="keyword">set</span> ruler                    <span class="comment">"显示标尺</span></span><br><span class="line"><span class="keyword">set</span> showcmd                  <span class="comment">"输入的命令显示出来，看的清楚些</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">"设置标记一列的背景颜色和数字一行颜色一致</span></span><br><span class="line">hi! link SignColumn   LineNr</span><br><span class="line">hi! link ShowMarksHLl DiffAdd</span><br><span class="line">hi! link ShowMarksHLu DiffChange</span><br><span class="line"></span><br><span class="line"><span class="keyword">set</span> hlsearch                 <span class="comment">"高亮显示结果</span></span><br><span class="line"><span class="keyword">set</span> incsearch                <span class="comment">"在输入要搜索的文字时，vim会实时匹配</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span></span><br><span class="line"><span class="comment">" F1 - F4 设置</span></span><br><span class="line"><span class="comment">" F1 废弃这个键,防止调出系统帮助</span></span><br><span class="line"><span class="comment">" F2 行号开关，用于鼠标复制代码用</span></span><br><span class="line"><span class="comment">" F3 换行开关</span></span><br><span class="line"><span class="comment">" F4 粘贴模式paste_mode开关,用于有格式的代码粘贴</span></span><br><span class="line"><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span><span class="string">""</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">" I can type :help on my own, thanks.  Protect your fat fingers from the evils of &lt;F1&gt;</span></span><br><span class="line"><span class="keyword">nnoremap</span> <span class="symbol">&lt;F1&gt;</span> <span class="symbol">&lt;Esc&gt;</span><span class="comment">"</span></span><br><span class="line"><span class="keyword">nnoremap</span> <span class="symbol">&lt;F2&gt;</span> :<span class="keyword">set</span> nonumber! <span class="keyword">number</span>?<span class="symbol">&lt;CR&gt;</span></span><br><span class="line"><span class="keyword">nnoremap</span> <span class="symbol">&lt;F3&gt;</span> :<span class="keyword">set</span> wrap! wrap?<span class="symbol">&lt;CR&gt;</span></span><br><span class="line"><span class="keyword">set</span> pastetoggle=<span class="symbol">&lt;F4&gt;</span>             <span class="comment">"when in insert mode, press &lt;F5&gt; to go to</span></span><br><span class="line">                                 <span class="comment">"paste mode, where you can paste mass data</span></span><br><span class="line">                                 <span class="comment">"that won't be autoindented</span></span><br><span class="line"><span class="keyword">au</span> InsertLeave * <span class="keyword">set</span> nopaste</span><br><span class="line"></span><br><span class="line"><span class="comment">" disbale paste mode when leaving insert mode</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">"Treat long lines as break lines (useful when moving around in them)</span></span><br><span class="line"><span class="comment">"se swap之后，同物理行上线直接跳</span></span><br><span class="line"><span class="keyword">nnoremap</span> <span class="keyword">k</span> gk</span><br><span class="line"><span class="keyword">nnoremap</span> gk <span class="keyword">k</span></span><br><span class="line"><span class="keyword">nnoremap</span> <span class="keyword">j</span> gj</span><br><span class="line"><span class="keyword">nnoremap</span> gj <span class="keyword">j</span></span><br><span class="line"><span class="keyword">nnoremap</span> <span class="symbol">&lt;Up&gt;</span> gk</span><br><span class="line"><span class="keyword">nnoremap</span> <span class="symbol">&lt;Down&gt;</span> gj</span><br><span class="line"></span><br><span class="line"><span class="keyword">colorscheme</span> torte</span><br><span class="line"></span><br><span class="line"><span class="comment">" 多语言语法检查</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:syntastic_error_symbol</span>=<span class="string">'✗'</span>      <span class="comment">"set error or warning signs</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:syntastic_warning_symbol</span>=<span class="string">'⚠'</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:syntastic_check_on_open</span>=<span class="number">1</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:syntastic_enable_highlighting</span>=<span class="number">0</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:syntastic_python_checkers</span>=[<span class="string">'pyflakes'</span>] <span class="comment">" 使用pyflakes,速度比pylint快</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:syntastic_javascript_checkers</span> = [<span class="string">'jsl'</span>, <span class="string">'jshint'</span>]</span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:syntastic_html_checkers</span>=[<span class="string">'tidy'</span>, <span class="string">'jshint'</span>]</span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:syntastic_cpp_include_dirs</span> = [<span class="string">'/usr/include/'</span>]</span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:syntastic_cpp_remove_include_errors</span> = <span class="number">1</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:syntastic_cpp_check_header</span> = <span class="number">1</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:syntastic_cpp_compiler</span> = <span class="string">'clang++'</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:syntastic_cpp_compiler_options</span> = <span class="string">'-std=c++11 -stdlib=libstdc++'</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:syntastic_enable_balloons</span> = <span class="number">1</span> <span class="comment">"whether to show balloons</span></span><br><span class="line"><span class="keyword">highlight</span> SyntasticErrorSign guifg=white guibg=black</span><br></pre></td></tr></table></figure><p>最后，在自己的目录下创建一个 .vimrc 文件，哪怕是空的也行。如果没有这个文件，你会发现有些配置不会生效。</p><p>最后把没用的 <code>vim</code>, <code>vim-tiny</code> 卸载掉就好了。</p><h1 id="命令行不爽是不行的"><a href="#命令行不爽是不行的" class="headerlink" title="命令行不爽是不行的"></a>命令行不爽是不行的</h1><p>接下来安装几个命令行增强工具：</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo apt-get install powerline htop thefuck</span><br><span class="line">wget https://github.com/jingweno/ccat/releases/download/v1.1.0/linux-amd64-1.1.0.tar.gz</span><br><span class="line">tar zxvfp linux-amd64-1.1.0.tar.gz</span><br><span class="line">sudo cp linux-amd64-1.1.0/ccat /usr/bin/ccat</span><br><span class="line">sudo chmod +x /usr/bin/ccat</span><br></pre></td></tr></table></figure><p>然后配置一下 <code>.bashrc</code> 文件：</p><figure class="highlight sh"><figcaption><span>.bashrc</span><a href="/2018/04/03/upgrade-to-debian9-on-aliyun/bashrc">下载</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># ~/.bashrc: executed by bash(1) for non-login shells.</span></span><br><span class="line"><span class="comment"># see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)</span></span><br><span class="line"><span class="comment"># for examples</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># If not running interactively, don't do anything</span></span><br><span class="line"><span class="keyword">case</span> $- <span class="keyword">in</span></span><br><span class="line">    *i*) ;;</span><br><span class="line">      *) <span class="built_in">return</span>;;</span><br><span class="line"><span class="keyword">esac</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># don't put duplicate lines or lines starting with space in the history.</span></span><br><span class="line"><span class="comment"># See bash(1) for more options</span></span><br><span class="line">HISTCONTROL=ignoreboth</span><br><span class="line"></span><br><span class="line"><span class="comment"># append to the history file, don't overwrite it</span></span><br><span class="line"><span class="built_in">shopt</span> -s histappend</span><br><span class="line"></span><br><span class="line"><span class="comment"># for setting history length see HISTSIZE and HISTFILESIZE in bash(1)</span></span><br><span class="line">HISTSIZE=1000</span><br><span class="line">HISTFILESIZE=2000</span><br><span class="line"></span><br><span class="line"><span class="comment"># check the window size after each command and, if necessary,</span></span><br><span class="line"><span class="comment"># update the values of LINES and COLUMNS.</span></span><br><span class="line"><span class="built_in">shopt</span> -s checkwinsize</span><br><span class="line"></span><br><span class="line"><span class="comment"># If set, the pattern "**" used in a pathname expansion context will</span></span><br><span class="line"><span class="comment"># match all files and zero or more directories and subdirectories.</span></span><br><span class="line"><span class="comment">#shopt -s globstar</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># make less more friendly for non-text input files, see lesspipe(1)</span></span><br><span class="line"><span class="comment">#[ -x /usr/bin/lesspipe ] &amp;&amp; eval "$(SHELL=/bin/sh lesspipe)"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># set variable identifying the chroot you work in (used in the prompt below)</span></span><br><span class="line"><span class="keyword">if</span> [ -z <span class="string">"<span class="variable">$&#123;debian_chroot:-&#125;</span>"</span> ] &amp;&amp; [ -r /etc/debian_chroot ]; <span class="keyword">then</span></span><br><span class="line">    debian_chroot=$(cat /etc/debian_chroot)</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># enable color support of ls and also add handy aliases</span></span><br><span class="line"><span class="keyword">if</span> [ -x /usr/bin/dircolors ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">test</span> -r ~/.dircolors &amp;&amp; <span class="built_in">eval</span> <span class="string">"<span class="variable">$(dircolors -b ~/.dircolors)</span>"</span> || <span class="built_in">eval</span> <span class="string">"<span class="variable">$(dircolors -b)</span>"</span></span><br><span class="line">    <span class="built_in">alias</span> ls=<span class="string">'ls --color=auto'</span></span><br><span class="line">    <span class="built_in">alias</span> dir=<span class="string">'dir --color=auto'</span></span><br><span class="line">    <span class="built_in">alias</span> vdir=<span class="string">'vdir --color=auto'</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">alias</span> grep=<span class="string">'grep --color=auto'</span></span><br><span class="line">    <span class="built_in">alias</span> fgrep=<span class="string">'fgrep --color=auto'</span></span><br><span class="line">    <span class="built_in">alias</span> egrep=<span class="string">'egrep --color=auto'</span></span><br><span class="line"><span class="built_in">alias</span> cat=<span class="string">'ccat --color=always --bg=dark'</span></span><br><span class="line"><span class="built_in">alias</span> top=<span class="string">'htop'</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># colored GCC warnings and errors</span></span><br><span class="line"><span class="built_in">export</span> GCC_COLORS=<span class="string">'error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># some more ls aliases</span></span><br><span class="line"><span class="built_in">alias</span> ll=<span class="string">'ls -l'</span></span><br><span class="line"><span class="built_in">alias</span> la=<span class="string">'ls -A'</span></span><br><span class="line"><span class="built_in">alias</span> l=<span class="string">'ls -CF'</span></span><br><span class="line"><span class="built_in">alias</span> cp=<span class="string">'cp -i'</span></span><br><span class="line"><span class="built_in">alias</span> mv=<span class="string">'mv -i'</span></span><br><span class="line"><span class="built_in">alias</span> rm=<span class="string">'rm -i'</span></span><br><span class="line"><span class="built_in">alias</span> fuck=<span class="string">'TF_CMD=$(TF_ALIAS=fuck PYTHONIOENCODING=utf-8 TF_SHELL_ALIASES=$(alias) thefuck $(fc -ln -1)) &amp;&amp; eval $TF_CMD &amp;&amp; history -s $TF_CMD'</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Alias definitions.</span></span><br><span class="line"><span class="comment"># You may want to put all your additions into a separate file like</span></span><br><span class="line"><span class="comment"># ~/.bash_aliases, instead of adding them here directly.</span></span><br><span class="line"><span class="comment"># See /usr/share/doc/bash-doc/examples in the bash-doc package.</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ -f ~/.bash_aliases ]; <span class="keyword">then</span></span><br><span class="line">    . ~/.bash_aliases</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># enable programmable completion features (you don't need to enable</span></span><br><span class="line"><span class="comment"># this, if it's already enabled in /etc/bash.bashrc and /etc/profile</span></span><br><span class="line"><span class="comment"># sources /etc/bash.bashrc).</span></span><br><span class="line"><span class="keyword">if</span> ! <span class="built_in">shopt</span> -oq posix; <span class="keyword">then</span></span><br><span class="line">  <span class="keyword">if</span> [ -f /usr/share/bash-completion/bash_completion ]; <span class="keyword">then</span></span><br><span class="line">    . /usr/share/bash-completion/bash_completion</span><br><span class="line">  <span class="keyword">elif</span> [ -f /etc/bash_completion ]; <span class="keyword">then</span></span><br><span class="line">    . /etc/bash_completion</span><br><span class="line">  <span class="keyword">fi</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">export</span> PATH=/snap/bin:<span class="variable">$PATH</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">export</span> LESS_TERMCAP_mb=$<span class="string">'\E[01;31m'</span></span><br><span class="line"><span class="built_in">export</span> LESS_TERMCAP_md=$<span class="string">'\E[01;31m'</span></span><br><span class="line"><span class="built_in">export</span> LESS_TERMCAP_me=$<span class="string">'\E[0m'</span></span><br><span class="line"><span class="built_in">export</span> LESS_TERMCAP_se=$<span class="string">'\E[0m'</span></span><br><span class="line"><span class="built_in">export</span> LESS_TERMCAP_so=$<span class="string">'\E[01;44;33m'</span></span><br><span class="line"><span class="built_in">export</span> LESS_TERMCAP_ue=$<span class="string">'\E[0m'</span></span><br><span class="line"><span class="built_in">export</span> LESS_TERMCAP_us=$<span class="string">'\E[01;32m'</span></span><br><span class="line"></span><br><span class="line">stty -ixon <span class="comment"># Ctrl+S 会停止输出，该命令让停止输出失效。</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ -f `<span class="built_in">which</span> powerline-daemon` ]; <span class="keyword">then</span></span><br><span class="line">  powerline-daemon -q</span><br><span class="line">  POWERLINE_BASH_CONTINUATION=1</span><br><span class="line">  POWERLINE_BASH_SELECT=1</span><br><span class="line">  . /usr/share/powerline/bindings/bash/powerline.sh</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">neofetch</span><br></pre></td></tr></table></figure><p>然后退出再登录，就可以看到酷炫的命令提示符了。还有彩色的 <code>ls</code>, <code>cat</code>, <code>top</code>, <code>man</code> 等。还可以使用 <code>fuck</code> 命令来纠正输入错误。</p><h1 id="开始干正事了"><a href="#开始干正事了" class="headerlink" title="开始干正事了"></a>开始干正事了</h1><p>先配置 PHP 最新版本的源。</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo apt -y install apt-transport-https lsb-release ca-certificates</span><br><span class="line">sudo wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg</span><br><span class="line">sudo sh -c <span class="string">'echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" &gt; /etc/apt/sources.list.d/php.list'</span></span><br><span class="line">sudo apt update</span><br></pre></td></tr></table></figure><p>再接下来配置 mysql 的源。</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">wget https://dev.mysql.com/get/mysql-apt-config_0.8.9-1_all.deb</span><br><span class="line">sudo dpkg -i mysql-apt-config_0.8.9-1_all.deb</span><br><span class="line">sudo apt update</span><br></pre></td></tr></table></figure><p>再接下来配置 nodejs 最新版本的源。</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">curl -sL https://deb.nodesource.com/setup_9.x | sudo bash -</span><br></pre></td></tr></table></figure><p>最后一步就是安装它们：</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo apt install -y php7.2 php7.2-fpm php7.2-mysql php7.2-json php7.2-mbstring php7.2-curl php7.2-gd php7.2-bcmath php7.2-opcache php7.2-bz2 php7.2-zip php7.2-dev php-pear nginx mysql-server nodejs</span><br></pre></td></tr></table></figure><p>接下来安装 <code>phpmyadmin</code>:</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo apt install phpmyadmin</span><br></pre></td></tr></table></figure><p>安装过程中要选择 Apache 还是 lighttpd，都不选，直接点 OK。然后配置密码就完成了。</p><p>然后执行：</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo ln -s /usr/share/phpmyadmin/ /var/www/</span><br></pre></td></tr></table></figure><p>最后配置一下 nginx，再重启 nginx 就可以了。</p><p>接下来安装 php 的 hprose 扩展：</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo pecl update-channels</span><br><span class="line">sudo pecl install hprose</span><br></pre></td></tr></table></figure><p>在 <code>/etc/php/7.2/mods-available</code> 下创建文件 <code>hprose.ini</code>：</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment">; configuration for php hprose module</span></span><br><span class="line"><span class="comment">; priority=20</span></span><br><span class="line"><span class="attr">extension</span>=hprose.so</span><br></pre></td></tr></table></figure><p>然后执行：</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo phpenmod hprose</span><br><span class="line">sudo service php7.2-fpm restart</span><br></pre></td></tr></table></figure><p><code>hprose</code> 扩展就装好了。</p><h1 id="系统优化"><a href="#系统优化" class="headerlink" title="系统优化"></a>系统优化</h1><p>阿里云服务器安装之后，没有交换分区，这样一旦内存用光，系统就会进入假死状态。所以必须要创建一个 swap 文件。不过不需要自己手动创建，直接安装 <code>dphys-swapfile</code> 这个包就可以了。</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo apt install dphys-swapfile</span><br></pre></td></tr></table></figure><p>安装完成后， 运行 <code>free</code> 就可以看到虚拟内存已经启用了。</p><p>修改 <code>/etc/sysctl.conf</code>, 在最后加上：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">vm.swappiness = 10</span><br><span class="line">net.ipv4.neigh.default.gc_stale_time = 120</span><br><span class="line">net.ipv4.conf.all.rp_filter = 0</span><br><span class="line">net.ipv4.conf.default.rp_filter = 0</span><br><span class="line">net.ipv4.conf.default.arp_announce = 2</span><br><span class="line">net.ipv4.conf.lo.arp_announce = 2</span><br><span class="line">net.ipv4.conf.all.arp_announce = 2</span><br><span class="line">net.ipv4.tcp_max_tw_buckets = 5000</span><br><span class="line">net.ipv4.tcp_syncookies = 1</span><br><span class="line">net.ipv4.tcp_max_syn_backlog = 1024</span><br><span class="line">net.ipv4.tcp_synack_retries = 2</span><br><span class="line">net.ipv6.conf.all.disable_ipv6 = 1</span><br><span class="line">net.ipv6.conf.default.disable_ipv6 = 1</span><br><span class="line">net.ipv6.conf.lo.disable_ipv6 = 1</span><br><span class="line"></span><br><span class="line">net.ipv4.tcp_syn_retries = 1</span><br><span class="line">net.ipv4.tcp_synack_retries = 1</span><br><span class="line">net.ipv4.tcp_keepalive_time = 600</span><br><span class="line">net.ipv4.tcp_keepalive_probes = 3</span><br><span class="line">net.ipv4.tcp_keepalive_intvl =15</span><br><span class="line">net.ipv4.tcp_retries2 = 5</span><br><span class="line">net.ipv4.tcp_fin_timeout = 2</span><br><span class="line">net.ipv4.tcp_max_tw_buckets = 36000</span><br><span class="line">net.ipv4.tcp_tw_recycle = 1</span><br><span class="line">net.ipv4.tcp_tw_reuse = 1</span><br><span class="line">net.ipv4.tcp_max_orphans = 131072</span><br><span class="line">net.ipv4.tcp_syncookies = 1</span><br><span class="line">net.ipv4.tcp_max_syn_backlog = 16384</span><br><span class="line">net.ipv4.tcp_wmem = 8192 131072 16777216</span><br><span class="line">net.ipv4.tcp_rmem = 32768 131072 16777216</span><br><span class="line">net.ipv4.tcp_mem = 786432 1048576 1572864</span><br><span class="line">net.ipv4.ip_local_port_range = 1024 65000</span><br><span class="line"></span><br><span class="line">net.core.somaxconn = 16384</span><br><span class="line">net.core.netdev_max_backlog = 16384</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;今天装了一台阿里云上的服务器，打算安装 nginx、php 和 mysql。系统一开始是 debian 8，所以需要先升级一下。&lt;/p&gt;
&lt;p&gt;首先进入 &lt;code&gt;/etc/apt/sources.list.d&lt;/code&gt; 目录，里面有个阿里云安装源的配置文件，把它内容删除，然后改成：&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;deb http://mirrors.aliyun.com/debian stretch main contrib non-free&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;deb-src http://mirrors.aliyun.com/debian stretch main contrib non-free&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;deb http://mirrors.aliyun.com/debian stretch-updates main contrib non-free&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;deb-src http://mirrors.aliyun.com/debian stretch-updates main contrib non-free&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;deb http://mirrors.aliyun.com/debian stretch-backports main non-free contrib&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;deb-src http://mirrors.aliyun.com/debian stretch-backports main non-free contrib&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;deb http://mirrors.aliyun.com/debian-security stretch/updates main contrib non-free&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;deb-src http://mirrors.aliyun.com/debian-security stretch/updates main contrib non-free&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;接下来执行：&lt;/p&gt;
&lt;figure class=&quot;highlight sh&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;sudo apt update&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;sudo apt dist-upgrade&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;然后确认一下，就是漫长的等待了。下载完所有的安装包之后，会有一个升级列表说明，看不看无所谓了，直接输入 &lt;kbd&gt;q&lt;/kbd&gt; 就可以退出继续了。后面会遇到几处确认，因为是新装的系统，所以没什么顾及，都选 &lt;kbd&gt;Y&lt;/kbd&gt; 就可以了。&lt;/p&gt;
&lt;p&gt;安装完成后，再执行：&lt;/p&gt;
&lt;figure class=&quot;highlight sh&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;sudo apt autoremove --purge&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;把没有用的包清理掉就可以了。&lt;/p&gt;
    
    </summary>
    
      <category term="操作系统" scheme="https://coolcode.org/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    
      <category term="Debian" scheme="https://coolcode.org/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/Debian/"/>
    
    
      <category term="debian" scheme="https://coolcode.org/tags/debian/"/>
    
  </entry>
  
  <entry>
    <title>创建面向多目标框架的.NET项目</title>
    <link href="https://coolcode.org/2018/03/26/create-a-dotnet-project-for-multiple-target-frameworks/"/>
    <id>https://coolcode.org/2018/03/26/create-a-dotnet-project-for-multiple-target-frameworks/</id>
    <published>2018-03-26T04:10:55.000Z</published>
    <updated>2020-01-01T07:55:37.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前在开发 hprose for .NET 时，因为老版本的 Visual Studio 不支持创建面向多个目标框架的 .NET 项目，所以只能在一个 .NET 工程下面为每个目标框架的 .NET 版本创建一个 csproj 项目文件，费时又费力。所以我甚至一度抛弃创建 .NET 工程项目，用编写 bat 文件的命令行方式来编译 hprose for .NET 的 dll 类库。</p><p>现在要开发 hprose 3.0 for .NET 时，我希望能够在这方面省点事，毕竟现在的 .NET 跟之前比起来，版本更多了，而且还增加了许多跨平台的目标框架，如果还用 bat 的方式来编译打包，想一想都是很头疼的事情。</p><p> 微软估计也考虑到了这一点，所以现在终于在 Visual Studio 2017/2019 中提供了一种新的 csproj 格式，使用这种格式，就可以创建面向多个目标框架的 .NET 项目了。这对于开发多目标框架的 .NET 库来说，无疑是提供了很大的便利。</p> <a id="more"></a><h1 id="创建新版本的-csproj-项目文件"><a href="#创建新版本的-csproj-项目文件" class="headerlink" title="创建新版本的 .csproj 项目文件"></a>创建新版本的 .csproj 项目文件</h1><p> 如果使用 .NET Core 2.0 命令行创建的话，非常简单，新建一个项目目录，然后在其中执行：</p> <figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line">dotnet new classlib</span><br></pre></td></tr></table></figure><p> 就可以了。</p><p> 如果使用 Visual Studio 2017/2019 的话，选择新建项目，然后选择 .NET Core 或者 .NET Standard，之后选择类库，就可以了，如图：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/03/26/create-a-dotnet-project-for-multiple-target-frameworks/create-netcoreapp-library-in-vs2017/2019.png" alt="创建 .NET Core 类库" title>                </div>                <div class="image-caption">创建 .NET Core 类库</div>            </figure><p> 新创建的 .csproj 项目文件，可以直接用文本编辑器（比如 VSCode 等）打开，里面内容很简单：</p> <figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"> <span class="tag">&lt;<span class="name">Project</span> <span class="attr">Sdk</span>=<span class="string">"Microsoft.NET.Sdk"</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">PropertyGroup</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">TargetFramework</span>&gt;</span>netstandard2.0<span class="tag">&lt;/<span class="name">TargetFramework</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">PropertyGroup</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">Project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这个项目目前还只支持单一的目标框架，要改成支持多目标框架就需要来用文本编辑器手动编辑这个 XML 文件了。Visual Studio 2017 目前还不支持通过图形界面的方式来配置多目标框架。</p><h1 id="改成面向多目标框架的项目"><a href="#改成面向多目标框架的项目" class="headerlink" title="改成面向多目标框架的项目"></a>改成面向多目标框架的项目</h1><p>修改其实也很简单，只需要把 <code>TargetFramework</code> 改成 <code>TargetFrameworks</code>，然后把你需要的目标框架填进去，用分号分隔就行了。例如：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"> <span class="tag">&lt;<span class="name">Project</span> <span class="attr">Sdk</span>=<span class="string">"Microsoft.NET.Sdk"</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">PropertyGroup</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">TargetFrameworks</span>&gt;</span>netstandard1.0;netstandard1.1;netstandard1.2;netstandard1.3;netstandard1.4;netstandard1.5;netstandard1.6;netstandard2.0;netcoreapp1.0;netcoreapp1.1;netcoreapp2.0;net40;net45;net451;net452;net46;net461;net462;net47;net471<span class="tag">&lt;/<span class="name">TargetFrameworks</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">PropertyGroup</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">Project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>上面这些目标框架是 .NET Core 和 Visual Studio 2017/2019 默认就支持的，只要这样写上，就可以直接用命令行：</p><figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line">dotnet build</span><br></pre></td></tr></table></figure><p>或在 Visual Studio 2017/2019 中编译通过。</p><p>但如果创建的不是类库，而是单元测试（mstest 和 xunit）项目的话，<code>netstandardx.x</code> 和 <code>net40</code> 就不支持了，这一点是测试框架限制的。</p><p>所以，既能编译，又能测试的多目标框架大概也就只有这些：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"> <span class="tag">&lt;<span class="name">Project</span> <span class="attr">Sdk</span>=<span class="string">"Microsoft.NET.Sdk"</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">PropertyGroup</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">TargetFrameworks</span>&gt;</span>netcoreapp1.0;netcoreapp1.1;netcoreapp2.0;net45;net451;net452;net46;net461;net462;net47;net471<span class="tag">&lt;/<span class="name">TargetFrameworks</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">PropertyGroup</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">Project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>如果不考虑测试，那么在 Visual Studio 2017/2019 中，还可以添加上：<code>net20</code>、<code>net30</code> 和 <code>net35</code> 这三个目标框架。</p><p>这些可以在 Visual Studio 2017/2019 中可以编译通过，使用 dotnet 命令行的话，会显示：</p><blockquote><p><span style="color: red">C:\Program Files\dotnet\sdk\2.1.4\Microsoft.Common.CurrentVersion.targets(1124,5): error MSB3644: 未找到框架“.NETFramework,Version=v2.0”的引用程序集。若要解决此问题，请安装此框架版本的 SDK 或 Targeting Pack，或将应用程序的目标重新指向已装有 SDK 或 Targeting Pack 的框架版本。请注意，将从全局程序集缓存(GAC)解析程序集，并将使用这些程序集替换引用程序集。因此，程序集的目标可能未正确指向您所预期的框架。</span></p></blockquote><p>这样的错误信息。这个问题能否解决我还不清楚，因为没有查到这方面的资料。</p><h1 id="增加-NuGet-打包信息"><a href="#增加-NuGet-打包信息" class="headerlink" title="增加 NuGet 打包信息"></a>增加 NuGet 打包信息</h1><p>新版的 .csproj 不但可以直接添加程序集信息，不再需要 <code>AssemblyInfo.cs</code> 这个文件了。还可以直接添加 NuGet 打包信息，一步生成 .nupkg 文件。例如：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">PropertyGroup</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">SignAssembly</span>&gt;</span>true<span class="tag">&lt;/<span class="name">SignAssembly</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">DelaySign</span>&gt;</span>false<span class="tag">&lt;/<span class="name">DelaySign</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">AssemblyOriginatorKeyFile</span>&gt;</span>HproseKeys.snk<span class="tag">&lt;/<span class="name">AssemblyOriginatorKeyFile</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">Version</span>&gt;</span>2.0.0<span class="tag">&lt;/<span class="name">Version</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">OutputType</span>&gt;</span>Library<span class="tag">&lt;/<span class="name">OutputType</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">GeneratePackageOnBuild</span>&gt;</span>true<span class="tag">&lt;/<span class="name">GeneratePackageOnBuild</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">PackageRequireLicenseAcceptance</span>&gt;</span>true<span class="tag">&lt;/<span class="name">PackageRequireLicenseAcceptance</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">Authors</span>&gt;</span>Ma Bingyao<span class="symbol">&amp;lt;</span>andot@hprose.com<span class="symbol">&amp;gt;</span><span class="tag">&lt;/<span class="name">Authors</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">Company</span>&gt;</span>hprose.com<span class="tag">&lt;/<span class="name">Company</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">Copyright</span>&gt;</span>Copyright © http://www.hprose.com, All rights reserved.<span class="tag">&lt;/<span class="name">Copyright</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">AssemblyVersion</span>&gt;</span>2.0.0.0<span class="tag">&lt;/<span class="name">AssemblyVersion</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">Product</span>&gt;</span>Hprose.Core<span class="tag">&lt;/<span class="name">Product</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">PackageLicenseUrl</span>&gt;</span>http://opensource.org/licenses/MIT<span class="tag">&lt;/<span class="name">PackageLicenseUrl</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">PackageProjectUrl</span>&gt;</span>http://www.hprose.com<span class="tag">&lt;/<span class="name">PackageProjectUrl</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">PackageIconUrl</span>&gt;</span>http://hprose.com/apple-touch-icon-180x180.png<span class="tag">&lt;/<span class="name">PackageIconUrl</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">RepositoryUrl</span>&gt;</span>https://github.com/hprose/hprose-dotnet<span class="tag">&lt;/<span class="name">RepositoryUrl</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">RepositoryType</span>&gt;</span>git<span class="tag">&lt;/<span class="name">RepositoryType</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">PropertyGroup</span>&gt;</span></span><br></pre></td></tr></table></figure><p>上面的信息对于所面向的不同框架信息都是一样的，还可以根据不同的框架来添加不同的信息，例如：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">Target</span> <span class="attr">Name</span>=<span class="string">"PreBuild"</span> <span class="attr">BeforeTargets</span>=<span class="string">"PreBuildEvent"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">PropertyGroup</span> <span class="attr">Condition</span>=<span class="string">" '$(TargetFrameworkIdentifier)' != '' "</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">Product</span>&gt;</span>Hprose.Core for $(TargetFrameworkIdentifier) $(TargetFrameworkVersion)<span class="tag">&lt;/<span class="name">Product</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">PropertyGroup</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">PropertyGroup</span> <span class="attr">Condition</span>=<span class="string">" '$(TargetFrameworkProfile)' != '' "</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">Product</span>&gt;</span>Hprose.Core for $(TargetFrameworkIdentifier) $(TargetFrameworkVersion) $(TargetFrameworkProfile) Profile<span class="tag">&lt;/<span class="name">Product</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">PropertyGroup</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">Target</span>&gt;</span></span><br></pre></td></tr></table></figure><p>通过上面这一段代码，就可以为生成的每个 dll 添加不同的产品名称信息了。</p><h1 id="为不同目标框架的增加预编译符号"><a href="#为不同目标框架的增加预编译符号" class="headerlink" title="为不同目标框架的增加预编译符号"></a>为不同目标框架的增加预编译符号</h1><p>要用一套代码为不同的目标框架来编写类库，那么条件编译是不可或缺的。</p><p>Java 没有提供条件编译，要实现这一目标就变得非常困难，目前虽然在 Java 中能借助 <code>final</code> 变量加 <code>if</code> 条件判断来实现有限的“条件编译”，但是 Java 的这种奇技淫巧跟 C# 提供的条件编译比起来，简直难用的要死。尤其是现在 Java 也成了版本帝，半年一个就发布一个版本，在这种情况下，还不提供条件编译，这简直是类库开发者的噩梦。</p><p>C# 从一开始就支持条件编译，不过最初并没有为每个不同版本的框架定义标准的预编译符号。但是现在，使用新版的 .csproj 创建的项目，会自动为不同版本的目标框架添加预编译符号了，下面是自动添加的预编译符号：</p><table><thead><tr><th style="text-align:left">目标框架</th><th style="text-align:left">预编译符号</th></tr></thead><tbody><tr><td style="text-align:left">.NET Framework 2.0</td><td style="text-align:left"><code>NET20</code></td></tr><tr><td style="text-align:left">.NET Framework 3.0</td><td style="text-align:left"><code>NET30</code></td></tr><tr><td style="text-align:left">.NET Framework 3.5</td><td style="text-align:left"><code>NET35</code></td></tr><tr><td style="text-align:left">.NET Framework 4.0</td><td style="text-align:left"><code>NET40</code></td></tr><tr><td style="text-align:left">.NET Framework 4.5</td><td style="text-align:left"><code>NET45</code></td></tr><tr><td style="text-align:left">.NET Framework 4.5.1</td><td style="text-align:left"><code>NET451</code></td></tr><tr><td style="text-align:left">.NET Framework 4.5.2</td><td style="text-align:left"><code>NET452</code></td></tr><tr><td style="text-align:left">.NET Framework 4.6</td><td style="text-align:left"><code>NET46</code></td></tr><tr><td style="text-align:left">.NET Framework 4.6.1</td><td style="text-align:left"><code>NET461</code></td></tr><tr><td style="text-align:left">.NET Framework 4.6.2</td><td style="text-align:left"><code>NET462</code></td></tr><tr><td style="text-align:left">.NET Framework 4.7</td><td style="text-align:left"><code>NET47</code></td></tr><tr><td style="text-align:left">.NET Framework 4.7.1</td><td style="text-align:left"><code>NET471</code></td></tr><tr><td style="text-align:left">.NET Framework 4.7.2</td><td style="text-align:left"><code>NET472</code></td></tr><tr><td style="text-align:left">.NET Framework 4.8</td><td style="text-align:left"><code>NET48</code></td></tr><tr><td style="text-align:left">.NET Standard 1.0</td><td style="text-align:left"><code>NETSTANDARD1_0</code></td></tr><tr><td style="text-align:left">.NET Standard 1.1</td><td style="text-align:left"><code>NETSTANDARD1_1</code></td></tr><tr><td style="text-align:left">.NET Standard 1.2</td><td style="text-align:left"><code>NETSTANDARD1_2</code></td></tr><tr><td style="text-align:left">.NET Standard 1.3</td><td style="text-align:left"><code>NETSTANDARD1_3</code></td></tr><tr><td style="text-align:left">.NET Standard 1.4</td><td style="text-align:left"><code>NETSTANDARD1_4</code></td></tr><tr><td style="text-align:left">.NET Standard 1.5</td><td style="text-align:left"><code>NETSTANDARD1_5</code></td></tr><tr><td style="text-align:left">.NET Standard 1.6</td><td style="text-align:left"><code>NETSTANDARD1_6</code></td></tr><tr><td style="text-align:left">.NET Standard 2.0</td><td style="text-align:left"><code>NETSTANDARD2_0</code></td></tr><tr><td style="text-align:left">.NET Standard 2.1</td><td style="text-align:left"><code>NETSTANDARD2_1</code></td></tr><tr><td style="text-align:left">.NET Core 1.0</td><td style="text-align:left"><code>NETCOREAPP1_0</code></td></tr><tr><td style="text-align:left">.NET Core 1.1</td><td style="text-align:left"><code>NETCOREAPP1_1</code></td></tr><tr><td style="text-align:left">.NET Core 2.0</td><td style="text-align:left"><code>NETCOREAPP2_0</code></td></tr><tr><td style="text-align:left">.NET Core 2.1</td><td style="text-align:left"><code>NETCOREAPP2_1</code></td></tr><tr><td style="text-align:left">.NET Core 2.2</td><td style="text-align:left"><code>NETCOREAPP2_2</code></td></tr><tr><td style="text-align:left">.NET Core 3.0</td><td style="text-align:left"><code>NETCOREAPP3_0</code></td></tr><tr><td style="text-align:left">.NET Core 3.1</td><td style="text-align:left"><code>NETCOREAPP3_1</code></td></tr></tbody></table><p>如果感觉不够用，还可以自己添加，例如：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">PropertyGroup</span> <span class="attr">Condition</span>=<span class="string">" $(TargetFramework.Contains('netstandard1.')) "</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">_IsNetStandard1Defined</span>&gt;</span>$([System.Text.RegularExpressions.Regex]::IsMatch('$(DefineConstants.Trim())', '(^|;)NETSTANDARD1($|;)'))<span class="tag">&lt;/<span class="name">_IsNetStandard1Defined</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">DefineConstants</span> <span class="attr">Condition</span>=<span class="string">"!$(_IsNetStandard1Defined)"</span>&gt;</span>NETSTANDARD1;$(DefineConstants)<span class="tag">&lt;/<span class="name">DefineConstants</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">PropertyGroup</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">PropertyGroup</span> <span class="attr">Condition</span>=<span class="string">" $(TargetFramework.Contains('netcoreapp1.')) "</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">_IsNetCoreApp1Defined</span>&gt;</span>$([System.Text.RegularExpressions.Regex]::IsMatch('$(DefineConstants.Trim())', '(^|;)NETCOREAPP1($|;)'))<span class="tag">&lt;/<span class="name">_IsNetCoreApp1Defined</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">DefineConstants</span> <span class="attr">Condition</span>=<span class="string">"!$(_IsNetCoreApp1Defined)"</span>&gt;</span>NETCOREAPP1;$(DefineConstants)<span class="tag">&lt;/<span class="name">DefineConstants</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">PropertyGroup</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这样就可以为 .NET Standard 1.x 增加预编译符号 <code>NETSTANDARD1</code>，为 .NET Core 1.0 增加预编译符号 <code>NETCOREAPP1</code> 了。这对于在程序中只需要一个条件就可以判断所有的 .NET Standard 1.x 平台或 .NET Core 1.x 平台了。</p><h1 id="添加-Android-平台支持"><a href="#添加-Android-平台支持" class="headerlink" title="添加 Android 平台支持"></a>添加 Android 平台支持</h1><p>默认情况下，使用 Visual Studio 2017/2019 创建的 Android 类库项目是不使用这个新版本 .csproj 格式的。不过我们可以手动把 Android 平台添加到已有的这个项目中。</p><p>首先在 <code>&lt;TargetFrameworks&gt;...&lt;/TargetFrameworks&gt;</code> 里面加入 <code>monoandroid71</code>。其中 <code>monoandroid</code> 是目标框架标识，<code>71</code> 是目标框架版本号。然后加入下列代码：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">PropertyGroup</span> <span class="attr">Condition</span>=<span class="string">" $(TargetFramework.Contains('monoandroid')) "</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">FileAlignment</span>&gt;</span>512<span class="tag">&lt;/<span class="name">FileAlignment</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">GenerateSerializationAssemblies</span>&gt;</span>Off<span class="tag">&lt;/<span class="name">GenerateSerializationAssemblies</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">AndroidUseLatestPlatformSdk</span>&gt;</span>true<span class="tag">&lt;/<span class="name">AndroidUseLatestPlatformSdk</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">TargetFrameworkVersion</span>&gt;</span>v$(TargetFramework[11]).$(TargetFramework[12])<span class="tag">&lt;/<span class="name">TargetFrameworkVersion</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">TargetFrameworkIdentifier</span>&gt;</span>MonoAndroid<span class="tag">&lt;/<span class="name">TargetFrameworkIdentifier</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">LanguageTargets</span>&gt;</span>$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets<span class="tag">&lt;/<span class="name">LanguageTargets</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">PropertyGroup</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">ItemGroup</span> <span class="attr">Condition</span>=<span class="string">" $(TargetFramework.Contains('monoandroid')) "</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">Reference</span> <span class="attr">Include</span>=<span class="string">"Mono.Android"</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">ItemGroup</span>&gt;</span></span><br></pre></td></tr></table></figure><p>Android 目标框架有好几个版本，我目前还不确定是否只添加最高版本的就能在所有版本上都可以使用，如果不行的话，那就把所有版本（<code>monoandroid44</code>;<code>monoandroid50</code>;<code>monoandroid51</code>;<code>monoandroid60</code>;<code>monoandroid71</code>）都添加进去也无妨。</p><h1 id="添加-Apple-系列平台支持"><a href="#添加-Apple-系列平台支持" class="headerlink" title="添加 Apple 系列平台支持"></a>添加 Apple 系列平台支持</h1><p>添加方法跟 Android 平台类似。首先在 <code>&lt;TargetFrameworks&gt;...&lt;/TargetFrameworks&gt;</code> 里面加入 <code>xamarinmac20;xamarinios10;xamarintvos10;xamarinwatchos10</code>。然后加入下列代码：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">PropertyGroup</span> <span class="attr">Condition</span>=<span class="string">" '$(TargetFramework)' == 'xamarinmac20' "</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">TargetFrameworkVersion</span>&gt;</span>v2.0<span class="tag">&lt;/<span class="name">TargetFrameworkVersion</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">TargetFrameworkIdentifier</span>&gt;</span>Xamarin.Mac<span class="tag">&lt;/<span class="name">TargetFrameworkIdentifier</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">PropertyGroup</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">PropertyGroup</span> <span class="attr">Condition</span>=<span class="string">" '$(TargetFramework)' == 'xamarinios10' "</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">TargetFrameworkVersion</span>&gt;</span>v1.0<span class="tag">&lt;/<span class="name">TargetFrameworkVersion</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">TargetFrameworkIdentifier</span>&gt;</span>Xamarin.iOS<span class="tag">&lt;/<span class="name">TargetFrameworkIdentifier</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">PropertyGroup</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">PropertyGroup</span> <span class="attr">Condition</span>=<span class="string">" '$(TargetFramework)' == 'xamarintvos10' "</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">TargetFrameworkVersion</span>&gt;</span>v1.0<span class="tag">&lt;/<span class="name">TargetFrameworkVersion</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">TargetFrameworkIdentifier</span>&gt;</span>Xamarin.TVOS<span class="tag">&lt;/<span class="name">TargetFrameworkIdentifier</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">PropertyGroup</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">PropertyGroup</span> <span class="attr">Condition</span>=<span class="string">" '$(TargetFramework)' == 'xamarinwatchos10' "</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">TargetFrameworkVersion</span>&gt;</span>v1.0<span class="tag">&lt;/<span class="name">TargetFrameworkVersion</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">TargetFrameworkIdentifier</span>&gt;</span>Xamarin.WatchOS<span class="tag">&lt;/<span class="name">TargetFrameworkIdentifier</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">PropertyGroup</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">PropertyGroup</span> <span class="attr">Condition</span>=<span class="string">" $(TargetFramework.Contains('xamarin')) "</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">LanguageTargets</span>&gt;</span>$(MSBuildExtensionsPath)\$(TargetFrameworkIdentifier.Replace('.', '\'))\$(TargetFrameworkIdentifier).CSharp.targets<span class="tag">&lt;/<span class="name">LanguageTargets</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">PropertyGroup</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">ItemGroup</span> <span class="attr">Condition</span>=<span class="string">" $(TargetFramework.Contains('xamarin')) "</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">Reference</span> <span class="attr">Include</span>=<span class="string">"$(TargetFrameworkIdentifier)"</span> /&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">ItemGroup</span>&gt;</span></span><br></pre></td></tr></table></figure><h1 id="添加-SliverLight-平台支持"><a href="#添加-SliverLight-平台支持" class="headerlink" title="添加 SliverLight 平台支持"></a>添加 SliverLight 平台支持</h1><p>在 <code>&lt;TargetFrameworks&gt;...&lt;/TargetFrameworks&gt;</code> 里面加入 <code>sl3;sl4;sl5</code>。然后加入下列代码：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">PropertyGroup</span> <span class="attr">Condition</span>=<span class="string">" $(TargetFramework.StartsWith('sl')) "</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">_SilverLightVersion</span>&gt;</span>$(TargetFramework[2])<span class="tag">&lt;/<span class="name">_SilverLightVersion</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">TargetFrameworkVersion</span>&gt;</span>v$(_SilverLightVersion).0<span class="tag">&lt;/<span class="name">TargetFrameworkVersion</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">TargetFrameworkIdentifier</span>&gt;</span>SilverLight<span class="tag">&lt;/<span class="name">TargetFrameworkIdentifier</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">SilverlightVersion</span> <span class="attr">Condition</span>=<span class="string">"'$(SilverlightVersion)' == ''"</span>&gt;</span>$(TargetFrameworkVersion)<span class="tag">&lt;/<span class="name">SilverlightVersion</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">SilverlightApplication</span>&gt;</span>false<span class="tag">&lt;/<span class="name">SilverlightApplication</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">ValidateXaml</span>&gt;</span>true<span class="tag">&lt;/<span class="name">ValidateXaml</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">DisableImplicitFrameworkReferences</span>&gt;</span>true<span class="tag">&lt;/<span class="name">DisableImplicitFrameworkReferences</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">_IsSilverLightDefined</span>&gt;</span>$([System.Text.RegularExpressions.Regex]::IsMatch('$(DefineConstants.Trim())', '(^|;)SILVERLIGHT($|;)'))<span class="tag">&lt;/<span class="name">_IsSilverLightDefined</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">DefineConstants</span> <span class="attr">Condition</span>=<span class="string">"!$(_IsSilverLightDefined)"</span>&gt;</span>SILVERLIGHT;SILVERLIGHT$(_SilverLightVersion);$(DefineConstants)<span class="tag">&lt;/<span class="name">DefineConstants</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">LanguageTargets</span>&gt;</span>$(MSBuildProgramFiles32)\MSBuild\Microsoft\$(TargetFrameworkIdentifier)\$(TargetFrameworkVersion)\Microsoft.$(TargetFrameworkIdentifier).CSharp.targets<span class="tag">&lt;/<span class="name">LanguageTargets</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">PropertyGroup</span>&gt;</span></span><br></pre></td></tr></table></figure><h1 id="其它平台"><a href="#其它平台" class="headerlink" title="其它平台"></a>其它平台</h1><p>.NET Compact Framework, .NET Micro Framwork, Windows Phone 这几个平台目前还没有研究，因为在 hprose 3.0 for .NET 中不打算支持它们了。其实 .NET 2.0、3.0、3.5 这几个版本的 .NET 框架以及 SliverLight 平台我也不打算在 hprose 3.0 中支持了。支持它们不但需要写大量的条件编译，而且编写代码时不能使用新特性，微软的测试框架也不支持，写起代码来太不爽了。</p><p>UWP（uap）这个平台我还不知道怎么配置到这个多目标框架的 .csproj 项目中，试了好久，都没有成功，也许还不支持吧。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;之前在开发 hprose for .NET 时，因为老版本的 Visual Studio 不支持创建面向多个目标框架的 .NET 项目，所以只能在一个 .NET 工程下面为每个目标框架的 .NET 版本创建一个 csproj 项目文件，费时又费力。所以我甚至一度抛弃创建 .NET 工程项目，用编写 bat 文件的命令行方式来编译 hprose for .NET 的 dll 类库。&lt;/p&gt;
&lt;p&gt;现在要开发 hprose 3.0 for .NET 时，我希望能够在这方面省点事，毕竟现在的 .NET 跟之前比起来，版本更多了，而且还增加了许多跨平台的目标框架，如果还用 bat 的方式来编译打包，想一想都是很头疼的事情。&lt;/p&gt;
&lt;p&gt; 微软估计也考虑到了这一点，所以现在终于在 Visual Studio 2017/2019 中提供了一种新的 csproj 格式，使用这种格式，就可以创建面向多个目标框架的 .NET 项目了。这对于开发多目标框架的 .NET 库来说，无疑是提供了很大的便利。&lt;/p&gt;
    
    </summary>
    
      <category term="编程" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/"/>
    
      <category term=".NET" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/NET/"/>
    
    
      <category term="c#" scheme="https://coolcode.org/tags/c/"/>
    
  </entry>
  
  <entry>
    <title>lolcat in PowerShell</title>
    <link href="https://coolcode.org/2018/03/24/lolcat-in-powershell/"/>
    <id>https://coolcode.org/2018/03/24/lolcat-in-powershell/</id>
    <published>2018-03-24T04:09:00.000Z</published>
    <updated>2018-03-24T04:09:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>这两天测试 PowerShell 环境，顺便写了一个小程序 <code>lolcat</code> 用来检测 Windows 控制台是否支持 24 位色，这个程序原版是 ruby 写的，用来以彩虹色显示文本。它还有一些其它语言的移植，比如 python，c，js，rust，go 等。不过都不太适合在 PowerShell 环境下运行，一是需要安装运行环境，或者需要自己编译，这一点倒还好说，只是比较麻烦而已。第二点就比较致命了，PowerShell 的管道传递的是对象，而其它语言实现的版本，只能处理文本，所以如果在 PowerShell 中使用其它语言实现的 <code>lolcat</code> 跟管道结合使用的话，要么显示的不是你想要的东西，要么干脆就挂掉了。所以，我花了两天时间写了这个 PowerShell 的版本。它可以工作在 Windows 10 自带的 PowerShell 环境下，也可以工作在跨平台的 PowerShell 6.0+ 环境下。</p><p>项目地址是：<a href="https://github.com/andot/lolcat" target="_blank" rel="noopener">https://github.com/andot/lolcat</a>，欢迎大家点赞~</p><a id="more"></a><p>它的安装方式很简单，在 PowerShell 控制台下运行：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Install-Module</span> lolcat <span class="literal">-Scope</span> CurrentUser</span><br></pre></td></tr></table></figure><p>或者在 PowerShell 管理员控制台下运行：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Install-Module</span> lolcat</span><br></pre></td></tr></table></figure><p>就可以了。</p><p>下面是这个程序的运行截图：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/03/24/lolcat-in-powershell/screenshot.png" alt="lolcat in PowerShell" title>                </div>                <div class="image-caption">lolcat in PowerShell</div>            </figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/03/24/lolcat-in-powershell/lolcat-in-vscode.png" alt="lolcat in VSCode" title>                </div>                <div class="image-caption">lolcat in VSCode</div>            </figure><p>通过上面的截图我们可以看出，Windows 10 的控制台确实是支持 24 位色的，而 VSCode 内嵌的终端则不支持 24 位色。</p><p>之前一直奇怪为啥 WSL 在控制台上运行的时候，PowerLine 提示符就显示的好好的，而到了 VSCode 终端下，PowerLine 提示符中的路径分隔符就显示不出来了。现在终于明白了，原来是 VSCode 内嵌终端不支持 24 位色，而 PowerLine 提示符中的路径分隔符所使用的颜色跟背景色比较相近，VSCode 直接把它按照背景色显示了，所以就看不见了。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;这两天测试 PowerShell 环境，顺便写了一个小程序 &lt;code&gt;lolcat&lt;/code&gt; 用来检测 Windows 控制台是否支持 24 位色，这个程序原版是 ruby 写的，用来以彩虹色显示文本。它还有一些其它语言的移植，比如 python，c，js，rust，go 等。不过都不太适合在 PowerShell 环境下运行，一是需要安装运行环境，或者需要自己编译，这一点倒还好说，只是比较麻烦而已。第二点就比较致命了，PowerShell 的管道传递的是对象，而其它语言实现的版本，只能处理文本，所以如果在 PowerShell 中使用其它语言实现的 &lt;code&gt;lolcat&lt;/code&gt; 跟管道结合使用的话，要么显示的不是你想要的东西，要么干脆就挂掉了。所以，我花了两天时间写了这个 PowerShell 的版本。它可以工作在 Windows 10 自带的 PowerShell 环境下，也可以工作在跨平台的 PowerShell 6.0+ 环境下。&lt;/p&gt;
&lt;p&gt;项目地址是：&lt;a href=&quot;https://github.com/andot/lolcat&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/andot/lolcat&lt;/a&gt;，欢迎大家点赞~&lt;/p&gt;
    
    </summary>
    
      <category term="编程" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/"/>
    
      <category term="PowerShell" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/PowerShell/"/>
    
    
      <category term="powershell" scheme="https://coolcode.org/tags/powershell/"/>
    
  </entry>
  
  <entry>
    <title>几个有用的 PowerShell 脚本</title>
    <link href="https://coolcode.org/2018/03/19/some-useful-scripts-of-powershell/"/>
    <id>https://coolcode.org/2018/03/19/some-useful-scripts-of-powershell/</id>
    <published>2018-03-19T02:04:00.000Z</published>
    <updated>2020-01-01T06:28:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>前一篇文章介绍了如何美化 PowerShell，今天来写几个比较实用的 PowerShell 脚本。</p><a id="more"></a><h1 id="更新-PowerShell-的-NuGet"><a href="#更新-PowerShell-的-NuGet" class="headerlink" title="更新 PowerShell 的 NuGet"></a>更新 PowerShell 的 NuGet</h1><p>默认 PowerShell 自带的 NuGet 还是 2.8 版本，使用这个版本无法正常发布 PowerShell 模块，所以需要将 NuGet 更新到最新版本。</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Invoke-WebRequest</span> <span class="literal">-Uri</span> https://dist.nuget.org/win<span class="literal">-x86</span><span class="literal">-commandline</span>/latest/nuget.exe <span class="literal">-OutFile</span> <span class="string">"<span class="variable">$env:LOCALAPPDATA</span>\Microsoft\Windows\PowerShell\PowerShellGet\NuGet.exe"</span></span><br></pre></td></tr></table></figure><p>运行一次就行了。</p><h1 id="通过-cd…-进入上上级目录"><a href="#通过-cd…-进入上上级目录" class="headerlink" title="通过 cd… 进入上上级目录"></a>通过 cd… 进入上上级目录</h1><p>在命令行里我们最常用的命令应该就是 <code>cd</code> 了，因为每次切换目录都要用到。不过如果进入了比较深的目录，想要向上退几层的话，对于我这种懒人来说，要输入 <code>cd ..\..</code> 这样的命令感觉太麻烦了。要是能支持 <code>cd...</code> 或者 <code>cd ...</code> 这样的写法就简单多了。</p><p>要实现 <code>cd...</code> 这样的写法很简单，只需要编写下面这样的脚本就可以了：</p><figure class="highlight powershell"><figcaption><span>cd...</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">cd</span>...</span> &#123; <span class="built_in">Set-Location</span> ..\.. &#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">cd</span>....</span> &#123; <span class="built_in">Set-Location</span> ..\..\.. &#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">cd</span>.....</span> &#123; <span class="built_in">Set-Location</span> ..\..\..\.. &#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">cd</span>......</span> &#123; <span class="built_in">Set-Location</span> ..\..\..\..\.. &#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">cd</span>.......</span> &#123; <span class="built_in">Set-Location</span> ..\..\..\..\..\.. &#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">cd</span>........</span> &#123; <span class="built_in">Set-Location</span> ..\..\..\..\..\..\.. &#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">cd</span>.........</span> &#123; <span class="built_in">Set-Location</span> ..\..\..\..\..\..\..\.. &#125;</span><br></pre></td></tr></table></figure><p>如果你愿意的话，可以定义更长的函数。</p><p>如果希望 <code>cd</code> 和 <code>...</code> 之间有空格也好用的话，那就稍微复杂一些了，不过也是可以实现的：</p><figure class="highlight powershell"><figcaption><span>增强型 cd</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Set-CurrentWorkingDirectory</span></span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">param</span></span><br><span class="line">    (</span><br><span class="line">        <span class="variable">$Path</span>,</span><br><span class="line">        <span class="variable">$LiteralPath</span>,</span><br><span class="line">        <span class="variable">$PassThru</span>,</span><br><span class="line">        <span class="variable">$StackName</span>,</span><br><span class="line">        <span class="variable">$UseTransaction</span></span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable">$Path</span> <span class="operator">-and</span> (<span class="variable">$Path</span>.Contains(<span class="string">'...'</span>)))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="variable">$a</span> = [<span class="type">System.Text.RegularExpressions.Regex</span>]::Split(<span class="variable">$Path</span>, <span class="string">"(\.&#123;3,&#125;)"</span>);</span><br><span class="line">        <span class="keyword">for</span> (<span class="variable">$i</span> = <span class="number">0</span>; <span class="variable">$i</span> <span class="operator">-lt</span> <span class="variable">$a</span>.Length; <span class="variable">$i</span>++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="variable">$e</span> = <span class="variable">$a</span>[<span class="variable">$i</span>];</span><br><span class="line">            <span class="variable">$l</span> = <span class="variable">$e</span>.Length;</span><br><span class="line">            <span class="keyword">if</span> ((<span class="variable">$l</span> <span class="operator">-gt</span> <span class="number">2</span>) <span class="operator">-and</span> (<span class="variable">$e</span> <span class="operator">-eq</span> <span class="string">""</span>.PadRight(<span class="variable">$l</span>, <span class="string">'.'</span>)))</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="variable">$a</span>[<span class="variable">$i</span>] = <span class="string">".."</span> + [<span class="type">System.String</span>]::Concat([<span class="type">System.Linq.Enumerable</span>]::Repeat(<span class="string">"\.."</span>, <span class="variable">$l</span> - <span class="number">2</span>))</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="variable">$PSBoundParameters</span>[<span class="string">'Path'</span>] = [<span class="type">System.String</span>]::Concat(<span class="variable">$a</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">Set-Location</span> @PSBoundParameters</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">Set-Alias</span> cd <span class="built_in">Set-CurrentWorkingDirectory</span> <span class="literal">-Option</span> <span class="string">"AllScope"</span></span><br></pre></td></tr></table></figure><p>上面这段代码定义了一个 <code>Set-CurrentWorkingDirectory</code> 函数，它做的事情很简单，就是把路径中的 <code>...</code> 都替换成 <code>..\..</code> 的形式，然后再传给 Set-Location。</p><p>其中 <code>...</code> 可以是任意长度，比如 <code>....</code> 或 <code>............................</code> 都可以，只要你真的需要这么长的路径。</p><p>路径中间的 <code>...</code> 也可以被替换，例如：<code>D:\Git\CoolCode\themes\indigo\source\....\source</code>，会被替换为<code>D:\Git\CoolCode\themes\indigo\source\..\..\..\source</code>。</p><p>好了，现在把上面两段代码加入到：</p><blockquote><p>C:\Users\andot\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1<br>C:\Users\andot\Documents\WindowsPowerShell\Microsoft.VSCode_profile.ps1</p></blockquote><p>中，然后再启动 PowerShell 就可以使用这个增强版的 <code>cd</code> 命令了。</p><p>注意，我这里没有替换 <code>chdir</code> 这个别名。因为 PowerShell 是跨平台的，在 Linux 上是可以建立 <code>...</code> 这样名字的目录的，所以，如果真的是要进入这样的目录，而不是上退两层目录的话，至少还可以使用 <code>chdir</code> 这个别名。Windows 上就无所谓了，因为 Windows 上无法创建这样的目录，也无法正常进入已存在的这样的目录。</p><h1 id="还原-Linux-风格的-ls，ll，la-命令"><a href="#还原-Linux-风格的-ls，ll，la-命令" class="headerlink" title="还原 Linux 风格的 ls，ll，la 命令"></a>还原 Linux 风格的 ls，ll，la 命令</h1><p><code>ls</code> 命令在 Linux 上的行为默认只是把文件名列出来，而 PowerShell 上的 <code>ls</code> 是以列表的形式列出文件详情列表。如果只想看文件名，还要用 <code>ls | Format-Wide -AutoSize</code> 这样的方式，感觉甚是麻烦。另外，在 Linux 上一般也会自定义 <code>ll</code>、<code>la</code> 这样的别名，比如 <code>ll</code> 会以列表形式显示文件详情，<code>la</code> 会列出包含隐藏文件在内的所有文件。下面的代码就是用来还原这种 Linux 风格操作的，另外，我还在其中加了一个 <code>lla</code> 别名，什么功能看名字应该就能猜出来吧。</p><figure class="highlight powershell"><figcaption><span>还原 Linux 风格的 ls，ll，la 命令</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Get-ChildItem-Wide</span></span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">param</span></span><br><span class="line">    (</span><br><span class="line">        <span class="variable">$Path</span>,</span><br><span class="line">        <span class="variable">$LiteralPath</span>,</span><br><span class="line">        <span class="variable">$Filter</span>,</span><br><span class="line">        <span class="variable">$Include</span>,</span><br><span class="line">        <span class="variable">$Exclude</span>,</span><br><span class="line">        <span class="variable">$Recurse</span>,</span><br><span class="line">        <span class="variable">$Force</span>,</span><br><span class="line">        <span class="variable">$Name</span>,</span><br><span class="line">        <span class="variable">$UseTransaction</span>,</span><br><span class="line">        <span class="variable">$Attributes</span>,</span><br><span class="line">        <span class="variable">$Depth</span>,</span><br><span class="line">        <span class="variable">$Directory</span>,</span><br><span class="line">        <span class="variable">$File</span>,</span><br><span class="line">        <span class="variable">$Hidden</span>,</span><br><span class="line">        <span class="variable">$ReadOnly</span>,</span><br><span class="line">        <span class="variable">$System</span></span><br><span class="line">    )</span><br><span class="line">    <span class="built_in">Get-ChildItem</span> @PSBoundParameters | <span class="built_in">Format-Wide</span> <span class="literal">-AutoSize</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Get-ChildItem-All</span></span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">param</span></span><br><span class="line">    (</span><br><span class="line">        <span class="variable">$Path</span>,</span><br><span class="line">        <span class="variable">$LiteralPath</span>,</span><br><span class="line">        <span class="variable">$Filter</span>,</span><br><span class="line">        <span class="variable">$Include</span>,</span><br><span class="line">        <span class="variable">$Exclude</span>,</span><br><span class="line">        <span class="variable">$Recurse</span>,</span><br><span class="line">        <span class="variable">$Force</span>,</span><br><span class="line">        <span class="variable">$Name</span>,</span><br><span class="line">        <span class="variable">$UseTransaction</span>,</span><br><span class="line">        <span class="variable">$Attributes</span>,</span><br><span class="line">        <span class="variable">$Depth</span>,</span><br><span class="line">        <span class="variable">$Directory</span>,</span><br><span class="line">        <span class="variable">$File</span>,</span><br><span class="line">        <span class="variable">$Hidden</span>,</span><br><span class="line">        <span class="variable">$ReadOnly</span>,</span><br><span class="line">        <span class="variable">$System</span></span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable">$Attributes</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="variable">$PSBoundParameters</span>.Remove(<span class="string">'Attributes'</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">Get-ChildItem</span> <span class="literal">-Attributes</span> ReadOnly, <span class="keyword">Hidden</span>, System, Normal, Archive, Directory, Encrypted, NotContentIndexed, Offline, ReparsePoint, SparseFile, Temporary @PSBoundParameters</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Get-ChildItem-All-Wide</span></span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">param</span></span><br><span class="line">    (</span><br><span class="line">        <span class="variable">$Path</span>,</span><br><span class="line">        <span class="variable">$LiteralPath</span>,</span><br><span class="line">        <span class="variable">$Filter</span>,</span><br><span class="line">        <span class="variable">$Include</span>,</span><br><span class="line">        <span class="variable">$Exclude</span>,</span><br><span class="line">        <span class="variable">$Recurse</span>,</span><br><span class="line">        <span class="variable">$Force</span>,</span><br><span class="line">        <span class="variable">$Name</span>,</span><br><span class="line">        <span class="variable">$UseTransaction</span>,</span><br><span class="line">        <span class="variable">$Attributes</span>,</span><br><span class="line">        <span class="variable">$Depth</span>,</span><br><span class="line">        <span class="variable">$Directory</span>,</span><br><span class="line">        <span class="variable">$File</span>,</span><br><span class="line">        <span class="variable">$Hidden</span>,</span><br><span class="line">        <span class="variable">$ReadOnly</span>,</span><br><span class="line">        <span class="variable">$System</span></span><br><span class="line">    )</span><br><span class="line">    <span class="built_in">Get-ChildItem</span><span class="literal">-All</span> @PSBoundParameters | <span class="built_in">Format-Wide</span> <span class="literal">-AutoSize</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">Set-Alias</span> ls <span class="built_in">Get-ChildItem</span><span class="literal">-Wide</span> <span class="literal">-Option</span> <span class="string">"AllScope"</span></span><br><span class="line"><span class="built_in">Set-Alias</span> ll <span class="built_in">Get-ChildItem</span></span><br><span class="line"><span class="built_in">Set-Alias</span> lla <span class="built_in">Get-ChildItem</span><span class="literal">-All</span></span><br><span class="line"><span class="built_in">Set-Alias</span> la <span class="built_in">Get-ChildItem</span><span class="literal">-All</span><span class="literal">-Wide</span></span><br></pre></td></tr></table></figure><h1 id="用-which-来查找命令的默认路径"><a href="#用-which-来查找命令的默认路径" class="headerlink" title="用 which 来查找命令的默认路径"></a>用 which 来查找命令的默认路径</h1><p>Linux 上有个 <code>which</code> 命令，它的作用是，在 PATH 变量指定的路径中，搜索某个系统命令的位置，并且返回第一个搜索结果。也就是说，使用 <code>which</code> 命令，就可以看到某个系统命令是否存在，以及执行的到底是哪一个位置的命令。</p><p>下面的代码还原了 <code>which</code> 命令的用法：</p><figure class="highlight powershell"><figcaption><span>which</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">which</span></span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="variable">$results</span> =<span class="built_in">New-Object</span> System.Collections.Generic.List[<span class="type">System.Object</span>];</span><br><span class="line">    <span class="keyword">foreach</span> (<span class="variable">$command</span> <span class="keyword">in</span> <span class="variable">$args</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="variable">$path</span> = (<span class="built_in">Get-Command</span> <span class="variable">$command</span>).Source</span><br><span class="line">        <span class="keyword">if</span> (<span class="variable">$path</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="variable">$results</span>.Add(<span class="variable">$path</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable">$results</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>用法：</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="built_in">which</span> bash notepad</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">C:\Windows\System32\bash.exe</span><br><span class="line">C:\Windows\System32\notepad.exe</span><br></pre></td></tr></table></figure><p>which 后面可以指定多个需要查找的命令。</p><h1 id="用-killall-杀死指定名字的所有进程"><a href="#用-killall-杀死指定名字的所有进程" class="headerlink" title="用 killall 杀死指定名字的所有进程"></a>用 killall 杀死指定名字的所有进程</h1><figure class="highlight powershell"><figcaption><span>killall</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">killall</span></span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="variable">$commands</span> = <span class="built_in">Get-Process</span> <span class="variable">$args</span></span><br><span class="line">    <span class="keyword">foreach</span> (<span class="variable">$command</span> <span class="keyword">in</span> <span class="variable">$commands</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">Stop-Process</span> <span class="variable">$command</span>.Id</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>用法：</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">killall notepad cmd</span><br></pre></td></tr></table></figure><p>killall 后面可以指定多个进程名。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;前一篇文章介绍了如何美化 PowerShell，今天来写几个比较实用的 PowerShell 脚本。&lt;/p&gt;
    
    </summary>
    
      <category term="编程" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/"/>
    
      <category term="PowerShell" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/PowerShell/"/>
    
    
      <category term="powershell" scheme="https://coolcode.org/tags/powershell/"/>
    
  </entry>
  
  <entry>
    <title>PowerShell 美化指南</title>
    <link href="https://coolcode.org/2018/03/16/how-to-make-your-powershell-beautiful/"/>
    <id>https://coolcode.org/2018/03/16/how-to-make-your-powershell-beautiful/</id>
    <published>2018-03-16T13:14:45.000Z</published>
    <updated>2018-03-18T02:02:31.000Z</updated>
    
    <content type="html"><![CDATA[<p>因为最近开始 Hprose 2.0 for .NET 的开发工作了，所以使用的操作系统从 Mac OS X 转到了 Windows 10 上来。在之前用 Mac OS X 和 Linux 时，命令行是经常用到的，转到 Windows 10 上之后，虽然大部分操作通过图形界面都可以完成了，但是有些操作还是会用到命令行，但是 Windows 10 的默认命令行控制台实在是丑的要命，让人感觉不爽。所以，我最后打算先把 Windows 10 的控制台美化一下，接下来工作时，也会变得心情舒畅。</p><a id="more"></a><p>先晒一下美化之后的成果：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/03/16/how-to-make-your-powershell-beautiful/screenfetch.png" alt="ScreenFetch" title>                </div>                <div class="image-caption">ScreenFetch</div>            </figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/03/16/how-to-make-your-powershell-beautiful/Show-Colors.png" alt="控制台颜色配置" title>                </div>                <div class="image-caption">控制台颜色配置</div>            </figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/03/16/how-to-make-your-powershell-beautiful/Show-ThemeColors.png" alt="主题颜色配置" title>                </div>                <div class="image-caption">主题颜色配置</div>            </figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/03/16/how-to-make-your-powershell-beautiful/git.png" alt="带有git状态的命令行提示符" title>                </div>                <div class="image-caption">带有git状态的命令行提示符</div>            </figure><p>怎么样？看了之后，你要是没兴趣，就可以忽略下面的内容了。要是感兴趣的话，那我们就开始吧。</p><h1 id="装逼利器-ScreenFetch-的安装"><a href="#装逼利器-ScreenFetch-的安装" class="headerlink" title="装逼利器 ScreenFetch 的安装"></a>装逼利器 ScreenFetch 的安装</h1><p>在正式开始之前，先装个逼。</p><p>上面第一张截图中使用的 screenfetch 并不是在 WSL 中安装的，也不是 Cygwin 或 MinGW 下面的版本，而是一个 PowerShell 模块 <a href="https://github.com/JulianChow94/Windows-screenFetch" target="_blank" rel="noopener">Windows-screenFetch</a>，你只需要在 PowerShell 控制台中使用：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Install-Module</span> windows<span class="literal">-screenfetch</span> <span class="literal">-Scope</span> CurrentUser</span><br></pre></td></tr></table></figure><p>就可以安装它了。</p><p>如果在安装过程中遇到类似于这样的提示：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">不受信任的存储库</span><br><span class="line">你正在从不受信任的存储库安装模块。如果你信任该存储库，请通过运行 Set-PSRepository</span><br><span class="line">cmdlet 更改其 InstallationPolicy 值。是否确实要从“PSGallery”安装模块?</span><br><span class="line">[Y] 是(Y)  [A] 全是(A)  [N] 否(N)  [L] 全否(L)  [S] 暂停(S)  [?] 帮助</span><br></pre></td></tr></table></figure><p>你可以按 <kbd>Y</kbd> 或 <kbd>A</kbd> 键，但是如果你觉得每次都这样麻烦的话，可以先执行下面的命令：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Set-PSRepository</span> <span class="literal">-Name</span> PSGallery <span class="literal">-InstallationPolicy</span> Trusted</span><br></pre></td></tr></table></figure><p>之后再安装模块就不会出现这个提示了。</p><p>现在，你可以直接使用 <code>screenfetch</code> 命令了。</p><p>如果你在执行 <code>screenfetch</code> 遇到以下错误：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">screenfetch : 在模块“windows-screenfetch”中找到“screenfetch”命令，但无法加载该模</span><br><span class="line">块。有关详细信息，请运行“Import-Module windows-screenfetch”。</span><br><span class="line">所在位置 行:1 字符: 1</span><br><span class="line">+ screenfetch</span><br><span class="line">+ ~~~~~~~~~~~</span><br><span class="line">    + CategoryInfo          : ObjectNotFound: (screenfetch:String) [], CommandNotFou</span><br><span class="line">   ndException</span><br><span class="line">    + FullyQualifiedErrorId : CouldNotAutoloadMatchingModule</span><br></pre></td></tr></table></figure><p>你可能会试着去执行：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Import-Module</span> windows<span class="literal">-screenfetch</span></span><br></pre></td></tr></table></figure><p>然而可能并没有什么用，接下来你可能会看到新的错误：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Import-Module : 无法加载文件 C:\Users\andot\Documents\WindowsPowerShell\Modules\windo</span><br><span class="line">ws-screenfetch\1.0.2\Art.psm1，因为在此系统上禁止运行脚本。有关详细信息，请参阅 https</span><br><span class="line">:/go.microsoft.com/fwlink/?LinkID=135170 中的 about_Execution_Policies。</span><br><span class="line">所在位置 行:1 字符: 1</span><br><span class="line">+ Import-Module windows-screenfetch</span><br><span class="line">+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~</span><br><span class="line">    + CategoryInfo          : SecurityError: (:) [Import-Module]，PSSecurityException</span><br><span class="line">    + FullyQualifiedErrorId : UnauthorizedAccess,Microsoft.PowerShell.Commands.Impor</span><br><span class="line">   tModuleCommand</span><br></pre></td></tr></table></figure><p>没关系，不要急，只要执行：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Set-ExecutionPolicy</span> <span class="literal">-Scope</span> CurrentUser Bypass</span><br></pre></td></tr></table></figure><p>然后选 <kbd>A</kbd> 就可以了。</p><p>接下来再执行 <code>screenfetch</code> 就可以看到类似于第一张截图的结果了，是不是很酷。</p><h1 id="Windows-10-控制台的字体配置"><a href="#Windows-10-控制台的字体配置" class="headerlink" title="Windows 10 控制台的字体配置"></a>Windows 10 控制台的字体配置</h1><p>什么？一点都不酷，跟截图上的一点都不像，简直丑爆了，是吗？</p><p>嗯，那是因为 Windows 控制台在中文系统下，默认是新宋体，如果显示器不是高分屏，字体设置的又不是很大，正好是点阵显示的话，效果还是不错的。然而很不巧，我用的 iMac 27 是 Retina 高分屏，所以，新宋体在控制台下就变得惨不忍睹了。差不多就是下图这样的效果吧。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/03/16/how-to-make-your-powershell-beautiful/ScreenFetchWithNSimSun.png" alt="默认新宋体下显示ScreenFetch的效果" title>                </div>                <div class="image-caption">默认新宋体下显示ScreenFetch的效果</div>            </figure><p>不只是汉字不好看，英文更是难看。在默认的<ruby>代码页<rp> (</rp><rt>CodePage</rt><rp>) </rp></ruby> <ruby>GBK<rp> (</rp><rt>936</rt><rp>) </rp></ruby> 下面可选的字体不多，只有几个汉字字体和两个日文字体。那些汉字字体在控制台下面没有一个好看的，日文字体更是没法看。如果用：</p><figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line"><span class="built_in">chcp</span> <span class="number">65001</span></span><br></pre></td></tr></table></figure><p>切换到 <ruby>UTF-8<rp> (</rp><rt>65001</rt><rp>) </rp></ruby> 的<ruby>代码页<rp> (</rp><rt>CodePage</rt><rp>) </rp></ruby>，会多出几个可选的英文字体，其中 <code>Consolas</code> 这个字体看上去倒还可以，不过汉字仍然是新宋体，还是不够完美，差不多是这样的效果吧：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/03/16/how-to-make-your-powershell-beautiful/ScreenFetchWithConsolas.png" alt="Consolas下显示ScreenFetch的效果" title>                </div>                <div class="image-caption">Consolas下显示ScreenFetch的效果</div>            </figure><p>之前在 Linux 下使用 VSCode 时，使用过一款 <a href="https://github.com/be5invis/Sarasa-Gothic" target="_blank" rel="noopener"><ruby>更纱黑体<rp> (</rp><rt>Sarasa-Gothic</rt><rp>) </rp></ruby></a>，感觉非常不错。当时选择这款字体的原因是，在 Linux 下使用 VSCode 时， 如果使用其它的汉字等宽字体，比如“文泉驿等宽正黑”、“文泉驿等宽微米黑”，或者使用一些比较好看的英文等宽字体配合这些汉字字体时，普通的空格会变成占 2 个字符的宽度。这样的话，代码排版就乱了。但是如果换成这款汉字字体，不但等宽问题解决了，而且这款字体还相当漂亮，不论是用于代码编写还是控制台，都是非常合适。于是果断安装这款字体到 Windows 10 上，然后在 Windows 10 的 PowerShell 控制台选中它：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/03/16/how-to-make-your-powershell-beautiful/SarasaTermSC.png" alt="选择Sarasa Term SC" title>                </div>                <div class="image-caption">选择Sarasa Term SC</div>            </figure><p>然后就能看到本文最开始的那幅图的效果了。而且这款字体已经内置了 PowerLine 字体，后面我们安装 PowerLine 效果的命令行提示符时，就不需要再装任何其它的 PowerLine 字体了。</p><p>到这里，字体配置已经完成一大半了。但是如果你使用 WSL，并且在 WSL 里面配置了 PowerLine 命令提示符的话，你会发现，在进入 Git 目录时会看到命令提示符中有个“□”：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/03/16/how-to-make-your-powershell-beautiful/WSL1.png" alt="字体中的乱码" title>                </div>                <div class="image-caption">字体中的乱码</div>            </figure><p>这个是个表情符“✎”，但是因为 Sarasa 系列字体中不包含表情符，因此就只能显示乱码了。不过也有解决办法，打开注册表编辑器，找到路径：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink</span><br></pre></td></tr></table></figure><p>在其中新建多字符串值，名称为：<code>Sarasa Term SC</code>，内容为：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">MICROSS.TTF,Microsoft Sans Serif,108,122</span><br><span class="line">MICROSS.TTF,Microsoft Sans Serif</span><br><span class="line">MINGLIU.TTC,PMingLiU</span><br><span class="line">MSMINCHO.TTC,MS PMincho</span><br><span class="line">BATANG.TTC,Batang</span><br><span class="line">MSYH.TTC,Microsoft YaHei UI</span><br><span class="line">MSJH.TTC,Microsoft JhengHei UI</span><br><span class="line">YUGOTHM.TTC,Yu Gothic UI</span><br><span class="line">MALGUN.TTF,Malgun Gothic</span><br><span class="line">SEGUISYM.TTF,Segoe UI Symbol</span><br></pre></td></tr></table></figure><p>这段内容是复制的<ruby>宋体<rp> (</rp><rt>SimSun</rt><rp>) </rp></ruby>的，如果你愿意的话，可以用同样的方法把 <code>Sarasa</code> 系列的都注册一下，如果像我这么懒的话，只修改这一个，然后重启计算机也是可以了。修改之后的效果是这样的：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/03/16/how-to-make-your-powershell-beautiful/WSL2.png" alt="没有乱码了" title>                </div>                <div class="image-caption">没有乱码了</div>            </figure><h1 id="Windows-10-控制台的颜色设置"><a href="#Windows-10-控制台的颜色设置" class="headerlink" title="Windows 10 控制台的颜色设置"></a>Windows 10 控制台的颜色设置</h1><p>字体问题解决了之后，接下来就是控制台的配色问题了。很多人放弃使用 Windows 的控制台，转而使用 ConEmu，Cmder 等第三方控制台程序，就是因为 Windows 控制台默认的配色太难看了。</p><p>但是实际上，Windows 10 的控制台的配色也是可以改的，很久之前 Windows 10 的控制台就已经支持 24 位真彩色，而且控制台也可以设置透明度，所以 Windows 10 的控制台完全可以配置的很酷炫。</p><p>但是很多人不知道该怎么对 Windows 10 的控制台进行配色，我也是从网上搜索了好久才找到了微软提供的这个<a href="https://github.com/Microsoft/console/releases" target="_blank" rel="noopener">配色工具</a>，而且它还是开源的。</p><p>它的使用方式很简单，下载之后，解压缩，不需要安装，在解压缩的目录下面打开命令行（或者把解压缩的目录加入到 PATH 环境变量中），然后执行：</p><figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line">colortool -b campbell</span><br></pre></td></tr></table></figure><p>然后会看到下图：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/03/16/how-to-make-your-powershell-beautiful/colortool.png" alt="ColorTool" title>                </div>                <div class="image-caption">ColorTool</div>            </figure><p>接下来在标题栏点击右键（或者单击标题栏左边的图标），在弹出菜单里面选最后一项属性，之后什么都不用做，直接点击确定，就可以把当前控制台和默认控制台的配色方案切换 campbell 主题了。下载包里自带了 8 款配色主题。其中 3 款是 ini 格式的，另外 5 款是 itermcolors 格式的。说实话，自带的这几款配色我都不是很喜欢，不过还好，我们可以从 <a href="https://github.com/mbadolato/iTerm2-Color-Schemes" target="_blank" rel="noopener">iTerm2 Color Schemes</a> 这里挑选我们喜欢的配色主题。</p><p>不过从 <a href="https://github.com/mbadolato/iTerm2-Color-Schemes" target="_blank" rel="noopener">iTerm2 Color Schemes</a> 的配色方案实在是太多了，选起来本身也是个麻烦事，我最后在这几款中选出了下面这几款配色，感觉还不错，推荐给大家：</p><ul><li><a href="https://raw.githubusercontent.com/mbadolato/iTerm2-Color-Schemes/master/schemes/ayu.itermcolors" target="_blank" rel="noopener">ayu</a></li><li><a href="https://raw.githubusercontent.com/mbadolato/iTerm2-Color-Schemes/master/schemes/Molokai.itermcolors" target="_blank" rel="noopener">Molokai</a></li><li><a href="https://raw.githubusercontent.com/mbadolato/iTerm2-Color-Schemes/master/schemes/Cobalt2.itermcolors" target="_blank" rel="noopener">Cobalt2</a></li><li><a href="https://raw.githubusercontent.com/mbadolato/iTerm2-Color-Schemes/master/schemes/Pandora.itermcolors" target="_blank" rel="noopener">Pandora</a></li><li><a href="https://raw.githubusercontent.com/mbadolato/iTerm2-Color-Schemes/master/schemes/Thayer%20Bright.itermcolors" target="_blank" rel="noopener">ThayerBright</a></li><li><a href="https://raw.githubusercontent.com/mbadolato/iTerm2-Color-Schemes/master/schemes/Symfonic.itermcolors" target="_blank" rel="noopener">Symfonic</a></li><li><a href="https://raw.githubusercontent.com/mbadolato/iTerm2-Color-Schemes/master/schemes/Red%20Sands.itermcolors" target="_blank" rel="noopener">RedSands</a></li><li><a href="https://raw.githubusercontent.com/mbadolato/iTerm2-Color-Schemes/master/schemes/Mathias.itermcolors" target="_blank" rel="noopener">Mathias</a></li></ul><p>我这样选择理由是，在这几款配色下，文字识别度都比较好，我我个人最喜欢的是 ayu，其次是 Molokai。尤其是 ayu，在控制台上的文字辨识度感觉比使用 ConEmu，Cmder 带的配色还要好，而且色彩上也比较柔和。话说前面最初晒的 4 幅图片都是在 ayu 主题下的效果，这里就不再重复贴图了。</p><h1 id="PowerShell-的彩色文件列表"><a href="#PowerShell-的彩色文件列表" class="headerlink" title="PowerShell 的彩色文件列表"></a>PowerShell 的彩色文件列表</h1><p>虽然控制台的配色问题解决了，但是在默认情况下， PowerShell 的文件列表并不会彩色显示。</p><p>想要文件列表彩色显示的话，最简单的方法就是安装一个 PowerShell 模块：<a href="https://github.com/Davlind/PSColor" target="_blank" rel="noopener">PSColor</a>。</p><p>这个模块安装使用都很简单，打开 Windows PowerShell 管理员控制台，输入：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Install-Module</span> PSColor</span><br></pre></td></tr></table></figure><p>就可以了。</p><p>如果你想使用普通用户来安装，打开 WIndows PowerShell 控制台，输入：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Install-Module</span> PSColor <span class="literal">-Scope</span> CurrentUser</span><br></pre></td></tr></table></figure><p>当然安装完了之后，直接输入 ls，显示的还是黑白效果的文件列表，你还需要启动它，不论是在管理员控制台，还是普通用户控制台下，都可以直接输入：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Import-Module</span> PSColor</span><br></pre></td></tr></table></figure><p>来导入该模块。之后，再输入 ls 就可以看到彩色文件列表了，如图：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/03/16/how-to-make-your-powershell-beautiful/PSColor.png" alt="PSColor彩色文件列表" title>                </div>                <div class="image-caption">PSColor彩色文件列表</div>            </figure><p>哪些文件类型可以被加亮显示是可以配置的，在 <a href="https://github.com/Davlind/PSColor" target="_blank" rel="noopener">PSColor</a> 官方的 README 中有介绍，这里就不转述了。不过这个配置方式是 PowerShell 式的，如果能直接像上面使用 itermcolors 文件配置控制台色彩一样，直接用 Linux 平台上的现成的 dircolors 配置文件的话，会不会更方便呢？这个想法很好，而且还真的有人实现了，它就是 <a href="https://github.com/DHowett/DirColors" target="_blank" rel="noopener">DirColors</a>。</p><p>这也是一个 PowerShell 模块，安装方式跟 <a href="https://github.com/Davlind/PSColor" target="_blank" rel="noopener">PSColor</a> 一样，使用：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Install-Module</span> DirColors</span><br></pre></td></tr></table></figure><p>或者</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Install-Module</span> DirColors <span class="literal">-Scope</span> CurrentUser</span><br></pre></td></tr></table></figure><p>就可以安装上了。之后，使用：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Import-Module</span> DirColors</span><br></pre></td></tr></table></figure><p>导入该模块。接下来，如果你想要载入某个现成的 dircolors 配置文件的话，只需要用：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Update-DirColors</span> ~\dir_colors</span><br></pre></td></tr></table></figure><p>这条命令就可以了。</p><p>其中 <code>~\dir_colors</code> 就是配置文件的路径，关于 dir_colors 的配置文件，在 github 上可以搜到不少，比如：<a href="https://github.com/seebi/dircolors-solarized" target="_blank" rel="noopener">dircolors-solarized</a>。这里就不再列举更多了。</p><h1 id="PowerShell-的-PowerLine-提示符"><a href="#PowerShell-的-PowerLine-提示符" class="headerlink" title="PowerShell 的 PowerLine 提示符"></a>PowerShell 的 PowerLine 提示符</h1><p>用过 Mac OS X 的同学，大部分应该都知道 <a href="https://github.com/robbyrussell/oh-my-zsh" target="_blank" rel="noopener">oh-my-zsh</a> 这个玩意，它是一个改进命令行体验的工具，安装之后，会让你的命令行操作爽的停不下来。</p><p>PowerShell 上也有一个类似的模块，叫 <a href="https://github.com/JanJoris/oh-my-posh" target="_blank" rel="noopener">oh-my-posh</a>。因为它的 git 命令提示符部分依赖 <a href="https://github.com/dahlbyk/posh-git" target="_blank" rel="noopener">post-git</a>，所以这两个模块都要安装：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Install-Module</span> posh<span class="literal">-git</span> <span class="literal">-Scope</span> CurrentUser</span><br><span class="line"><span class="built_in">Install-Module</span> oh<span class="literal">-my</span><span class="literal">-posh</span> <span class="literal">-Scope</span> CurrentUser</span><br></pre></td></tr></table></figure><p>安装完之后，要想使用，还需要安装 <a href="https://git-scm.com/" target="_blank" rel="noopener">git for windows</a>，普通版和 Portable 版本都可以，但是需要把安装之后的可执行文件目录加到 PATH 中。</p><p>之后直接执行：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Import-Module</span> posh<span class="literal">-git</span></span><br><span class="line"><span class="built_in">Import-Module</span> oh<span class="literal">-my</span><span class="literal">-posh</span></span><br><span class="line"><span class="built_in">Set-Theme</span> PowerLine</span><br></pre></td></tr></table></figure><p>就可以加载模块和 PowerLine 主题了。</p><p>如果你觉得每次启动这些模块都要手动输入很麻烦的话，可以编辑：</p><blockquote><p>C:\Users\andot\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1</p></blockquote><p>这个文件。</p><p>把下列代码加入即可：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Import-Module</span> DirColors</span><br><span class="line"><span class="built_in">Update-DirColors</span> ~/dircolors</span><br><span class="line"><span class="built_in">Import-Module</span> posh<span class="literal">-git</span></span><br><span class="line"><span class="keyword">if</span> (!(<span class="built_in">Get-SshAgent</span>)) &#123;</span><br><span class="line">    <span class="built_in">Start-SshAgent</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">Import-Module</span> oh<span class="literal">-my</span><span class="literal">-posh</span></span><br><span class="line"><span class="built_in">Set-Theme</span> PowerLine</span><br><span class="line">Screenfetch</span><br></pre></td></tr></table></figure><p>这样启动控制台之后就可以看到这个画面了：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/03/16/how-to-make-your-powershell-beautiful/screenfetch.png" alt="ScreenFetch" title>                </div>                <div class="image-caption">ScreenFetch</div>            </figure><p>如果你想在 VSCode 的控制台上也能使用的话，可以把上面的代码加入到：</p><blockquote><p>C:\Users\andot\Documents\WindowsPowerShell\Microsoft.VSCode_profile.ps1</p></blockquote><p>中，然后打开 VSCode 的控制台时，VSCode 的 PowerShell 控制台也会自动执行这些脚本，效果如下：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2018/03/16/how-to-make-your-powershell-beautiful/screenfetchOnVSCode.png" alt="VSCode 下的 PowerShell 控制台" title>                </div>                <div class="image-caption">VSCode 下的 PowerShell 控制台</div>            </figure><p>上面这一段代码中，最后一句 <code>Screenfetch</code> 会占用比较长的时间，如果你不想每次点开控制台都要等上几秒钟的话，可以不要这一句，只是效果不够装逼了而已。</p><p>安装了 oh-my-posh 之后，命令行操作爽多了，但是还有一个问题未解决。</p><p>如果把控制台<ruby>代码页<rp> (</rp><rt>CodePage</rt><rp>) </rp></ruby>切换到 <ruby>UTF-8<rp> (</rp><rt>65001</rt><rp>) </rp></ruby> 的情况下，在 PowerShell 命令行中输入汉字的话，汉字、日文、表情符等非英文字符会有重叠问题，如果按 Tab 键，在下面出现的提示补全信息中的汉字、日文、表情符等也会出现重叠问题。开始我以为这是 Windows 控制台的锅，后来发现 WSL 下并不会有这个问题。然后经过搜索，确认这是 PowerShell 的 Bug，目前虽然有人提供了补丁，但是官方似乎没有合并的意思：</p><blockquote><p><a href="https://github.com/PowerShell/PowerShell/pull/5739" target="_blank" rel="noopener">https://github.com/PowerShell/PowerShell/pull/5739</a></p></blockquote><p>另外，就算 github 上这个 PowerShell 项目把这个问题解决了，它跟 Windows 10 中自带的 PowerShell 也不是一回事。从 Github 上下载安装的这个 PowerShell 是一个独立的版本，安装之后，跟 Windows 10 自带的那个 PowerShell 是并存的，并不是升级替换。</p><p>不过还好，这个问题目前也有解决的方法，那就是升级 Windows 10 的 PowerShell 自带的 <a href="https://www.powershellgallery.com/packages/PSReadline/2.0.0-beta1" target="_blank" rel="noopener">PSReadLine</a> 模块到最新的 2.0.0-beta1 版，最新的 2.0.0-beta1 版本已经把这个问题基本解决了，虽然还是不够完美，但是比没完全解决的时候还是好多了。不过升级 PSReadLine 之前，还要先升级 Windows 10 自带 <a href="https://www.powershellgallery.com/packages/PowerShellGet/1.6.0" target="_blank" rel="noopener">PowerShellGet</a> 模块到最新版，因为自带的版本不支持安装 beta 版的 PSReadLine (lll￢ω￢)。安装过程倒不复杂，打开 PowerShell 管理员控制台，执行：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Install-Module -Name PowerShellGet -Force</span><br><span class="line">Install-Module -Name PSReadLine -AllowPrerelease -Force</span><br></pre></td></tr></table></figure><p>然后重启 PowerShell 控制台，就可以在 PowerShell 命令行里正常输入汉字了。</p><p>好了，今天就先写到这里，明天再写如何配置让 PowerShell 变得更好用。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;因为最近开始 Hprose 2.0 for .NET 的开发工作了，所以使用的操作系统从 Mac OS X 转到了 Windows 10 上来。在之前用 Mac OS X 和 Linux 时，命令行是经常用到的，转到 Windows 10 上之后，虽然大部分操作通过图形界面都可以完成了，但是有些操作还是会用到命令行，但是 Windows 10 的默认命令行控制台实在是丑的要命，让人感觉不爽。所以，我最后打算先把 Windows 10 的控制台美化一下，接下来工作时，也会变得心情舒畅。&lt;/p&gt;
    
    </summary>
    
      <category term="编程" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/"/>
    
      <category term="PowerShell" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/PowerShell/"/>
    
    
      <category term="powershell" scheme="https://coolcode.org/tags/powershell/"/>
    
  </entry>
  
  <entry>
    <title>日语的时间（二）</title>
    <link href="https://coolcode.org/2017/10/06/my-japanese-learning-notes-24/"/>
    <id>https://coolcode.org/2017/10/06/my-japanese-learning-notes-24/</id>
    <published>2017-10-06T15:34:09.000Z</published>
    <updated>2020-04-26T12:55:49.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="星期"><a href="#星期" class="headerlink" title="星期"></a>星期</h1><p>星期在日语中用<span lang="ja">「曜日」</span>表示，一周七天分别为：</p><table><thead><tr><th style="text-align:center">周日</th><th style="text-align:center">周一</th><th style="text-align:center">周二</th><th style="text-align:center">周三</th><th style="text-align:center">周四</th><th style="text-align:center">周五</th><th style="text-align:center">周六</th></tr></thead><tbody><tr><td style="text-align:center"><span lang="ja">日曜日</span></td><td style="text-align:center"><span lang="ja">月曜日</span></td><td style="text-align:center"><span lang="ja">火曜日</span></td><td style="text-align:center"><span lang="ja">水曜日</span></td><td style="text-align:center"><span lang="ja">木曜日</span></td><td style="text-align:center"><span lang="ja">金曜日</span></td><td style="text-align:center"><span lang="ja">土曜日</span></td></tr><tr><td style="text-align:center"><span lang="ja">にちようび</span></td><td style="text-align:center"><span lang="ja">げつようび</span></td><td style="text-align:center"><span lang="ja">かようび</span></td><td style="text-align:center"><span lang="ja">すいようび</span></td><td style="text-align:center"><span lang="ja">もくようび</span></td><td style="text-align:center"><span lang="ja">きんようび</span></td><td style="text-align:center"><span lang="ja">どようび</span></td></tr></tbody></table><p>也就是说，这一周七天是日月加五行构成，只不过五行的顺序不太好记，网上流传着这样一个记忆法，只需看一遍便能记住：</p><blockquote><p>周日对应日，都有个「日」字<br>周一对应月，天上只有一个月亮<br>周二对应火，火字上面有两个点<br>周三对应水，有个偏旁叫三点水<br>周四对应木，「木」字有四笔构成<br>周五对应金，有种商店叫五金店<br>周六对应土，其它的都分完了，就剩它了，所以就分给周六了。</p></blockquote><p>现在你记住了吗？</p><p>提问星期的疑问词是「<span lang="ja">何曜日（なんようび）</span>」。</p><h1 id="年"><a href="#年" class="headerlink" title="年"></a>年</h1><p>在日语中，年份按照全部数字的位数来读。</p><blockquote><p><span lang="ja">1980年 （せんきゅうひゃくはちじゅうねん）</span><br><span lang="ja">2006年 （にせんろくねん）</span><br><span lang="ja">昭和64年（しょうわろくじゅうよねん）</span></p></blockquote><h1 id="月"><a href="#月" class="headerlink" title="月"></a>月</h1><blockquote><p><span lang="ja">一月（いちがつ）</span><br><span lang="ja">二月（にがつ）</span><br><span lang="ja">三月（さんがつ）</span><br><span lang="ja">四月（しがつ）</span><br><span lang="ja">五月（ごがつ）</span><br><span lang="ja">六月（ろくがつ）</span><br><span lang="ja">七月（しちがつ）</span><br><span lang="ja">八月（はちがつ）</span><br><span lang="ja">九月（くがつ）</span><br><span lang="ja">十月（じゅうがつ）</span><br><span lang="ja">十一月（じゅういちがつ）</span><br><span lang="ja">十二月（じゅうにがつ）</span></p></blockquote><p>月份没什么难点，注意一下 4、7、9 月的读音就可以了。</p><h1 id="日"><a href="#日" class="headerlink" title="日"></a>日</h1><blockquote><p><span lang="ja">1日（ついたち）</span><br><span lang="ja">2日（ふつか）</span><br><span lang="ja">3日（みっか）</span><br><span lang="ja">4日（よっか）</span><br><span lang="ja">5日（いつか）</span><br><span lang="ja">6日（むいか）</span><br><span lang="ja">7日（なのか）</span><br><span lang="ja">8日（ようか）</span><br><span lang="ja">9日（ここのか）</span><br><span lang="ja">10日（とおか）</span><br><span lang="ja">11日（じゅういちにち）</span><br><span lang="ja">12日（じゅうににち）</span><br><span lang="ja">13日（じゅうさんにち）</span><br><span lang="ja">14日（じゅうよっか）</span><br><span lang="ja">15日（じゅうごにち）</span><br><span lang="ja">16日（じゅうろくにち）</span><br><span lang="ja">17日（じゅうしちにち）</span><br><span lang="ja">18日（じゅうはちにち）</span><br><span lang="ja">19日（じゅうくにち）</span><br><span lang="ja">20日（はつか）</span><br><span lang="ja">21日（にじゅういちにち）</span><br><span lang="ja">22日（にじゅうににち）</span><br><span lang="ja">23日（にじゅうさんにち）</span><br><span lang="ja">24日（にじゅうよっか）</span><br><span lang="ja">25日（にじゅうごにち）</span><br><span lang="ja">26日（にじゅうろくにち）</span><br><span lang="ja">27日（にじゅうしちにち）</span><br><span lang="ja">28日（にじゅうはちにち）</span><br><span lang="ja">29日（にじゅうくにち）</span><br><span lang="ja">30日（さんじゅう）</span><br><span lang="ja">31日（さんじゅういちにち）</span></p></blockquote><p>其中，1-10 日和 20 日是训读，11-19，21-31 日是音读（14 日、24 日比较特殊，前面是音读，后面是训读）。</p><h1 id="周数和年数"><a href="#周数和年数" class="headerlink" title="周数和年数"></a>周数和年数</h1><p>几个周和几年跟小时，分，秒一样，是在后面加<span lang="ja">「間（かん）」</span>就可以了。比如：</p><blockquote><p><span lang="ja">一週間（いっしゅうかん）</span><br><span lang="ja">三週間（さんしゅうかん）</span><br><span lang="ja">十週間（じっしゅうかん）</span><br><span lang="ja">五年間（ごねんかん）</span></p></blockquote><p>需要注意的是 1、10 周有促音变。</p><h1 id="月数"><a href="#月数" class="headerlink" title="月数"></a>月数</h1><p>几个月用量词<span lang="ja">「か」</span>，可以写作<span lang="ja">「ヶ」「か」「ヵ」「個」</span>等，而且月数的月读音为<span lang="ja">「げつ」</span>，跟月份的月<span lang="ja">「がつ」</span>的读音不同。例如：</p><blockquote><p><span lang="ja">一ヶ月（いっかげつ）</span><br><span lang="ja">二ヶ月（にかげつ）</span><br><span lang="ja">三ヶ月（さんかげつ）</span><br><span lang="ja">四ヶ月（よんかげつ）</span><br><span lang="ja">五ヶ月（こかげつ）</span><br><span lang="ja">六ヶ月（ろっかげつ）</span><br><span lang="ja">七ヶ月（ななかげつ）</span><br><span lang="ja">八ヶ月（はっかげつ／はちかげつ）</span><br><span lang="ja">九ヶ月（きゅうかげつ）</span><br><span lang="ja">十ヶ月 （じっかげつ／じゅうかげつ）</span></p></blockquote><p>注意，1、6、8、10 个月有促音变，8 和 10 个月也有不发生音变的读法。</p><p>另外，一个月，两个月还有训读法：</p><blockquote><p><span lang="ja">一月（ひとつき）</span><br><span lang="ja">二月（ふたつき）</span></p></blockquote><p>所以，在文章中看到<span lang="ja">「一月」「二月」</span>时，要根据上下文来判断是指的月份还是月数，如果是前者就读<span lang="ja">「いちがつ」「にがつ」</span>，如果是后者就读<span lang="ja">「ひとつき」「ふたつき」</span>。</p><h1 id="天数"><a href="#天数" class="headerlink" title="天数"></a>天数</h1><p>一天写作<span lang="ja">「一日」</span>，但是读作<span lang="ja">「いちにち」</span>，作为一号的时候，读作<span lang="ja">「ついたち」</span>。这两个不能混淆。其它的天数跟日期的读法是一样的，为了避免混淆，也可以在后面加上<span lang="ja">「間（かん）」</span>来表示天数。</p><h1 id="相对的时间点"><a href="#相对的时间点" class="headerlink" title="相对的时间点"></a>相对的时间点</h1><table><thead><tr><th style="text-align:center">大前天</th><th style="text-align:center">前天</th><th style="text-align:center">昨天</th><th style="text-align:center">今天</th><th style="text-align:center">明天</th><th style="text-align:center">后天</th><th style="text-align:center">大后天</th></tr></thead><tbody><tr><td style="text-align:center"><span lang="ja">一昨昨日</span></td><td style="text-align:center"><span lang="ja">一昨日</span></td><td style="text-align:center"><span lang="ja">昨日</span></td><td style="text-align:center"><span lang="ja">今日</span></td><td style="text-align:center"><span lang="ja">明日</span></td><td style="text-align:center"><span lang="ja">明後日</span></td><td style="text-align:center"><span lang="ja">明明後日</span></td></tr><tr><td style="text-align:center"><span lang="ja">さきおととい</span></td><td style="text-align:center"><span lang="ja">おととい</span></td><td style="text-align:center"><span lang="ja">きのう</span></td><td style="text-align:center"><span lang="ja">きょう</span></td><td style="text-align:center"><span lang="ja">あした</span></td><td style="text-align:center"><span lang="ja">あさって</span></td><td style="text-align:center"><span lang="ja">しあさって</span></td></tr></tbody></table><table><thead><tr><th style="text-align:center">上上周</th><th style="text-align:center">上周</th><th style="text-align:center">本周</th><th style="text-align:center">下周</th><th style="text-align:center">下下周</th></tr></thead><tbody><tr><td style="text-align:center"><span lang="ja">先々週</span></td><td style="text-align:center"><span lang="ja">先週</span></td><td style="text-align:center"><span lang="ja">今週</span></td><td style="text-align:center"><span lang="ja">来週</span></td><td style="text-align:center"><span lang="ja">再来週</span></td></tr><tr><td style="text-align:center"><span lang="ja">せんせんしゅう</span></td><td style="text-align:center"><span lang="ja">せんしゅう</span></td><td style="text-align:center"><span lang="ja">こんしゅう</span></td><td style="text-align:center"><span lang="ja">らいしゅう</span></td><td style="text-align:center"><span lang="ja">さらいしゅう</span></td></tr></tbody></table><table><thead><tr><th style="text-align:center">上上个月</th><th style="text-align:center">上个月</th><th style="text-align:center">这个月</th><th style="text-align:center">下个月</th><th style="text-align:center">下下个月</th></tr></thead><tbody><tr><td style="text-align:center"><span lang="ja">先々月</span></td><td style="text-align:center"><span lang="ja">先月</span></td><td style="text-align:center"><span lang="ja">今月</span></td><td style="text-align:center"><span lang="ja">来月</span></td><td style="text-align:center"><span lang="ja">再来月</span></td></tr><tr><td style="text-align:center"><span lang="ja">せんせんげつ</span></td><td style="text-align:center"><span lang="ja">せんげつ</span></td><td style="text-align:center"><span lang="ja">こんげつ</span></td><td style="text-align:center"><span lang="ja">らいげつ</span></td><td style="text-align:center"><span lang="ja">さらいげつ</span></td></tr></tbody></table><table><thead><tr><th style="text-align:center">大前年</th><th style="text-align:center">前年</th><th style="text-align:center">去年</th><th style="text-align:center">今年</th><th style="text-align:center">明年</th><th style="text-align:center">后年</th><th style="text-align:center">大后年</th></tr></thead><tbody><tr><td style="text-align:center"><span lang="ja">一昨々年</span></td><td style="text-align:center"><span lang="ja">一昨年</span></td><td style="text-align:center"><span lang="ja">去年</span></td><td style="text-align:center"><span lang="ja">今年</span></td><td style="text-align:center"><span lang="ja">来年</span></td><td style="text-align:center"><span lang="ja">再来年</span></td><td style="text-align:center"><span lang="ja">大後年</span></td></tr><tr><td style="text-align:center"><span lang="ja">さきおととし</span></td><td style="text-align:center"><span lang="ja">おととし</span></td><td style="text-align:center"><span lang="ja">きょねん</span></td><td style="text-align:center"><span lang="ja">ことし</span></td><td style="text-align:center"><span lang="ja">らいねん</span></td><td style="text-align:center"><span lang="ja">さらいねん</span></td><td style="text-align:center"><span lang="ja">だいこうねん</span></td></tr></tbody></table>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;星期&quot;&gt;&lt;a href=&quot;#星期&quot; class=&quot;headerlink&quot; title=&quot;星期&quot;&gt;&lt;/a&gt;星期&lt;/h1&gt;&lt;p&gt;星期在日语中用&lt;span lang=&quot;ja&quot;&gt;「曜日」&lt;/span&gt;表示，一周七天分别为：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
      
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>日语的时间（一）</title>
    <link href="https://coolcode.org/2017/10/05/my-japanese-learning-notes-23/"/>
    <id>https://coolcode.org/2017/10/05/my-japanese-learning-notes-23/</id>
    <published>2017-10-05T15:34:09.000Z</published>
    <updated>2020-04-22T12:10:49.000Z</updated>
    
    <content type="html"><![CDATA[<p>今天来集中学习一下日语的时间名词。</p><h1 id="小时"><a href="#小时" class="headerlink" title="小时"></a>小时</h1><blockquote><p><span lang="ja">1時（いちじ）</span><br><span lang="ja">2時（にじ）</span><br><span lang="ja">3時（さんじ）</span><br><span lang="ja">4時（よじ）</span><br><span lang="ja">5時（ごじ）</span><br><span lang="ja">6時（ろくじ）</span><br><span lang="ja">7時（しちじ）</span><br><span lang="ja">8時（はちじ）</span><br><span lang="ja">9時（くじ）</span><br><span lang="ja">10時（じゅうじ）</span><br><span lang="ja">11時（じゅういちじ）</span><br><span lang="ja">12時（じゅうにじ）</span><br><span lang="ja">13時（じゅうさんじ）</span><br><span lang="ja">14時（じゅうよじ）</span><br><span lang="ja">15時（じゅうごじ）</span><br><span lang="ja">16時（じゅうろくじ）</span><br><span lang="ja">17時（じゅうしちじ）</span><br><span lang="ja">18時（じゅうはちじ）</span><br><span lang="ja">19時（じゅうくじ）</span><br><span lang="ja">20時（にじゅうじ）</span><br><span lang="ja">21時（にじゅういちじ）</span><br><span lang="ja">22時（にじゅうにじ）</span><br><span lang="ja">23時（にじゅうさんじ）</span><br><span lang="ja">24時（にじゅうよじ）</span><br><span lang="ja">0時（れいじ）</span></p></blockquote><p>半点用「<span lang="ja">半（はん）</span>」来表示，例如：</p><blockquote><p><span lang="ja">1時半（いちじはん）</span><br><span lang="ja">9時半（くじはん）</span><br><span lang="ja">23時半（にじゅうさんじはん）</span></p></blockquote><p>疑问词用「<span lang="ja">何時（なんじ）</span>」。</p><p>上面的都是时间点，如果要表示时间段，比如 3 个小时，而不是 3 点钟，只需要在后面加上「<span lang="ja">‐間（かん）</span>」就可以了，比如：</p><blockquote><p><span lang="ja">3時間（さんじかん）</span><br><span lang="ja">11時間半（じゅういちじかんはん）</span></p></blockquote><p>用来表示时间段的疑问词是「<span lang="ja">何時間（なんじかん）</span>」。</p><h1 id="分钟"><a href="#分钟" class="headerlink" title="分钟"></a>分钟</h1><blockquote><p><span lang="ja">1分（いっぷん）</span><br><span lang="ja">2分（にふん）</span><br><span lang="ja">3分（さんぷん）</span><br><span lang="ja">4分（よんぷん）</span><br><span lang="ja">5分（ごふん）</span><br><span lang="ja">6分（ろっぷん）</span><br><span lang="ja">7分（ななふん／しちふん）</span><br><span lang="ja">8分（はっぷん／はちふん）</span><br><span lang="ja">9分（きゅうふん）</span><br><span lang="ja">10分（じっぷん／じゅっぷん）</span><br><span lang="ja">11分（じゅういっぷん）</span><br><span lang="ja">14分（じゅうよんぷん）</span><br><span lang="ja">20分（にじっぷん／にじゅっぷん）</span><br><span lang="ja">25分（にじゅうごふん）</span><br><span lang="ja">100分（ひゃっぷん）</span></p></blockquote><p>分钟的个位数为 1，6，8，10 时会发生促音变，此外，个位为 3 和 4 时也读「<span lang="ja">ぷん</span>」。</p><p>提问分钟的疑问词是「<span lang="ja">何分（なんぷん）</span>」。</p><p>提问时间的疑问词可以是「<span lang="ja">何時何分（なんじなんぷん）</span>」，也可以用「<span lang="ja">いつ</span>」，但是「<span lang="ja">いつ</span>」的意思是什么时候，表示的范围更广一些。</p><h1 id="秒"><a href="#秒" class="headerlink" title="秒"></a>秒</h1><p>秒直接在数字后面跟「<span lang="ja">秒（びょう）</span>」就可以了，读音都一样。</p><p>分，秒也可以在后面加「<span lang="ja">‐間（かん）</span>」来表示时间段。</p><h1 id="一天中的各个时间点（段）"><a href="#一天中的各个时间点（段）" class="headerlink" title="一天中的各个时间点（段）"></a>一天中的各个时间点（段）</h1><table><thead><tr><th style="text-align:center">日语</th><th style="text-align:center">中文</th></tr></thead><tbody><tr><td style="text-align:center"><span lang="ja">日の出（ひので）</span></td><td style="text-align:center">日出</td></tr><tr><td style="text-align:center"><span lang="ja">朝（あさ）</span></td><td style="text-align:center">早上</td></tr><tr><td style="text-align:center"><span lang="ja">午前（ごぜん）</span></td><td style="text-align:center">上午</td></tr><tr><td style="text-align:center"><span lang="ja">昼（ひる）</span></td><td style="text-align:center">中午，白天</td></tr><tr><td style="text-align:center"><span lang="ja">午後（ごご）</span></td><td style="text-align:center">下午</td></tr><tr><td style="text-align:center"><span lang="ja">夕方（ゆうがた）</span></td><td style="text-align:center">傍晚</td></tr><tr><td style="text-align:center"><span lang="ja">夕暮れ（ゆうぐれ）</span></td><td style="text-align:center">黄昏</td></tr><tr><td style="text-align:center"><span lang="ja">日の入り（ひのいり）</span></td><td style="text-align:center">日落</td></tr><tr><td style="text-align:center"><span lang="ja">晩（ばん）</span></td><td style="text-align:center">晚上</td></tr><tr><td style="text-align:center"><span lang="ja">夜（よる）</span></td><td style="text-align:center">夜晚</td></tr><tr><td style="text-align:center"><span lang="ja">真夜中（まよなか）</span></td><td style="text-align:center">午夜</td></tr><tr><td style="text-align:center"><span lang="ja">夜通し（よどおし）</span></td><td style="text-align:center">通宵</td></tr><tr><td style="text-align:center"><span lang="ja">夜明け（よあけ）</span></td><td style="text-align:center">黎明</td></tr></tbody></table><p>表示昨天晚上还有几个特别的词：<span lang="ja">昨夜（さくや）</span>、<span lang="ja">昨夜（ゆうべ）</span>、<span lang="ja">昨晩（さくばん）</span>。</p><p>今天晚上是「<span lang="ja">今晩（こんばん）</span>」。就是「<span lang="ja">こんばんは</span>」（晚上好）里面的「<span lang="ja">こんばん</span>」。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;今天来集中学习一下日语的时间名词。&lt;/p&gt;
&lt;h1 id=&quot;小时&quot;&gt;&lt;a href=&quot;#小时&quot; class=&quot;headerlink&quot; title=&quot;小时&quot;&gt;&lt;/a&gt;小时&lt;/h1&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;span lang=&quot;ja&quot;&gt;1時（いちじ）&lt;/span&gt;&lt;b
      
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>形容词的连用形</title>
    <link href="https://coolcode.org/2017/10/04/my-japanese-learning-notes-22/"/>
    <id>https://coolcode.org/2017/10/04/my-japanese-learning-notes-22/</id>
    <published>2017-10-04T15:11:51.000Z</published>
    <updated>2020-04-20T13:49:49.000Z</updated>
    
    <content type="html"><![CDATA[<p>Ⅰ类形容词和Ⅱ类形容词都各有两种连用形，分别被称为第一连用形和第二连用形。</p><h1 id="Ⅰ类形容词的第一连用形"><a href="#Ⅰ类形容词的第一连用形" class="headerlink" title="Ⅰ类形容词的第一连用形"></a>Ⅰ类形容词的第一连用形</h1><p>Ⅰ类形容词的第一连用形是将词尾的「い」变成「く」。例如：</p><blockquote><p><span lang="ja">高い　→　高く</span><br><span lang="ja">易しい　→　易しく</span></p></blockquote><p>我们前面学习形容词的时态时，Ⅰ类形容词的否定形式，其实就是Ⅰ类形容词的第一连用形 + ない 构成的。例如：</p><blockquote><p><span lang="ja">高い＋ない　→　高く＋ない　→　高くない</span><br><span lang="ja">易しい＋ない　→　易しく＋ない　→　易しくない</span></p></blockquote><p>而「ない」本身也是一个形容词，所以形容词的否定形式也可以看作是由形容词的第一连用形跟ない构成的一个新的Ⅰ类形容词。所以，Ⅰ类形容词的过去否定形式，可以直接看成是这个新的否定形式的Ⅰ类形容词根据过去形式的变形规则把最后的「い」变为「かった」来构成的。</p><p>形容词的第一连用形除了可以跟「ない」构成否定以外，还可以直接跟后面的形容词构成并列关系，比如：</p><blockquote><p><span lang="ja">高い＋大きい　→　高く大きい</span>　// 又高又大<br><span lang="ja">安い＋おいしい　→　安くおいしい</span>　// 便宜又好吃</p></blockquote><p>形容词的第一连用形还可以在句子中用于中顿、表示两个（或两个以上的）谓语或分句的并列。例如：</p><blockquote><p><span lang="ja">歌舞伎は能狂言より歴史が新しく、17世紀に始まった。</span><br><span lang="ja">日本は山が多く、川も少なくない。</span></p></blockquote><p>第一连用形还可以用于修饰动词，此时，Ⅰ类形容词在句中做副词用。例如：</p><blockquote><p><span lang="ja">美味しい＋なる　→　美味しくなる</span>　// 变得好吃<br><span lang="ja">広い＋造る　→　広く造る</span>　// 建造得宽敞<br><span lang="ja">安い＋する　→　安くする</span>　// 变得便宜</p></blockquote><h1 id="Ⅰ类形容词的第二连用形"><a href="#Ⅰ类形容词的第二连用形" class="headerlink" title="Ⅰ类形容词的第二连用形"></a>Ⅰ类形容词的第二连用形</h1><p>Ⅰ类形容词的第二连用形是将词尾的「い」变成「く」再后续「て」。例如：</p><blockquote><p><span lang="ja">高い　→　高くて</span><br><span lang="ja">易しい　→　易しくて</span></p></blockquote><p>第二连用形跟第一连用形一样，可以跟后面的形容词构成并列关系，也可以在句子中用于中顿、表示两个（或两个以上的）谓语或分句的并列。例如：</p><blockquote><p><span lang="ja">電車は速くて安い。</span>　// 电车又快又便宜。<br><span lang="ja">面白くて易しい日本語</span>　// 有趣又简单的日语。<br><span lang="ja">あの映画は面白くて、ためになる。</span>　// 那部电影有趣又有益。</p></blockquote><p>但第一连用形的这种用法主要用于书面语中。</p><h1 id="Ⅱ类形容词的第一连用形"><a href="#Ⅱ类形容词的第一连用形" class="headerlink" title="Ⅱ类形容词的第一连用形"></a>Ⅱ类形容词的第一连用形</h1><p>Ⅱ类形容词的第一连用形是将词尾「だ」变成「に」。例如：</p><blockquote><p><span lang="ja">好きだ　→　好きに</span></p></blockquote><p>Ⅱ类形容词的第一连用形只能用于修饰动词，不能用于形容词并列，也不能在句子中表示中顿。例如：</p><blockquote><p><span lang="ja">好きだ＋なる　→　好きになる</span>　// 变得喜欢<br><span lang="ja">綺麗だ＋掃除する　→　綺麗に掃除する</span>　// 打扫得干净</p></blockquote><h1 id="Ⅱ类形容词的第二连用形"><a href="#Ⅱ类形容词的第二连用形" class="headerlink" title="Ⅱ类形容词的第二连用形"></a>Ⅱ类形容词的第二连用形</h1><p>Ⅱ类形容词的第二连用形是将词尾「だ」变成「で」。例如：</p><blockquote><p><span lang="ja">好きだ　→　好きで</span></p></blockquote><p>Ⅱ类形容词跟Ⅰ类形容词的第二连用形用法相同。例如：</p><blockquote><p><span lang="ja">静かで広い部屋</span>　//　安静又宽敞的房间<br><span lang="ja">広くて静かな部屋</span>　//　宽敞又安静的房间</p></blockquote><blockquote><p><span lang="ja">張さんはテニスが上手で、水泳も得意だ。</span> // 张先生擅长网球，游泳也拿手。</p></blockquote><h1 id="形容词否定形态的连用形"><a href="#形容词否定形态的连用形" class="headerlink" title="形容词否定形态的连用形"></a>形容词否定形态的连用形</h1><p>不管是Ⅰ类形容词还是Ⅱ类形容词，它们的否定形式最后都是以「ない」结尾的，所以它们的否定形态都可以看作一个新的Ⅰ类形容词来对待。因此，它们的连用形也跟Ⅰ类形容词的变形一样，即第一连用形为：</p><blockquote><p><span lang="ja">A<sub>Ⅰ</sub>词干くなく</span><br><span lang="ja">A<sub>Ⅱ</sub>词干ではなく</span></p></blockquote><p>第二连用形为：</p><blockquote><p><span lang="ja">A<sub>Ⅰ</sub>词干くなくて</span><br><span lang="ja">A<sub>Ⅱ</sub>词干ではなくて</span></p></blockquote><p>我们再往下推，既然形容词如此，那么名词我们之前学了它的否定形式，也学了它的中顿形，这俩跟Ⅱ类形容词在形式上是一样的，那么它的否定形式的中顿形是不是也是一样的呢？没错，名词的否定中顿形如下：</p><blockquote><p><span lang="ja">Nではなくて</span></p></blockquote><p>那既然否定形式存在连用形，那过去时是否存在连用形呢？好吧，你想多了，过去时其实是不存在连用形的，如果是多个词通过连用形连接，那么你可以把连接之后的词看成是一个词，时态通过最后的词尾来决定。如果有多个句子通过连用形进行连接，时态是通过最后一个子句的时态来体现的。也就是说，连用形本身是不需要时态的，因此，过去时没有连用形。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Ⅰ类形容词和Ⅱ类形容词都各有两种连用形，分别被称为第一连用形和第二连用形。&lt;/p&gt;
&lt;h1 id=&quot;Ⅰ类形容词的第一连用形&quot;&gt;&lt;a href=&quot;#Ⅰ类形容词的第一连用形&quot; class=&quot;headerlink&quot; title=&quot;Ⅰ类形容词的第一连用形&quot;&gt;&lt;/a&gt;Ⅰ类形容词的第一
      
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>形容词的使用</title>
    <link href="https://coolcode.org/2017/10/03/my-japanese-learning-notes-21/"/>
    <id>https://coolcode.org/2017/10/03/my-japanese-learning-notes-21/</id>
    <published>2017-10-03T15:11:51.000Z</published>
    <updated>2020-04-15T01:19:49.000Z</updated>
    
    <content type="html"><![CDATA[<p>昨天介绍了形容词的分类，基本型和连体形以及形容词的时态。今天我们来讲讲形容词基本用法。</p><h1 id="修饰体言"><a href="#修饰体言" class="headerlink" title="修饰体言"></a>修饰体言</h1><p>首先形容词可以直接用来修饰名词（体言），就像昨天讲的那样使用形容词的连体形就可以了。例如：</p><blockquote><p><span lang="ja">静かな部屋</span>　　　// 安静的房间<br><span lang="ja">賑やかな町</span>　　　// 热闹的街道<br><span lang="ja">綺麗な教室</span>　　　// 干净的教室<br><span lang="ja">美味しい食べ物</span>　// 美味的食物<br><span lang="ja">高い車</span>　　　　　// 昂贵的汽车<br><span lang="ja">古い家</span>　　　　　// 老旧的房子</p></blockquote><p>另外，形容词（包含Ⅰ类形容词和Ⅱ类形容词）的现在否定，过去肯定，过去否定这三种时态的简体型，也可以直接用来修饰体言，也就是说这三种简体形式也是连体形。</p><p>虽然从语法上来说没什么问题，但是按照习惯来说，一般情况下基本上看不到用形容词的否定来直接修饰体言的，比如要表达“难吃的食物”，一般不用<span lang="ja">「美味しくない食べ物」</span>这种形式，而直接用<span lang="ja">「不味い食べ物」</span>来表达。</p><p>而用过去式的时候表达的意思往往暗含有现在不再是那个样子的意思，比如<span lang="ja">「美味しかった食べ物」</span>表达的意思中暗含有现在已经不好吃了。也就是说，过去式用来修饰名词时，表达的不是这个名词具有这样的属性，而是表示它过去短暂的一时的状态。所以当你要表示你昨天吃过好吃的食物的时候，一般也不用过去式。例如：</p><blockquote><p><span lang="ja">昨日は、美味しい食べ物を食べました。</span>　　　（表示客观描述）<br><span lang="ja">昨日は、美味しかった食べ物を食べました。</span>　（暗示现在那个食物已经不好吃了）</p></blockquote><p>虽然都可以翻译成“昨天我吃了好吃的食物。”，但是表达的意思却截然不同。</p><h1 id="形容词做谓语"><a href="#形容词做谓语" class="headerlink" title="形容词做谓语"></a>形容词做谓语</h1><p>形容词也可以直接作谓语来表示主语所具有的性质、感情、态度、能力、状态等。今天只讲表示性质的属性形容词。</p><p>上面所举的形容词修饰名词的例子中的形容词都是属性形容词。如果把它们改成形容词谓语句是这样的：</p><blockquote><p><span lang="ja">部屋が静かです。</span>　　　　// 房间很安静。<br><span lang="ja">街は賑やかです。</span>　　　　// 街道很热闹。<br><span lang="ja">教室は綺麗だ。</span>　　　　　// 教室很干净。<br><span lang="ja">食べ物は美味しいです。</span>　// 食物很美味。<br><span lang="ja">車が高いです。</span>　　　　　// 汽车很昂贵。<br><span lang="ja">家が古い。</span>　　　　　　　// 房子很老旧。</p></blockquote><p>从上面的例子，我们会发现，形容词做谓语时，既可以用简体形式，也可以用敬体形式。究竟该用敬体还是简体，跟应用场合有关。</p><p>在日常会话时，兄弟姐妹，亲朋好友，同学，同事之间以及其他不需要客气的场合使用简体。而在社交场合，比较陌生的场合，或需要跟对方保持一定距离的时候（日本的上流社会的人喜欢使用这样的语气来跟别人保持距离）以及其他需要客气的场合需要使用敬体。</p><p>而在书面语中，写给别人看的书信，留言，求职信，新闻等用敬体，而日记、论文、个人简历等则用简体。</p><p>形容词作谓语时，可以根据要表达的意思来使用不同的时态。比如表示否定，就用否定形式，表示过去就用过去形式。比如：</p><blockquote><p><span lang="ja">昨日の夕食は美味しかったです。</span> // 昨天的晚饭很美味。</p></blockquote><p>请注意，这里跟形容词过去式做定语修饰名词不同，形容词过去式做谓语时，并没有现在不再是那个样子的意思。所以上面这句话并没有暗含今天的晚饭很难吃的意思。</p><p>另外，上面的例子中，我们发现主语有的用「は」提示，有的用「が」提示，虽然翻译上都是一样的，但是它们还是有微妙的区别的。</p><p>当用「は」提示时，强调的「は」后面的部分，比如<span lang="ja">「教室は綺麗だ。」</span>强调的是干净，不脏。</p><p>而当用「が」来提示时，强调的是「が」前面的部分，比如<span lang="ja">「部屋が静かです。」</span>强调的是房间，而不是别的地方安静。</p><p>所以，「は」和「が」使用哪个要看你想要表达什么意思，尤其是当使用疑问句式时，疑问词要放在「が」前「は」后。</p><p>当使用疑问词提问谓语部分的形容词所表示的性质时，使用「どう」。例如：</p><blockquote><p><span lang="ja">A：食堂の料理はどうですか。</span>　// 食堂的菜怎么样？<br><span lang="ja">B：美味しいですよ。</span>　　　　　// 很好吃哟。</p></blockquote><p>而如果要提问名词前的形容词定语部分，则使用疑问词「どんな」。例如：</p><blockquote><p><span lang="ja">A：どんな食べ物が好きですか。</span>　// 你喜欢什么样的食物？<br><span lang="ja">B：甘い食べ物が好きです。</span>　　　// 我喜欢甜的食物。</p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;昨天介绍了形容词的分类，基本型和连体形以及形容词的时态。今天我们来讲讲形容词基本用法。&lt;/p&gt;
&lt;h1 id=&quot;修饰体言&quot;&gt;&lt;a href=&quot;#修饰体言&quot; class=&quot;headerlink&quot; title=&quot;修饰体言&quot;&gt;&lt;/a&gt;修饰体言&lt;/h1&gt;&lt;p&gt;首先形容词可以直接用来
      
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>形容词的时态</title>
    <link href="https://coolcode.org/2017/10/02/my-japanese-learning-notes-20/"/>
    <id>https://coolcode.org/2017/10/02/my-japanese-learning-notes-20/</id>
    <published>2017-10-02T15:11:51.000Z</published>
    <updated>2020-04-10T00:37:49.000Z</updated>
    
    <content type="html"><![CDATA[<p>形容词是说明人或事物的性质、状态的一类词。日语中的形容词有活用变化，根据活用类型的不同，可以把它分为Ⅰ类形容词和Ⅱ类形容词。在后面我们用A<sub>Ⅰ</sub>来表示Ⅰ类形容词，用A<sub>Ⅱ</sub>来表示Ⅱ类形容词。</p><p>另外，在学校文法中，“形容词”只相当于上面所讲的“Ⅰ类形容词”，“Ⅱ类形容词”则被称为“形容动词”。</p><h1 id="连体型"><a href="#连体型" class="headerlink" title="连体型"></a>连体型</h1><p>Ⅰ类形容词在修饰名词时，采用「A<sub>Ⅰ</sub>词干い」这种形态，例如：「<span lang="ja">広い（部屋）</span>」，所以Ⅰ类形容词也被称为「イ形容詞」。</p><p>Ⅱ类形容词在修饰名词时，采用「A<sub>Ⅱ</sub>词干な」这种形态，例如：「<span lang="ja">立派な（図書館）</span>」，所以Ⅱ类形容词也被称为「ナ形容詞」。</p><p>上面这种修饰名词（体言）的形式就叫做”连体形“。</p><h1 id="基本型"><a href="#基本型" class="headerlink" title="基本型"></a>基本型</h1><p>Ⅰ类形容词的基本型的词尾是「‐い」，Ⅱ类形容词的基本型的词尾是「‐だ」。</p><p>Ⅱ类形容词通常在词典中只写词干部分，不写词尾部分。</p><h1 id="时态"><a href="#时态" class="headerlink" title="时态"></a>时态</h1><p>形容词同样有四种常见时态，而且每种时态也都有敬体和简体之分，下面的两个表格总结了Ⅰ类形容词和Ⅱ类形容词的时态。</p><p>##Ⅰ类形容词的时态</p><table><thead><tr><th style="text-align:center">时态</th><th style="text-align:center">敬体</th><th style="text-align:center">简体</th></tr></thead><tbody><tr><td style="text-align:center">现在肯定</td><td style="text-align:center"><span lang="ja">A<sub>Ⅰ</sub>词干いです</span></td><td style="text-align:center"><span lang="ja">A<sub>Ⅰ</sub>词干い</span></td></tr><tr><td style="text-align:center">现在否定</td><td style="text-align:center"><span lang="ja">A<sub>Ⅰ</sub>词干くありません</span></td><td style="text-align:center"><span lang="ja">A<sub>Ⅰ</sub>词干くない</span></td></tr><tr><td style="text-align:center">过去肯定</td><td style="text-align:center"><span lang="ja">A<sub>Ⅰ</sub>词干かったです</span></td><td style="text-align:center"><span lang="ja">A<sub>Ⅰ</sub>词干かった</span></td></tr><tr><td style="text-align:center">过去否定</td><td style="text-align:center"><span lang="ja">A<sub>Ⅰ</sub>词干くありませんでした</span></td><td style="text-align:center"><span lang="ja">A<sub>Ⅰ</sub>词干くなかった</span></td></tr></tbody></table><p>Ⅰ类形容词的现在否定敬体形式还可以写作「A<sub>Ⅰ</sub>词干くないです」，过去否定敬体形式还可以写作「A<sub>Ⅰ</sub>词干くなかったです」，但这种形式主要用于口语中。</p><p>几乎所有的Ⅰ类形容词都遵循上面的规则，但是有一个特例，就是「いい」，它只有现在肯定时态符合上面的规则，而其他三种时态，需要先变作「よい」，然后再把「よい」按照上面的规则进行变化。</p><p>##Ⅱ类形容词的时态</p><table><thead><tr><th style="text-align:center">时态</th><th style="text-align:center">敬体</th><th style="text-align:center">简体</th></tr></thead><tbody><tr><td style="text-align:center">现在肯定</td><td style="text-align:center"><span lang="ja">A<sub>Ⅱ</sub>词干です</span></td><td style="text-align:center"><span lang="ja">A<sub>Ⅱ</sub>词干だ</span></td></tr><tr><td style="text-align:center">现在否定</td><td style="text-align:center"><span lang="ja">A<sub>Ⅱ</sub>词干ではありません</span></td><td style="text-align:center"><span lang="ja">A<sub>Ⅱ</sub>词干ではない</span></td></tr><tr><td style="text-align:center">过去肯定</td><td style="text-align:center"><span lang="ja">A<sub>Ⅱ</sub>词干でした</span></td><td style="text-align:center"><span lang="ja">A<sub>Ⅱ</sub>词干だった</span></td></tr><tr><td style="text-align:center">过去否定</td><td style="text-align:center"><span lang="ja">A<sub>Ⅱ</sub>词干ではありませんでした</span></td><td style="text-align:center"><span lang="ja">A<sub>Ⅱ</sub>词干ではなかった</span></td></tr></tbody></table><p>仔细观察我们会发现这个表格跟上一篇中<a href="/2017/10/01/my-japanese-learning-notes-19/">名词的时态</a>的表格几乎一摸一样，只是把「N」换成了「A<sub>Ⅱ</sub>词干」而已。同样Ⅱ类形容词的现在否定和过去否定的几种口语形式也跟名词的一样。所以这里就不重复了，没记住的话请看上一篇。</p><p>但一定要注意，名词没有词干词尾之分，而Ⅱ类形容词的完整形式是包含词干和词尾的，也就是说「‐だ」是Ⅱ类形容词基本型的一部分，上面的时态变化是这个词尾「‐だ」的变化，而名词简体后面的「だ」是附加的判断助动词，这一点还是有区别的。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;形容词是说明人或事物的性质、状态的一类词。日语中的形容词有活用变化，根据活用类型的不同，可以把它分为Ⅰ类形容词和Ⅱ类形容词。在后面我们用A&lt;sub&gt;Ⅰ&lt;/sub&gt;来表示Ⅰ类形容词，用A&lt;sub&gt;Ⅱ&lt;/sub&gt;来表示Ⅱ类形容词。&lt;/p&gt;
&lt;p&gt;另外，在学校文法中，“形容词”只
      
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>名词的时态</title>
    <link href="https://coolcode.org/2017/10/01/my-japanese-learning-notes-19/"/>
    <id>https://coolcode.org/2017/10/01/my-japanese-learning-notes-19/</id>
    <published>2017-10-01T14:23:42.000Z</published>
    <updated>2020-04-10T00:48:49.000Z</updated>
    
    <content type="html"><![CDATA[<p>在日语中，按照词性的分类，可以将词分为两大类：概念词和功能词。而概念词中，又包含两大类：体言和用言。名词就属于体言这一类。</p><p>体言在形态上是固定的，而用言会根据所表示的语法意义和语法功能的不同而发生形态变化，这种形态变化称为“活用”。</p><p>用言在活用时，不发生变化的部分被称为词干，发生变化的部分被称为词尾。</p><p>名词没有词干和词尾之分，但这并不是说名词就没有时态变化，而是说名词在进行时态变化时，需要借助其他的词来帮助其完成。</p><p>比如我们之前所讲的名词谓语句中最后的「です」就是用来表示名词时态的，它被称为判断词，或者叫判断助动词。它属于功能词中的一类，但是从它的名称上我们可以看出，它跟动词有接近的地方，那就是它也会变形。</p><a id="more"></a><p>它有四种形态，分别是：</p><blockquote><p><span lang="ja">Nです</span>　　　　　　　　　是 N<br><span lang="ja">Nではありません</span>　　　　不是 N<br><span lang="ja">Nでした</span>　　　　　　　　是 N（过去时态）<br><span lang="ja">Nではありませんでした</span>　不是 N（过去时态）</p></blockquote><p><strong>现在肯定  <span lang="ja">Nです</span></strong></p><blockquote><p><span lang="ja">学生です。</span>　// 是学生。</p></blockquote><p><strong>现在否定 <span lang="ja">Nではありません</span></strong></p><blockquote><p><span lang="ja">学生ではありません。</span>　// 不是学生。</p></blockquote><p><strong>过去肯定 <span lang="ja">Nでした</span></strong></p><blockquote><p><span lang="ja">学生でした。</span>　// 以前是学生。</p></blockquote><p><strong>过去否定 <span lang="ja">Nではありませんでした</span></strong></p><blockquote><p><span lang="ja">学生ではありませんでした。</span>　// 以前不是学生。</p></blockquote><p>另外，在表达现在否定和过去否定时，还有以下几种口语形式：</p><p><strong>现在否定</strong></p><blockquote><p><span lang="ja">Nじゃありません</span><br><span lang="ja">Nではないです</span><br><span lang="ja">Nじゃないです</span></p></blockquote><p><strong>过去否定</strong></p><blockquote><p><span lang="ja">Nじゃありませんでした</span><br><span lang="ja">Nではなかったです</span><br><span lang="ja">Nじゃなかったです</span></p></blockquote><p>上面这四种形态被称为敬体形态，它们还有对应的简体形态分别是：</p><blockquote><p><span lang="ja">Nだ</span>　　　　　　　　是 N<br><span lang="ja">Nではない</span>　　　　　不是 N<br><span lang="ja">Nだった</span>　　　　　　是 N（过去时态）<br><span lang="ja">Nではなかった</span>　　　不是 N（过去时态）</p></blockquote><p>对于简体，在表达现在否定和过去否定时，同样也有以下口语形式：</p><p><strong>现在否定</strong></p><blockquote><p><span lang="ja">Nじゃない</span></p></blockquote><p><strong>过去否定</strong></p><blockquote><p><span lang="ja">Nじゃなかった</span></p></blockquote><p>下面我们可以列一个表格来进行对比：</p><table><thead><tr><th style="text-align:center">时态</th><th style="text-align:center">敬体</th><th style="text-align:center">简体</th></tr></thead><tbody><tr><td style="text-align:center">现在肯定</td><td style="text-align:center"><span lang="ja">Nです</span></td><td style="text-align:center"><span lang="ja">Nだ</span></td></tr><tr><td style="text-align:center">现在否定</td><td style="text-align:center"><span lang="ja">Nではありません</span></td><td style="text-align:center"><span lang="ja">Nではない</span></td></tr><tr><td style="text-align:center">过去肯定</td><td style="text-align:center"><span lang="ja">Nでした</span></td><td style="text-align:center"><span lang="ja">Nだった</span></td></tr><tr><td style="text-align:center">过去否定</td><td style="text-align:center"><span lang="ja">Nではありませんでした</span></td><td style="text-align:center"><span lang="ja">Nではなかった</span></td></tr></tbody></table><p>最后是刨根问底时间，有人（比如我）可能会问了，为什么「<span lang="ja">Nです</span>」的否定形式是那么长一串的「<span lang="ja">Nではありません</span>」啊，它是怎么变来的呢？</p><p>有人可能会这样回答：“别问那么多为什么啦，学个语言哪有那么多为什么啊，记住就行了。”</p><p>我觉得说这种话的人，八成是自己也没懂，但又不好意思说自己不懂，只好用上面的话来逞强了。</p><p>下面是我搜来的答案：で（判断助动词「だ」的连用形）+は（系助词，表提示，强调）+あり（补助动词「ある」的连用形）+ませ（礼貌助动词「ます」的未然形）+ん（否定助动词「ぬ」的音便）。看懂了吗？看不懂没关系，先记住，继续往后学，后面的学完了，回头再来看，就秒懂了。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在日语中，按照词性的分类，可以将词分为两大类：概念词和功能词。而概念词中，又包含两大类：体言和用言。名词就属于体言这一类。&lt;/p&gt;
&lt;p&gt;体言在形态上是固定的，而用言会根据所表示的语法意义和语法功能的不同而发生形态变化，这种形态变化称为“活用”。&lt;/p&gt;
&lt;p&gt;用言在活用时，不发生变化的部分被称为词干，发生变化的部分被称为词尾。&lt;/p&gt;
&lt;p&gt;名词没有词干和词尾之分，但这并不是说名词就没有时态变化，而是说名词在进行时态变化时，需要借助其他的词来帮助其完成。&lt;/p&gt;
&lt;p&gt;比如我们之前所讲的名词谓语句中最后的「です」就是用来表示名词时态的，它被称为判断词，或者叫判断助动词。它属于功能词中的一类，但是从它的名称上我们可以看出，它跟动词有接近的地方，那就是它也会变形。&lt;/p&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>「こそあど」系列（三）</title>
    <link href="https://coolcode.org/2017/09/30/my-japanese-learning-notes-18/"/>
    <id>https://coolcode.org/2017/09/30/my-japanese-learning-notes-18/</id>
    <published>2017-09-30T06:56:00.000Z</published>
    <updated>2020-04-08T00:09:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>今天重点总结「こそあど」系列中的常用疑问词。</p><a id="more"></a><h1 id="特殊疑问句"><a href="#特殊疑问句" class="headerlink" title="特殊疑问句"></a>特殊疑问句</h1><p>前面讲名词谓语句的疑问句式时，介绍了<strong>一般疑问句</strong>，即不包含疑问词的疑问句。有“一般”自然就会想到“特殊”，今天介绍的带疑问词的疑问句，就叫<strong>特殊疑问句</strong>。</p><blockquote><p><span lang="ja">図書館はどこですか。</span> // 图书馆在哪儿？<br><span lang="ja">どなたが王さんですか。</span> // 哪位是王先生？</p></blockquote><p>上面两句都是特殊疑问句，特殊疑问句在回答时不能使用“是”或“否”来回答。另外还要注意一点，疑问词在后要用「は」，回答也用「は」。疑问词在前要用「が」，回答也用「が」。例如前面两句的回答可以是这样的：</p><blockquote><p><span lang="ja">図書館はあそこです。</span> // 图书馆在那边。<br><span lang="ja">この方が王さんです。</span> // 这位是王先生。</p></blockquote><p>当然，「は」和「が」的区别当然不止这一点，它们之间的区别都可以写一本书了：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2017/09/30/my-japanese-learning-notes-18/batoga.webp" alt="「は」と「が」" title>                </div>                <div class="image-caption">「は」と「が」</div>            </figure><p>但今天要讲的内容不是「は」和「が」，而是疑问词，所以「は」和「が」的讨论就到此为止了。</p><h1 id="疑问词表示选择"><a href="#疑问词表示选择" class="headerlink" title="疑问词表示选择"></a>疑问词表示选择</h1><p>使用「どこ」来问“哪儿”时，通常意味着提问者并不知道要问的地方在哪儿，也没有明确的候选项，虽然有时候可以用「ここ」、「そこ」、「あそこ」来回答，但更多的是使用指路的方式来回答，比如“左拐”，“右转”，“直走”，“转个圈就到了”之类的。</p><p>而使用「どれ」、「どの」则用于表示从不少于三者中选择其一。比如：</p><blockquote><p><span lang="ja">どれが山田さんの本ですか。</span> // 哪本是山田的书？<br><span lang="ja">どの本が山田さんのですか。</span> // 哪本书是山田的？</p></blockquote><p>上面两句话都暗示了这里有两本以上的书可以选择。</p><p>但「どれ」只能指物，而「どの」既可以指物也可以指人。例如：</p><blockquote><p><span lang="ja">どの方が山田さんですか。</span> // 哪位是山田先生？</p></blockquote><p>除了「どの」以外，还有专门用来在多人之间进行选择的疑问词「だれ」「どなた」「どいつ」。其中「どなた」用于表示恭敬，而「どいつ」则有轻蔑的意味。比如：</p><blockquote><p><span lang="ja">だれが山田さんですか。</span> // 谁是山田？<br><span lang="ja">どなたが山田さんですか。</span> // 哪位是山田先生？<br><span lang="ja">どいつが山田だ。</span> // 哪个家伙是山田？</p></blockquote><p>那有什么办法表示从两者之中进行选择吗？当然有啊！这个时候用疑问词「どちら／どっち」就可以了，其中「どっち」更口语化。例如：</p><blockquote><p><span lang="ja">どちらが山田さんの本ですか。</span> // 哪本是山田的书？<br><span lang="ja">どちらの本が山田さんのですか。</span> // 哪本书是山田的？</p></blockquote><p>上面两句话都暗示了这里是在两本书中进行选择。</p><p>「どちら」和「どちらの」都既可以指物也可以指人。例如：</p><blockquote><p><span lang="ja">どちらが山田さんですか。</span> // 哪一位是山田先生？<br><span lang="ja">どちらの方が山田さんですか。</span> // 哪一位是山田先生？</p></blockquote><p>上面两句意思一样，都表示从两人之中进行选择。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;今天重点总结「こそあど」系列中的常用疑问词。&lt;/p&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>「こそあど」系列（二）</title>
    <link href="https://coolcode.org/2017/09/29/my-japanese-learning-notes-17/"/>
    <id>https://coolcode.org/2017/09/29/my-japanese-learning-notes-17/</id>
    <published>2017-09-29T06:56:00.000Z</published>
    <updated>2018-03-29T11:55:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>今天对「こそあど」系列中的常用指示词做一个详解。</p><a id="more"></a><h1 id="「この／その／あの」「これ／それ／あれ」"><a href="#「この／その／あの」「これ／それ／あれ」" class="headerlink" title="「この／その／あの」「これ／それ／あれ」"></a>「この／その／あの」「これ／それ／あれ」</h1><p>「この／その／あの」放在名词前面用于限定名词，被限定的名词可以是人也可以是物。例如：</p><blockquote><p><span lang="ja">この人</span>  // 这个人<br><span lang="ja">その本</span> // 那本书</p></blockquote><p>「これ／それ／あれ」用于指物。如果用于指人是不礼貌的。</p><blockquote><p><span lang="ja">これは本です。<strong>○</strong></span><br><span lang="ja">これは田中さんです。<strong>×</strong></span></p></blockquote><p>例如上面两句，第一句是正确用法，第二句是错误用法。</p><p>如果已知事物的种类时，既可以用「これ／それ／あれ」，也可以用「この／その／あの」。例如：</p><blockquote><p><span lang="ja">これはバナナです。</span> // 这是香蕉。<br><span lang="ja">この果物はバナナです。</span> // 这种水果是香蕉。</p></blockquote><p>但是不知道所指物为何物时，只能用「これ／それ／あれ」。例如：</p><blockquote><p><span lang="ja">これは何ですか。</span> // 这是什么？</p></blockquote><p>指人时可以用<span lang="ja">「この／その／あの＋人」</span>，也可以用<span lang="ja">「この／その／あの＋方」</span>。后者更礼貌一些。例如：</p><blockquote><p><span lang="ja">この人は田中さんです。</span> // 这个人是田中。<br><span lang="ja">この方は田中さんです。</span> // 这位是田中先生。</p></blockquote><p>如果所指的物为复数（相对于单数而言，而不是相对于实数而言的复数），可以用「これら／それら／あれら」，也可以用「これ／それ／あれ」，而且多数情况下用的是「これ／それ／あれ」。例如：</p><blockquote><p><span lang="ja">これは私の本です。</span> // 这（些）是我的书。<br><span lang="ja">これらは私の本です。</span> // 这些是我的书。<br><span lang="ja">これは全部私の本です。</span> // 这些都是我的书。</p></blockquote><p>上面这些都是对的，这一点跟英语（及大部分欧洲语言）是不同的，跟汉语更为接近。在英语中单复数是敏感的，而日语、汉语相对英语来说是不敏感的。</p><p>在使用「この／その／あの」（还有「Nの」）来修饰名词时，如果还有其他修饰名词的成分存在，「この／その／あの」（「Nの」）一定要放在形容词的前面，例如：</p><blockquote><p><span lang="ja">この赤いリンゴ</span>  // 这个红苹果</p></blockquote><h1 id="「ここ／そこ／あそこ」「こちら／そちら／あちら」"><a href="#「ここ／そこ／あそこ」「こちら／そちら／あちら」" class="headerlink" title="「ここ／そこ／あそこ」「こちら／そちら／あちら」"></a>「ここ／そこ／あそこ」「こちら／そちら／あちら」</h1><p>「ここ／そこ／あそこ」用于表示场所，例如：</p><blockquote><p><span lang="ja">ここは図書館です。</span> // 这里是图书馆。</p></blockquote><p>「こちら／そちら／あちら」一般用于表示方向。例如：</p><blockquote><p><span lang="ja">図書館はそちらです。</span> // 图书馆在那边。</p></blockquote><p>「こちら／そちら／あちら」也可以作为「ここ／そこ／あそこ」的礼貌用语的形式使用。</p><blockquote><p><span lang="ja">本はこちらにあります。</span> // 书在这里。<br><span lang="ja">本はここにあります。</span> // 书在这儿。</p></blockquote><p>「こちら／そちら／あちら」还可以用于指人，作用与<span lang="ja">「この／その／あの＋方」</span>相同。都是比较礼貌的说法。</p><p>「こっち／そっち／あっち」是「こちら／そちら／あちら」比较随意的形式，可以指方向，也可以指人。当用于指人时，通常用于亲朋好友之间。</p><p>而「こいつ／そいつ／あいつ」也用于指人，但属于比较粗俗的说法，一般用于男性朋友之间。</p><blockquote><p><span lang="ja">こちらは田中さんです。</span> // 这位是田中先生。（<em>比较正式的场合</em>）<br><span lang="ja">こっちは田中さんです。</span> // 这位是田中先生。（<em>亲密朋友之间</em>）<br><span lang="ja">こいつは田中（だ）。</span> // 这个家伙是田中。（<em>男性朋友之间</em>）</p></blockquote><p>在打电话或书信中，可以用「こちら／こっち」来指代说话人所在地，用「そちら／そっち」来指代听话人所在地。</p><blockquote><p><span lang="ja">（手紙）こちらは皆、元気にしています。そちらはいかがですか。</span>　// （书信）我们这里都很好，您那里怎么样？<br><span lang="ja">（留守番電話）こちらは田中です。ただいま留守にしております。</span>　// （录音电话）我是田中，现在不在家。<br><span lang="ja">（電話）こっちはみんな元気。そっちはどう？</span>　// （电话）我们这里都好。你们那里怎么样？</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;今天对「こそあど」系列中的常用指示词做一个详解。&lt;/p&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>「こそあど」系列（一）</title>
    <link href="https://coolcode.org/2017/09/28/my-japanese-learning-notes-16/"/>
    <id>https://coolcode.org/2017/09/28/my-japanese-learning-notes-16/</id>
    <published>2017-09-28T15:19:00.000Z</published>
    <updated>2018-03-28T02:24:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>在学习英语时，学完了基本的问候语之后，基本上接下来要学的就是：</p><blockquote><p>What’s this? （这是什么？）<br>It’s a book. （这是一本书。）<br>What’s that? （那是什么？）<br>It’s an apple. （那是一个苹果。）</p></blockquote><p>之类的句子了。</p><p>这些句子里面，像 <code>this</code>， <code>that</code> 在这里被称为指示代词，当然英语里的指示代词不止这两个，而是有一个系列，不过这里就不详细列举了，毕竟今天要讲的不是英语。今天要讲的是日语的「こそあど」系列，其中的「こ・そ・あ」就跟英语中的这部分是对应的，而「ど」对应的就是英语中 <code>what</code> 这类的疑问词，因为日语中与「こ・そ・あ」对应的同样形式的疑问词是以「ど」开头的，所以把它们统称为「こそあど」系列，但并不是说所有的疑问词都是以「ど」开头的，也不是所有的疑问词都有相应的指示词。</p><a id="more"></a><p>日语中的「こ・そ・あ」系列被统称为【指示词】，【指示代词】只是它其中的一部分。而且你会发现，日语跟英语啊，汉语啊不一样。英语、汉语中除了【这】就是【那】，而日语中，除了「こ」，不止有个「そ」，还有一个「あ」。</p><p>英语中的 <code>this</code> 除了代词，还有形容词，副词的词性。而日语中的「こそあど」系列根据词性的不同，对应的词也不同，甚至在指代不同的事物和人时，也用不同的词进行区分。下面先列出一个常见的「こそあど」系列的表格。</p><table><thead><tr><th style="text-align:center">词性　</th><th style="text-align:center">作用</th><th style="text-align:center">こ系列</th><th style="text-align:center">そ系列</th><th style="text-align:center">あ系列</th><th style="text-align:center">疑问词</th></tr></thead><tbody><tr><td style="text-align:center">连体词</td><td style="text-align:center">修饰名词</td><td style="text-align:center">この</td><td style="text-align:center">その</td><td style="text-align:center">あの</td><td style="text-align:center">どの</td></tr><tr><td style="text-align:center">连体词</td><td style="text-align:center">表示属性</td><td style="text-align:center">こんな</td><td style="text-align:center">そんな</td><td style="text-align:center">あんな</td><td style="text-align:center">どんな</td></tr><tr><td style="text-align:center">代词</td><td style="text-align:center">指代事物</td><td style="text-align:center">これ（ら）</td><td style="text-align:center">それ（ら）</td><td style="text-align:center">あれ（ら）</td><td style="text-align:center">どれ、なに（なん）</td></tr><tr><td style="text-align:center">代词</td><td style="text-align:center">指代人</td><td style="text-align:center">こいつ</td><td style="text-align:center">そいつ</td><td style="text-align:center">あいつ</td><td style="text-align:center">どいつ、だれ、どなた</td></tr><tr><td style="text-align:center">代词</td><td style="text-align:center">指代场所</td><td style="text-align:center">ここ</td><td style="text-align:center">そこ</td><td style="text-align:center">あそこ</td><td style="text-align:center">どこ</td></tr><tr><td style="text-align:center">代词</td><td style="text-align:center">指代方向</td><td style="text-align:center">こちら</td><td style="text-align:center">そちら</td><td style="text-align:center">あちら</td><td style="text-align:center">どちら</td></tr><tr><td style="text-align:center">代词</td><td style="text-align:center">指代方向</td><td style="text-align:center">こっち</td><td style="text-align:center">そっち</td><td style="text-align:center">あっち</td><td style="text-align:center">どっち</td></tr><tr><td style="text-align:center">副词</td><td style="text-align:center">表示程度</td><td style="text-align:center">こう</td><td style="text-align:center">そう</td><td style="text-align:center">ああ</td><td style="text-align:center">どう</td></tr></tbody></table><p>今天先不讲上面这些词的区别。今天只讲一下「こ・そ・あ」指示词的共同点。</p><p>指示词有两种用法，一种是指所指物在说话现场，即【现场指示】。另一种是所指物在谈话和文章中出现，即【语境指示】。</p><h1 id="现场指示"><a href="#现场指示" class="headerlink" title="现场指示"></a>现场指示</h1><p>现场指示的指示词有两种用法，分别是【对立型】和【融合型】。</p><h2 id="对立型"><a href="#对立型" class="headerlink" title="对立型"></a>对立型</h2><p>在现场说话人和听话人不在同一位置时，可以根据对立型的原理区别使用指示词。原理如下：</p><p><strong>在说话人身边用「こ」系列，在听话人身边用「そ」系列，其他用「あ」系列来指示。</strong></p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2017/09/28/my-japanese-learning-notes-16/kosoado-1.png" alt="对立型" title>                </div>                <div class="image-caption">对立型</div>            </figure><p>在对立型原理中，说话人和听话人对同一事物所用的指示词是不同的。假设有 A、B 两人对话，A 用「こ」时 B 用「そ」，A 用「そ」时 B 用「こ」，A 用「あ」时 B 也用「あ」。这里「こ、そ、あ」的关系类似于“我、你、他”的关系。例如：</p><blockquote><p>A：それは本ですか。（那是书吗？）<br>B：はい、これは本です。（是，这是书。）</p></blockquote><p>一般离说话人近的事物用「こ」表示，但是像说话人自己手触及不到的后背等地方，可以用「そ」来表示。</p><h2 id="融合型"><a href="#融合型" class="headerlink" title="融合型"></a>融合型</h2><p>在现场如果说话人和听话人在同一位置或没有听话人的时候，可以根据融合型的原理区别使用指示词。原理如下：</p><p><strong>近处的事物用「こ」系列，远处的事物用「あ」系列，不远不近的事物用「そ」系列。</strong></p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2017/09/28/my-japanese-learning-notes-16/kosoado-2.png" alt="融合型" title>                </div>                <div class="image-caption">融合型</div>            </figure><p>融合型的「そ」可以用于这样的场合，比如坐在出租车里，对出租车司机说：</p><blockquote><p><span lang="jp">そこで止めてください。</span>（请在那里停一下。）</p></blockquote><p>其它场合一般不使用「そ」。</p><h1 id="语境指示"><a href="#语境指示" class="headerlink" title="语境指示"></a>语境指示</h1><p>在语境指示中还可以细分为对话中的语境指示和文章中的语境指示两种。</p><h1 id="对话中的语境指示"><a href="#对话中的语境指示" class="headerlink" title="对话中的语境指示"></a>对话中的语境指示</h1><p>在对话中，对于双方都知道的人或事物，用「あ」系列，否则用「そ」系列。</p><p>说详细一点就是，A 和 B 对话中，A 知道这个人或物，A 也确信 B 也知道这个人或物时，用「あ」。例如：</p><blockquote><p><span lang="ja">王：高木さんは経済学部の研修生ですか。</span><br><span lang="ja">高橋：ええ、そうですよ。<strong>あの</strong>人は中国語がとても上手ですよ。</span></p></blockquote><blockquote><p>王：高木是经济系的研修生吧。<br>高桥：嗯，是的呢。那哥们中文很牛逼哟。</p></blockquote><p>如果 A 知道这个人或物，但 A 知道 B 不知道的时候，用「そ」。或者 A 知道，但 A 不知道 B 是否知道的时候，也用「そ」。或者 A 不知道，但 A 知道 B 知道的时候，还用「そ」。又或者 A 不知道，A 也不知道 B 知不知道的时候，仍用 「そ」。例如：</p><blockquote><p><span lang="ja">王：相手の人は今でもいい友達です。</span><br><span lang="ja">高橋：その人は今、日本ですか。</span></p></blockquote><blockquote><p>王：作为伙伴的他现在依然是好朋友。<br>高桥：那哥们现在在日本吗？</p></blockquote><p>对于人来说，所谓的知道是指见过，而不仅仅是听说过，前一段对话中的高木应该是王和高桥都见过的，而这一段中王提到的「相手の人」高桥是没见过的，所以高桥用的是「そ」来代指那哥们。</p><p>对话中的语境指示很少用「こ」，但是如果是说话人知道的人或事，但是听话人不知道，说话人也不在乎听话人知不知道的情况下，除了可以用「そ」以外，也可以用「こ」。</p><p>例如：</p><blockquote><p><span lang="ja">Ａ：友人に田中という男がいるんですが、こいつは面白い男なんですよ。</span></p></blockquote><blockquote><p>A：我有个叫田中的朋友，这家伙可有意思啦。</p></blockquote><h1 id="文章中的语境指示"><a href="#文章中的语境指示" class="headerlink" title="文章中的语境指示"></a>文章中的语境指示</h1><p>在文章中，语境指示不能用「あ」，通常用「そ」，有些情况下可以用「こ」，少数情况下必须用「こ」。但是什么情况下用「こ」，以及「こ」和「そ」有哪些区别，因为我还没有学到，而且好像还挺复杂的，一时半会儿我也搞不清楚，就不举例说明了。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在学习英语时，学完了基本的问候语之后，基本上接下来要学的就是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What’s this? （这是什么？）&lt;br&gt;It’s a book. （这是一本书。）&lt;br&gt;What’s that? （那是什么？）&lt;br&gt;It’s an apple. （那是一个苹果。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;之类的句子了。&lt;/p&gt;
&lt;p&gt;这些句子里面，像 &lt;code&gt;this&lt;/code&gt;， &lt;code&gt;that&lt;/code&gt; 在这里被称为指示代词，当然英语里的指示代词不止这两个，而是有一个系列，不过这里就不详细列举了，毕竟今天要讲的不是英语。今天要讲的是日语的「こそあど」系列，其中的「こ・そ・あ」就跟英语中的这部分是对应的，而「ど」对应的就是英语中 &lt;code&gt;what&lt;/code&gt; 这类的疑问词，因为日语中与「こ・そ・あ」对应的同样形式的疑问词是以「ど」开头的，所以把它们统称为「こそあど」系列，但并不是说所有的疑问词都是以「ど」开头的，也不是所有的疑问词都有相应的指示词。&lt;/p&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>并列助词「と、や、か」</title>
    <link href="https://coolcode.org/2017/09/26/my-japanese-learning-notes-15/"/>
    <id>https://coolcode.org/2017/09/26/my-japanese-learning-notes-15/</id>
    <published>2017-09-26T16:47:00.000Z</published>
    <updated>2018-03-27T08:07:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>在连接名词的助词中，把两个或两个以上的名词并列起来的并列助词有「と、や、か」等。我们先来举例看看这三个并列助词的作用和区别。</p><blockquote><p><span lang="ja">1. 朝ご飯はパン<strong>と</strong>ピザ(<strong>と</strong>)を食べた。</span><br><span lang="ja">2. 朝ご飯はパン<strong>や</strong>ピザ(<strong>など</strong>)を食べた。</span><br><span lang="ja">3. 彼は朝ご飯パン<strong>か</strong>ピザ(<strong>か</strong>)を食べたはずだ。</span></p></blockquote><p>这三个句子中的意思分别是：</p><blockquote><ol><li>早饭吃的面包和披萨。</li><li>早饭吃的面包和披萨什么的。</li><li>他早饭吃的应该是面包或披萨。</li></ol></blockquote><a id="more"></a><h1 id="区别一"><a href="#区别一" class="headerlink" title="区别一"></a>区别一</h1><p>当用「と」时，表示完全列举，所以第 1 句话表示，早饭除了「パン」和「ピザ」，就没吃别的了。</p><p>当用「や」时，表示不完全列举，所以第 2 句话表示，早饭除了「パン」和「ピザ」，还可能有别的。</p><p>当用「か」时，表示从列举的范围内进行选择。所以第 3 句话表示，他早饭吃了「パン」和「ピザ」之中的一种。</p><h1 id="区别二"><a href="#区别二" class="headerlink" title="区别二"></a>区别二</h1><p>「と」和「か」也可以接在并列列举的最后一个名词之后，但在口语中一般予以省略。「や」不会接在并列列举的最后一个名词之后，但可以使用「など」接在使用「や」列举的最后一个名词之后来更加明确地表示在所列举的要素之外还有其他同类的事物存在，加上「など」并不改变句子的基本意思。「など」不能跟「と」和「か」这两个明确表示列举所有要素的并列助词一起使用。</p><p>例如下面这个题目：</p><blockquote><p><span lang="ja">大学一年生のとき、中国語___日本語などを勉強しました。</span><br>A　と　　　　　　B　や　　　　　　C　か</p></blockquote><p>只能选 B。</p><h1 id="区别三"><a href="#区别三" class="headerlink" title="区别三"></a>区别三</h1><p>「と」和「や」不能连接名词以外的词或句子。</p><p>当连接两个动词、形容词、形容动词或名词谓语句时，要表示完全列举应使用它们相应的中顿型来代替「と」。要表示不完全列举应使用「たり」型来代替「や」。</p><p>而「か」可以连接名词以外的词或句子。</p><p>因为这些语法点在后面会学到，所以暂时不在这里举例。</p><h1 id="共同点"><a href="#共同点" class="headerlink" title="共同点"></a>共同点</h1><p>最后，「と、や、か」也可以接在带有格助词的名词短语后面。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在连接名词的助词中，把两个或两个以上的名词并列起来的并列助词有「と、や、か」等。我们先来举例看看这三个并列助词的作用和区别。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;span lang=&quot;ja&quot;&gt;1. 朝ご飯はパン&lt;strong&gt;と&lt;/strong&gt;ピザ(&lt;strong&gt;と&lt;/strong&gt;)を食べた。&lt;/span&gt;&lt;br&gt;&lt;span lang=&quot;ja&quot;&gt;2. 朝ご飯はパン&lt;strong&gt;や&lt;/strong&gt;ピザ(&lt;strong&gt;など&lt;/strong&gt;)を食べた。&lt;/span&gt;&lt;br&gt;&lt;span lang=&quot;ja&quot;&gt;3. 彼は朝ご飯パン&lt;strong&gt;か&lt;/strong&gt;ピザ(&lt;strong&gt;か&lt;/strong&gt;)を食べたはずだ。&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这三个句子中的意思分别是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;早饭吃的面包和披萨。&lt;/li&gt;
&lt;li&gt;早饭吃的面包和披萨什么的。&lt;/li&gt;
&lt;li&gt;他早饭吃的应该是面包或披萨。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>日语的亲属称谓</title>
    <link href="https://coolcode.org/2017/09/26/my-japanese-learning-notes-14/"/>
    <id>https://coolcode.org/2017/09/26/my-japanese-learning-notes-14/</id>
    <published>2017-09-26T07:44:00.000Z</published>
    <updated>2018-03-27T07:42:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>日语中家庭成员的称呼比汉语要简单一些，但是对他人讲自己的家庭成员和称呼对方的家庭成员时有所不同。一般称呼对方的家庭成员要用敬称。</p><a id="more"></a><h1 id="自己的家庭成员"><a href="#自己的家庭成员" class="headerlink" title="自己的家庭成员"></a>自己的家庭成员</h1><table><thead><tr><th style="text-align:center">家庭成员</th><th style="text-align:center">称谓</th></tr></thead><tbody><tr><td style="text-align:center">爷爷，姥爷</td><td style="text-align:center"><ruby lang="ja">祖父<rp> (</rp><rt>そふ</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">奶奶，姥姥</td><td style="text-align:center"><ruby lang="ja">祖母<rp> (</rp><rt>そぼ</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">爸爸</td><td style="text-align:center"><ruby lang="ja">父<rp> (</rp><rt>ちち</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">妈妈</td><td style="text-align:center"><ruby lang="ja">母<rp> (</rp><rt>はは</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">哥哥</td><td style="text-align:center"><ruby lang="ja">兄<rp> (</rp><rt>あに</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">姐姐</td><td style="text-align:center"><ruby lang="ja">姉<rp> (</rp><rt>あね</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">弟弟</td><td style="text-align:center"><ruby lang="ja">弟<rp> (</rp><rt>おとうと</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">妹妹</td><td style="text-align:center"><ruby lang="ja">妹<rp> (</rp><rt>いもうと</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">丈夫</td><td style="text-align:center"><ruby lang="ja">主人<rp> (</rp><rt>しゅじん</rt><rp>) </rp></ruby>／<ruby lang="ja">夫<rp> (</rp><rt>おっと</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">妻子</td><td style="text-align:center"><ruby lang="ja">家内<rp> (</rp><rt>かない</rt><rp>) </rp></ruby>／<ruby lang="ja">妻<rp> (</rp><rt>つま</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">儿子</td><td style="text-align:center"><ruby lang="ja">息子<rp> (</rp><rt>むすこ</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">女儿</td><td style="text-align:center"><ruby lang="ja">娘<rp> (</rp><rt>むすめ</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">兄弟</td><td style="text-align:center"><ruby lang="ja">兄弟<rp> (</rp><rt>きょうだい</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">叔叔，舅舅</td><td style="text-align:center"><ruby lang="ja">叔父<rp> (</rp><rt>おじ</rt><rp>) </rp></ruby>／<ruby lang="ja">伯父<rp> (</rp><rt>おじ</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">姑姑，姨</td><td style="text-align:center"><ruby lang="ja">叔母<rp> (</rp><rt>おば</rt><rp>) </rp></ruby>／<ruby lang="ja">伯母<rp> (</rp><rt>おば</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">侄子，外甥</td><td style="text-align:center"><ruby lang="ja">甥<rp> (</rp><rt>おい</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">侄女，外甥女</td><td style="text-align:center"><ruby lang="ja">姪<rp> (</rp><rt>めい</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">堂（表）兄弟（姐妹）</td><td style="text-align:center"><ruby lang="ja">従兄弟<rp> (</rp><rt>いとこ</rt><rp>) </rp></ruby>／<ruby lang="ja">従姉妹<rp> (</rp><rt>いとこ</rt><rp>) </rp></ruby></td></tr></tbody></table><h1 id="他人的家庭成员"><a href="#他人的家庭成员" class="headerlink" title="他人的家庭成员"></a>他人的家庭成员</h1><table><thead><tr><th style="text-align:center">家庭成员</th><th style="text-align:center">称谓</th></tr></thead><tbody><tr><td style="text-align:center">爷爷，姥爷</td><td style="text-align:center"><ruby lang="ja">お祖父さん<rp> (</rp><rt>おじいさん</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">奶奶，姥姥</td><td style="text-align:center"><ruby lang="ja">お祖母さん<rp> (</rp><rt>おばあさん</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">爸爸</td><td style="text-align:center"><ruby lang="ja">お父さん<rp> (</rp><rt>おとうさん</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">妈妈</td><td style="text-align:center"><ruby lang="ja">お母さん<rp> (</rp><rt>おかあさん</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">哥哥</td><td style="text-align:center"><ruby lang="ja">お兄さん<rp> (</rp><rt>おにいさん</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">姐姐</td><td style="text-align:center"><ruby lang="ja">お姉さん<rp> (</rp><rt>おねえさん</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">弟弟</td><td style="text-align:center"><ruby lang="ja">弟さん<rp> (</rp><rt>おとうとさん</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">妹妹</td><td style="text-align:center"><ruby lang="ja">妹さん<rp> (</rp><rt>いもうとさん</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">丈夫</td><td style="text-align:center"><ruby lang="ja">ご主人<rp> (</rp><rt>ごしゅじん</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">妻子</td><td style="text-align:center"><ruby lang="ja">奥さん<rp> (</rp><rt>おくさん</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">儿子</td><td style="text-align:center"><ruby lang="ja">息子さん<rp> (</rp><rt>むすこさん</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">女儿</td><td style="text-align:center"><ruby lang="ja">娘さん<rp> (</rp><rt>むすめさん</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">兄弟</td><td style="text-align:center"><ruby lang="ja">ご兄弟<rp> (</rp><rt>ごきょうだい</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">叔叔，舅舅</td><td style="text-align:center"><ruby lang="ja">叔父さん<rp> (</rp><rt>おじさん</rt><rp>) </rp></ruby>／<ruby lang="ja">伯父さん<rp> (</rp><rt>おじさん</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">姑姑，姨</td><td style="text-align:center"><ruby lang="ja">叔母さん<rp> (</rp><rt>おばさん</rt><rp>) </rp></ruby>／<ruby lang="ja">伯母さん<rp> (</rp><rt>おばさん</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">侄子，外甥</td><td style="text-align:center"><ruby lang="ja">甥御さん<rp> (</rp><rt>おいごさん</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">侄女，外甥女</td><td style="text-align:center"><ruby lang="ja">姪御さん<rp> (</rp><rt>めいごさん</rt><rp>) </rp></ruby></td></tr><tr><td style="text-align:center">堂（表）兄弟（姐妹）</td><td style="text-align:center"><ruby lang="ja">従兄弟さん<rp> (</rp><rt>いとこさん</rt><rp>) </rp></ruby>／<ruby lang="ja">従姉妹さん<rp> (</rp><rt>いとこさん</rt><rp>) </rp></ruby></td></tr></tbody></table><p>不过上面的称谓是相对而言的。比如你对别人介绍自己家人的时候，一般使用的是“自己的家庭成员”这个表里的称谓，但是你对自己家人直接对话时，称呼对方一般用“他人的家庭成员”这个表里的称谓。</p><p>当然这也不是绝对的。</p><p>比如夫妻之间的相互称谓，父母对子女的称谓，肯定不会使用“他人的家庭成员”表中的<span lang="ja">「ご主人」、「奥さん」、「息子さん」、「娘さん」</span>这样的称呼。</p><p>如果是新婚夫妇，还没生孩子，老公叫老婆一般还是叫名字，或者在名字后面加上<span lang="ja">「さん／ちゃん」</span>，因为突然改口不习惯嘛。粗俗一点的就直接<span lang="ja">「おい」</span>（这里可不是“侄子，外甥”的意思，而是“喂”的意思）了，不过这样的话，老婆可能会比较介意，认为不再有爱了(T_T)。</p><p>老婆叫老公除了叫名字以外，还可以称呼老公为<span lang="ja">「あなた」</span>，这可是比达令更肉麻的称呼哦。</p><p>生了孩子之后，就<span lang="ja">「パパ」、「ママ」</span>的互称了。等孩子长的了，孩子都不叫你<span lang="ja">「パパ」、「ママ」</span>了，那夫妻之间也就都换<span lang="ja">「お父さん」、「お母さん」</span>这样的互称了。</p><p>当然，也有还没生孩子就互称对方为<span lang="ja">「パパ」、「ママ」、「お父さん」、「お母さん」</span>的夫妻，可见上面说的这些都不是绝对的。</p><p>父母称呼子女一般就直呼其名了。</p><p>丈夫对外介绍自己的妻子时，也不是说不能用<span lang="ja">「奥さん」</span>，事实上，在日本，妻子反而更希望丈夫在对别人介绍自己时，使用<span lang="ja">「奥さん」</span>而不是<span lang="ja">「家内」「妻」</span>这种称谓，因为妻子觉得丈夫在别人面前称呼自己为<span lang="ja">「奥さん」</span>是对自己的尊重，当然也就感觉自己倍儿有面子咯。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;日语中家庭成员的称呼比汉语要简单一些，但是对他人讲自己的家庭成员和称呼对方的家庭成员时有所不同。一般称呼对方的家庭成员要用敬称。&lt;/p&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>日语的数字</title>
    <link href="https://coolcode.org/2017/09/25/my-japanese-learning-notes-13/"/>
    <id>https://coolcode.org/2017/09/25/my-japanese-learning-notes-13/</id>
    <published>2017-09-25T06:06:00.000Z</published>
    <updated>2018-03-27T05:59:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>日语的数字比汉语要复杂一些。因为在数不同的东西的时候，同一个数字念法上却不同。这算是学习日语过程中遇到的第一个难点。很多学习日语的人在好不容易过了五十音一关之后，在这里被卡住了。</p><a id="more"></a><h1 id="基数词"><a href="#基数词" class="headerlink" title="基数词"></a>基数词</h1><blockquote><p>０（ゼロ、れい）<br>１（いち）<br>２（に）<br>３（さん）<br>４（し、よん、よ）<br>５（ご）<br>６（ろく）<br>７（しち、なな）<br>８（はち）<br>９（く、きゅう）<br>１０（じゅう）<br>１１（じゅういち）<br>１２（じゅうに）<br>１３（じゅうさん）<br>１４（じゅうし、じゅうよん、じゅうよ）<br>１５（じゅうご）<br>１６（じゅうろく）<br>１７（じゅうしち、じゅうなな）<br>１８（じゅうはち）<br>１９（じゅうく、じゅうきゅう）<br>２０（にじゅう）<br>３０（さんじゅう）<br>４０（よんじゅう）<br>５０（ごじゅう）<br>６０（ろくじゅう）<br>７０（ななじゅう、しちじゅう）<br>８０（はちじゅう）<br>９０（きゅうじゅう）<br>１００（ひゃく）<br>２００（にひゃく）<br>３００（さんびゃく）<br>４００（よんひゃく）<br>５００（ごひゃく）<br>６００（ろっぴゃく）<br>７００（ななひゃく）<br>８００（はっぴゃく）<br>９００（きゅうひゃく）<br>１０００（せん）<br>２０００（にせん）<br>３０００（さんぜん）<br>４０００（よんせん）<br>５０００（ごせん）<br>６０００（ろくせん）<br>７０００（ななせん）<br>８０００（はっせん）<br>９０００（きゅうせん）<br>１００００（いちまん）<br>１０８（ひゃくはち）<br>１８３００（いちまんはっせんさんびゃく）<br>一億（いちおく）<br>…</p></blockquote><p>日语里面基数词的表达方式跟汉语类似，甚至更简单。只不过有些数字有多个读音。</p><p>100、1000 直接读「<ruby lang="ja">百<rp> (</rp><rt>ひゃく</rt><rp>) </rp></ruby>」，「<ruby lang="ja">千<rp> (</rp><rt>せん</rt><rp>) </rp></ruby>」，前面不加「<ruby lang="ja">一<rp> (</rp><rt>いち</rt><rp>) </rp></ruby>」，10000，100000000 则需要读作「<ruby lang="ja">一万<rp> (</rp><rt>いちまん</rt><rp>) </rp></ruby>」、「<ruby lang="ja">一億<rp> (</rp><rt>いちおく</rt><rp>) </rp></ruby>」。</p><p>300，3000 后面的<span lang="ja">「ひゃく、せん」</span>会变为<span lang="ja">「びゃく、ぜん」</span>。600，800 会有促音便，同时<span lang="ja">「ひゃく」</span>会变为<span lang="ja">「ぴゃく」</span>。8000 也会有促音便。</p><p>还有一点，如果数字中有 0，汉语中会读出 0，但日语中直接跳过，比如上面列表中后面的 108。</p><h1 id="序数词"><a href="#序数词" class="headerlink" title="序数词"></a>序数词</h1><p>英语里面基数词和序数词完全是不同的两套数字，比如 1 的基数词是 one，序数词是 first。而日语跟英语不同，跟汉语更像，日语里面的序数词只需要在前面加上「<ruby lang="ja">第<rp> (</rp><rt>だい</rt><rp>) </rp></ruby>」就可以了。比如：</p><blockquote><p><ruby lang="ja">第一<rp> (</rp><rt>だいいち</rt><rp>) </rp></ruby><br><ruby lang="ja">第二<rp> (</rp><rt>だいに</rt><rp>) </rp></ruby><br><ruby lang="ja">第三<rp> (</rp><rt>だいさん</rt><rp>) </rp></ruby><br><ruby lang="ja">第四<rp> (</rp><rt>だいよん</rt><rp>) </rp></ruby><br>…</p></blockquote><p>但是这并不是唯一的方式，日语中还有后接量词的序数词，比如：</p><blockquote><p><ruby lang="ja">一番<rp> (</rp><rt>いちばん</rt><rp>) </rp></ruby><br><ruby lang="ja">二番<rp> (</rp><rt>にばん</rt><rp>) </rp></ruby><br><ruby lang="ja">三番<rp> (</rp><rt>さんばん</rt><rp>) </rp></ruby><br><ruby lang="ja">四番<rp> (</rp><rt>よんばん</rt><rp>) </rp></ruby><br>…</p></blockquote><blockquote><p><ruby lang="ja">一番目<rp> (</rp><rt>いちばんめ</rt><rp>) </rp></ruby><br><ruby lang="ja">二番目<rp> (</rp><rt>にばんめ</rt><rp>) </rp></ruby><br><ruby lang="ja">三番目<rp> (</rp><rt>さんばんめ</rt><rp>) </rp></ruby><br><ruby lang="ja">四番目<rp> (</rp><rt>よんばんめ</rt><rp>) </rp></ruby><br>…</p></blockquote><p>有些人会把下面这些用来表示个数的数量词当成是序数词，一定要注意区分。下面这些<strong>不是序数词，不是序数词，不是序数词</strong>。</p><h1 id="个数和年龄"><a href="#个数和年龄" class="headerlink" title="个数和年龄"></a>个数和年龄</h1><blockquote><ul><li><ruby lang="ja">一つ<rp> (</rp><rt>ひとつ</rt><rp>) </rp></ruby></li><li><ruby lang="ja">二つ<rp> (</rp><rt>ふたつ</rt><rp>) </rp></ruby></li><li><ruby lang="ja">三つ<rp> (</rp><rt>みっつ</rt><rp>) </rp></ruby></li><li><ruby lang="ja">四つ<rp> (</rp><rt>よっつ</rt><rp>) </rp></ruby></li><li><ruby lang="ja">五つ<rp> (</rp><rt>いつつ</rt><rp>) </rp></ruby></li><li><ruby lang="ja">六つ<rp> (</rp><rt>むっつ</rt><rp>) </rp></ruby></li><li><ruby lang="ja">七つ<rp> (</rp><rt>ななつ</rt><rp>) </rp></ruby></li><li><ruby lang="ja">八つ<rp> (</rp><rt>やっつ</rt><rp>) </rp></ruby></li><li><ruby lang="ja">九つ<rp> (</rp><rt>ここのつ</rt><rp>) </rp></ruby></li><li><ruby lang="ja">十<rp> (</rp><rt>とお</rt><rp>) </rp></ruby></li></ul></blockquote><p>上面这些词都是训读，它们除了可以表示个数以外，还可以表示小孩的年龄。比如：</p><blockquote><p><span lang="ja">A：いくつですか。</span>（你几岁啊？）<br><span lang="ja">B：五つです。</span>（5岁）。</p></blockquote><p>不过用上面的方式只能数到 10 个/岁，超过 10 怎么办呢？超过 10 的时候，要表示个数可以用前面讲的基数词加「<span lang="ja">個（こ、か）</span>」来表示，要表示年龄可以用基数词加「<ruby lang="ja">歳<rp> (</rp><rt>さい</rt><rp>) </rp></ruby>」。比如：</p><blockquote><ruby lang="ja">十二個<rp> (</rp><rt>じゅうにこ</rt><rp>) </rp></ruby><ruby lang="ja">十二歳<rp> (</rp><rt>じゅうにさい</rt><rp>) </rp></ruby></blockquote><p>但是我们也发现了，<span lang="ja">「個」</span>有两个读音，在数不同的东西时，发音不一样。比如要表示“十二个月”，可以写作<span lang="ja">「十二個月」</span>，读作<span lang="ja">「じゅうにかげつ」</span>。表示“三个苹果”，可以写作<span lang="ja">「リンゴ三個」</span>，读作<span lang="ja">「りんごさんこ」</span>。因为汉字<span lang="ja">「個」</span>比较难写，还不容易辨别读音，所以平时我们看到的更多的是它读音对应的假名，而不是汉字<span lang="ja">「個」</span>。</p><p>另外，即使在 10 个数字之内的时候，也不一定就要用上面那些训读方式的个数/岁数，比如要表示“五个月”这样的时间段，也是用<span lang="ja">「五か月」</span>来表示的。至于什么时候该用什么，就只能遇到的时候去记住了。</p><p><span lang="ja">「個」、「歳」</span>在跟基数词构成数量词后，在读音上要注意，有一些特殊的数字会发生音便，比如 1、8、10 这几个。</p><p>另外，还有一个年龄“20 岁”，比较特殊。它有音读和训读两种表示。音读为<span lang="ja">「にじゅっさい／にじっさい」</span>，训读为<span lang="ja">「はたち」</span>。一般当写作<span lang="ja">「２０歳」</span>时音读，写作<span lang="ja">「二十歳」</span>时训读。当然你要记不住训读方式，可以都用音读。比如在新闻中都使用<span lang="ja">「にじっさい」</span>这种音读方式，而不会读作<span lang="ja">「はたち」</span>这种训读方式。</p><p><span lang="ja">「はたち」</span>在古文中，不仅限于年龄，而是泛指的 20 这样的数量词。<span lang="ja">「はた」</span>是 20，<span lang="ja">「ち」</span>有点儿相当于现代日语中的<span lang="ja">「一つ、二つ」</span>中的<span lang="ja">「つ」</span>。还有「<ruby lang="ja">二十年<rp> (</rp><rt>はたとせ</rt><rp>) </rp></ruby>」，「<ruby lang="ja">二十冊<rp> (</rp><rt>はたまき</rt><rp>) </rp></ruby>」等等，数词<span lang="ja">「はた」</span>与其他量词搭配出现的情况。但现在这些词都不用这种训读方式了，而用更好记的音读方式了。</p><p>询问个数的疑问词有两个，分别是<span lang="ja">「いくつ」</span>和「<ruby lang="ja">何個<rp> (</rp><rt>なんこ</rt><rp>) </rp></ruby>」，询问年龄的疑问词也有两个：<span lang="ja">「いくつ」</span>和「<ruby lang="ja">何歳<rp> (</rp><rt>なんさい</rt><rp>) </rp></ruby>」。</p><p>所以，<span lang="ja">「いくつ」</span>既可以问年龄，也可以问个数。所以看到<span lang="ja">「いくつ」</span>时要注意上下文，比如上面那个例子：</p><blockquote><p><span lang="ja">A：いくつですか。</span><br><span lang="ja">B：五つです。</span></p></blockquote><p>其实它还可能有：</p><blockquote><p>A：几个啊？<br>B：五个。</p></blockquote><p>这样的意思。</p><h1 id="人数"><a href="#人数" class="headerlink" title="人数"></a>人数</h1><p>数人的时候，不能用几个几个的数，要用几人几人的数。所以在日语中通常是在基数词的后面加上「<ruby lang="ja">人<rp> (</rp><rt>にん</rt><rp>) </rp></ruby>」来表示，但是有两个特殊的它们分别是「<ruby lang="ja">一人<rp> (</rp><rt>ひとり</rt><rp>) </rp></ruby>」和「<ruby lang="ja">二人<rp> (</rp><rt>ふたり</rt><rp>) </rp></ruby>」，虽然它们的汉字写法跟其它的人数的表示形式相同，但是在读法上，这两个词用训读，而不是音读。</p><p>询问人数的疑问词是：「<ruby lang="ja">何人<rp> (</rp><rt>なんにん</rt><rp>) </rp></ruby>」。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;日语的数字比汉语要复杂一些。因为在数不同的东西的时候，同一个数字念法上却不同。这算是学习日语过程中遇到的第一个难点。很多学习日语的人在好不容易过了五十音一关之后，在这里被卡住了。&lt;/p&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>常用的基本问候寒暄语（一）</title>
    <link href="https://coolcode.org/2017/09/23/my-japanese-learning-notes-12/"/>
    <id>https://coolcode.org/2017/09/23/my-japanese-learning-notes-12/</id>
    <published>2017-09-23T18:25:00.000Z</published>
    <updated>2018-03-26T14:15:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>今天讲几个问候寒暄语：</p><ul><li>はじめまして</li><li>どうぞよろしく</li><li>すみません</li><li>きのうはどうもすみませんでした</li><li>こちらこそ</li><li>おはよう</li><li>じゃあ、また</li></ul><a id="more"></a><h1 id="はじめまして"><a href="#はじめまして" class="headerlink" title="はじめまして"></a>はじめまして</h1><p>「はじめまして」用于初次见面时的寒暄语，表示“初次见面（，请多关照）”的意思。</p><p>它可以看作是「はじめる」先变成它的「ます」型「はじめます」，而「ます」型变过去式是将「ます」变成「ました」，也就是说，这个「ました」就是「ます」的「た」型。而「た」型和「て」型的唯一区别就是「た」换成「て」（如果是「だ」就换成「で」）。所以「ます」的「て」型不就是「まして」了吗？而「て」型也就是中顿型，所以，「はじめまして」是「はじめる」的郑重语（丁寧語）的中顿型。既然是中顿型，那后面就应该可以继续接句子啊。不接呢，就是省略了。要接呢，当然也没问题，比如接下面这句。</p><h1 id="どうぞよろしく"><a href="#どうぞよろしく" class="headerlink" title="どうぞよろしく"></a>どうぞよろしく</h1><p>「どうぞよろしく」用于请对方给予关照时，对年龄或地位与自己相仿的人，或者在比较轻松的场合用「どうぞよろしく」。</p><p>「どうぞ」是副词，“请”的意思。</p><p>「よろしく」也是副词，词典上写有四层意思：</p><ol><li>适当地</li><li>请问好，请致意，请关照</li><li>应该，应当，必须</li><li>真像，活像</li></ol><p>那这里就是“请关照”的意思。</p><p>「よろしく」是由「よろしい」变来的。而「よろしい」是「よい」的郑重语（丁寧語）形式。</p><p>「どうぞよろしく」这句话是「どうぞよろしくお願いします／お願いいたします」省略说法。</p><p>「お願いします／お願いいたします」是「お願いする／お願いいたす」郑重语（丁寧語）形式。而「お願いする／お願いいたす」又是「願う」的自谦语形式。</p><p>通过这种分解，我们就很容易理解这句话是怎么来的了，也就很容易把它记住了。</p><h1 id="すみません"><a href="#すみません" class="headerlink" title="すみません"></a>すみません</h1><p>「すみません」适用范围非常广泛，有“对不起”的意思，有“谢谢”的意思，还有“excuse me” 的意思。</p><p>这个词可以看作是由「<ruby>済<rp> (</rp><rt>す</rt><rp>) </rp></ruby>む」变来的，「済む」有“完成，办妥”等意思。「済む」的「ます」型是「すみます」，那「すみません」就是「すみます」的否定形式。「済む」直接否定的意思就是“没完成，没办妥”，事没办妥当然就会感觉很抱歉咯。所以，「すみません」就有了“对不起，很抱歉”的意思。那自己没办妥当然可能就需要拜托麻烦别人啊，那所以又有了“excuse me” 的意思，那要拜托麻烦别人的话，你不得谢谢人家吗？所以又有了“谢谢”的意思。</p><h1 id="きのうはどうもすみませんでした"><a href="#きのうはどうもすみませんでした" class="headerlink" title="きのうはどうもすみませんでした"></a>きのうはどうもすみませんでした</h1><p>这句话是表示为昨天的事情道歉，但是昨天可能已经道过歉了，但这是日本人的习惯，一件事不道歉上三遍心里都过意不去。</p><p>还需要注意的是，这句话里面「<ruby lang="ja">きのう<rp> (</rp><rt>昨日</rt><rp>) </rp></ruby>」是并不是主语，也就是说「は」这个助词提示的并不总是主语。它的作用是提示话题（主题），即它表示后面的话都是在它所提示的这个话题（主题）下展开的。</p><p>「どうも」是个副词，你可以把它看成是「どう」加上「も」，那「どう」是“怎么”的意思，「も」是“也”的意思，所以「どうも」本身就是“怎么也”的意思，表示这个意思时，后面要接否定，而「すみません」正好是个否定，所以「どうもすみません」这句要是直译的话就是“怎么也没办妥”。这个意思引申一下，不就是“实在很抱歉”的意思了吗？所以「どうも」还有“实在”的意思。</p><p>最后的「でした」是「です」的过去式。「です」是个判断词，也叫判断助动词，所以在变形上跟动词还有点相似。如果把「です」可以看作是以「す」结尾的动词，那根据以「す」结尾的动词变「た」型的规则，就是「す」变「し」加「た」咯，所以就变成了「でした」，像上面提到的「ました」也可以看作是由「ます」这样变来的。</p><h1 id="こちらこそ"><a href="#こちらこそ" class="headerlink" title="こちらこそ"></a>こちらこそ</h1><p>当对方表示感谢，道歉，客气时，通常回答「こちらこそ（后续感谢，道歉用语）」，表示“我才应该（谢谢您）”，“我才应该（向您道歉）”，“我才应该（请您关照）”等意思。表示感谢等语句可以省略。</p><p>「こちらこそ」可以分解为「こちら」和「こそ」两个词，「こちら」有“我”，“我这一边”的意思，「こそ」有“才（是）”的意思。所以连在一起本来就是“我（这一边）才（是）”的意思。这样就好记多了。</p><h1 id="おはよう"><a href="#おはよう" class="headerlink" title="おはよう"></a>おはよう</h1><p>这个词，课本上解释说是“早上或上午见面时的寒暄用语”，其实这个词不限于早上用，也可以用于一天中第一次见面的时候用，哪怕是晚上见面的时候。为啥呢？</p><p>「おはよう」还可以写作「<span lang="ja">お早う</span>」，看到里面有个“早”，所以常常会被误认为这个是“早上”的“早”，其实呢，并不是。「<span lang="ja">お早う</span>」其实是由「<span lang="ja">お早く…ですね</span>」中的「<span lang="ja">お早く</span>」演变而来的。所以，这个问候原本其实差不多是“哟，您来的这么早！”，““哟，这么早就上工了！”之类的意思。</p><p>这个词对比较亲近的人或在比较轻松的场合下可以使用，对年龄，地位高于自己的人，或者在比较郑重的场合下要说：「<span lang="ja">お早うございます</span>」。</p><h1 id="じゃあ、また"><a href="#じゃあ、また" class="headerlink" title="じゃあ、また"></a>じゃあ、また</h1><p>用于与比较亲近的人道别时，表示”回头见“，”再见“的意思。</p><p>这句话里，「また」其实是「また会いましょう」的省略。</p><p>所以，从上面这几个基本问候语，就可以看出日本人说话真是能省就省啊！这还不算什么，在现在的日本年轻人当中，还有一种不被承认是正确的打招呼方式：「おっす」。它主要是在关系很好的男人之间使用，不区分早、中、晚。因为据说它就是「おはようございます。」的缩写，把整个一句话的中间部分省略成一个促音了，这种省略方式真是让人“不明觉厉”啊！</p><hr><p>好吧，我承认上面的很多内容是我自己瞎编的，今天编不下去了，所以就先到这里吧。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;今天讲几个问候寒暄语：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;はじめまして&lt;/li&gt;
&lt;li&gt;どうぞよろしく&lt;/li&gt;
&lt;li&gt;すみません&lt;/li&gt;
&lt;li&gt;きのうはどうもすみませんでした&lt;/li&gt;
&lt;li&gt;こちらこそ&lt;/li&gt;
&lt;li&gt;おはよう&lt;/li&gt;
&lt;li&gt;じゃあ、また&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>语气助词「ね」</title>
    <link href="https://coolcode.org/2017/09/23/my-japanese-learning-notes-11/"/>
    <id>https://coolcode.org/2017/09/23/my-japanese-learning-notes-11/</id>
    <published>2017-09-23T14:00:00.000Z</published>
    <updated>2018-03-26T13:51:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>语气助词「ね」接在句子的末尾，可以表示向对方确认自己的谈话内容或征得对方的赞同。通常可译为“吧”，“哈”。这时最后的「ね」读升调。例如课文中的：</p><blockquote><p><span lang="ja">あ、昨日の方ですね。</span><br>啊，你是昨天那位吧。</p></blockquote><blockquote><p><span lang="ja">「初めまして」じゃありませんね。</span><br>不是“初次见面”哈。</p></blockquote><a id="more"></a><p>语气助词「ね」接在句子的末尾，还可以表示感叹的语气，相当于“啊”，这时一般读降调。比如：</p><blockquote><p><span lang="ja">安いですね。</span><br>真便宜啊！</p></blockquote><blockquote><p><span lang="ja">可愛いですね。</span><br>好可爱啊！</p></blockquote><p>注意，升调和降调表示的意思是有所不同的，例如：</p><blockquote><p><span lang="ja">A：おいしいですね。</span><br><span lang="ja">B：おいしいですね。</span><br>A：好吃吧？<br>B：好吃啊！</p></blockquote><p> 另外，我们还会遇到一个很常见的句子：</p><blockquote><p><span lang="ja">そうですね。</span></p></blockquote><p>这句话有很多意思，而且很容易跟：</p><blockquote><p><span lang="ja">そうですか。</span></p></blockquote><p>搞混。因为<span lang="ja">「そうですか。」</span>也是用来表示确认的。</p><p>但这两句话表达的意思有所不同。</p><p><span lang="ja">「そうですか。」</span>一般翻译为“是吗！”，“是这样啊！”，有原来如此，恍然大悟的意思。包含有对所听到的事，之前不了解，听了之后才知道这样一层含义。</p><p>而<span lang="ja">「 そうですね。」</span>并没有这层意思。不过它所能表达的意思更多一些。</p><p>比如它有表示认同的意思，即包含有对所听到的事，之前是了解的，并且或多或少有相同的认知这样一层含义。这种情况下可以翻译成：“是啊！”，“是这样的呢！”。</p><p>但它更常用来表示随声附和，它并不一定表示对对方所说的话的肯定，有时候对方说的话可能还是特殊疑问句，根本就不可能用是或否来回答，所以这时候，这句话只是表达一种缓和的语气。比如：</p><blockquote><p><span lang="ja">A：テレビをよく見ますか。</span><br><span lang="ja">B：そうですね。毎日は見ません。</span></p></blockquote><p>翻译成汉语是：</p><blockquote><p>A：经常看电视吗？<br>B：这个嘛，不是每天都看。</p></blockquote><p>这种时候，<span lang="ja">「 そうですね。」</span>一般翻译成 “嗯”，“这个嘛”，“怎么说呢” 之类的。</p><p>在后面的课文中，我们经常会见到表达这种含义的这句话，有时候还会写作：</p><blockquote><p><span lang="ja">「 そうですねえ。」</span></p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;语气助词「ね」接在句子的末尾，可以表示向对方确认自己的谈话内容或征得对方的赞同。通常可译为“吧”，“哈”。这时最后的「ね」读升调。例如课文中的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;span lang=&quot;ja&quot;&gt;あ、昨日の方ですね。&lt;/span&gt;&lt;br&gt;啊，你是昨天那位吧。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;span lang=&quot;ja&quot;&gt;「初めまして」じゃありませんね。&lt;/span&gt;&lt;br&gt;不是“初次见面”哈。&lt;/p&gt;
&lt;/blockquote&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>名词谓语句的中顿</title>
    <link href="https://coolcode.org/2017/09/21/my-japanese-learning-notes-10/"/>
    <id>https://coolcode.org/2017/09/21/my-japanese-learning-notes-10/</id>
    <published>2017-09-21T17:18:00.000Z</published>
    <updated>2018-03-26T13:44:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>在我们说话的时候，有可能要用两句或多句话才能表达一个完整的意思。但如果这其中的每句话都像前面讲的名词谓语句那样，用单独的句子来表示的话，会显得很零碎，看上去没有一种整体的感觉。</p><p>在这种情况下，我们可以使用句子的中顿形态，将两句话或多句话连接为一个整体。但是到目前为止，我们仅仅只讲了名词谓语句，所以现在只讲名词谓语句的中顿形态。但这并不是说，名词谓语句只能跟名词谓语句之间使用下面的这种中顿形态进行连接，跟起它的句式进行连接时，它的中顿形态是相同的。</p><a id="more"></a><p>下面先来看两个例子：</p><blockquote><p><span lang="ja">高橋さんは高校の後輩で、今、京華大学の語学研修生です。</span><br><span lang="ja">京華大学はそちらで、北燕大学はあちらです。</span></p></blockquote><p>第一个例子中，前后两个句子的主语都是<span lang="ja">「高橋さん」</span>，所以第二个分句的主语就省略了，只保留了第一个分句的主语。即第一个分句中的<span lang="ja">「高橋さん」</span>也是整个句子的主语。</p><p>而第二个例子中，前后两个句子的主语不同，所以第二个句子的主语是不能省略的。</p><p>另外，大家会注意到在这两个例子中，第一个分句的结尾用了「で」加顿号「、」，这个顿号相当于汉语中的逗号，猜也猜得出来。但是这个「で」看上去像是「です」省略了「す」，那你会不会认为表示名词谓语句中顿形式的最后的这个「で」就是把「です」去掉「す」呢？</p><p>如果你这么想的话，那么恭喜你，你想多了。</p><p>这个「で」是「です」的连用形，当然它也是「だ」、「である」等判断词的连用型，只不过还没讲到，这里先忽略。它只是看上去恰好像「です」省略了「す」而已，实际上并没有去掉「す」就表示中顿的这种变法。如果非要这么认为的话，后面学到动词的「ます」形时，要是搞出个把「ます」去掉「す」只剩下一个「ま」来表中顿就笑话了，因为根本不存在「ま」这种中顿。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在我们说话的时候，有可能要用两句或多句话才能表达一个完整的意思。但如果这其中的每句话都像前面讲的名词谓语句那样，用单独的句子来表示的话，会显得很零碎，看上去没有一种整体的感觉。&lt;/p&gt;
&lt;p&gt;在这种情况下，我们可以使用句子的中顿形态，将两句话或多句话连接为一个整体。但是到目前为止，我们仅仅只讲了名词谓语句，所以现在只讲名词谓语句的中顿形态。但这并不是说，名词谓语句只能跟名词谓语句之间使用下面的这种中顿形态进行连接，跟起它的句式进行连接时，它的中顿形态是相同的。&lt;/p&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>名词谓语句的疑问句式</title>
    <link href="https://coolcode.org/2017/09/21/my-japanese-learning-notes-9/"/>
    <id>https://coolcode.org/2017/09/21/my-japanese-learning-notes-9/</id>
    <published>2017-09-21T15:06:00.000Z</published>
    <updated>2018-03-25T06:55:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>上一篇我们讲了名词谓语句<span lang="ja">「N<sub>1</sub> は N<sub>2</sub> です」</span>，那么要把它变成疑问句该怎么变呢？</p><a id="more"></a><h1 id="疑问句「Sか」"><a href="#疑问句「Sか」" class="headerlink" title="疑问句「Sか」"></a>疑问句<span lang="ja">「Sか」</span></h1><p>在陈述句句尾接语气助词「か」就构成了疑问句，「か」在这里相当于汉语的【吗】。书写时，疑问词「か」后面一般不使用问号，而是用句号。</p><p>名词谓语句的疑问句式是：</p><blockquote><p><span lang="ja">N<sub>1</sub> は N<sub>2</sub> ですか。</span></p></blockquote><p>疑问句表示疑问的时候读升调，表示确认的时候读降调。</p><p>当一句话有疑问，惊讶的语气，但是却没有疑问词「か」的时候，要使用问号。这时候读升调。</p><p>日语的口语常常是能省就省。比如名词谓语句中，如果是疑问句式，主语是听话人；或者陈述句式，主语是说话人自己，那么「N<sub>1</sub> は」部分在对话时，就会常常省略。</p><p>比如课文中的：</p><blockquote><p><span lang="ja">あ、日本の方ですか。</span>（省略了<span lang="ja">「あなたは」</span>）<br><span lang="ja">ああ、日本語学科の方ですか。</span>（省略了<span lang="ja">「あなたは」</span>）<br><span lang="ja">高橋美穂です。</span>（省略了<span lang="ja">「私は」</span>）<br><span lang="ja">王宇翔です。</span>（省略了<span lang="ja">「私は」</span>）</p></blockquote><p>当主语可以通过上下文推断出来的时候，主语也会省略。</p><p>上面列举的疑问句都是一般疑问句，即疑问句中不包含疑问词的疑问句。</p><p>这种疑问句在回答时，需要先回答【是】或【不是】，那么日语的【是】或【不是】怎么说呢。</p><h1 id="一般疑问句的回答"><a href="#一般疑问句的回答" class="headerlink" title="一般疑问句的回答"></a>一般疑问句的回答</h1><p>比较正式的回答是这样的：</p><blockquote><p><span lang="ja">はい</span> （是）<br><span lang="ja">いいえ</span> （不是）</p></blockquote><p>比较随意的回答是：</p><blockquote><p><span lang="ja">ええ</span> （是）<br><span lang="ja">いえ</span> （不是）</p></blockquote><p>当然，后面还可以有补充说明。比如肯定的情况下，一般跟<span lang="ja">「そうです」</span>。否定的情况下可以加一些解释。</p><p>但有时候你不想回答的太明白，只想给个模棱两可，不太明确，不太肯定，也不想否定的回答的话，可以在<span lang="ja">「ええ」</span>后面加上<span lang="ja">「ちょっと」、「まあ」</span>之类的。</p><p>比如课文里，小王回答<span lang="ja">鈴木さん</span>的问题和<span lang="ja">鈴木さん</span>回答小王问题时，都是用的这种方式。这是日语的一个特点，不把话说死了，对方怎么理解是他自己的事情，如果对方理解错了，对方也会觉得责任在自己，而不是在说话人。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;上一篇我们讲了名词谓语句&lt;span lang=&quot;ja&quot;&gt;「N&lt;sub&gt;1&lt;/sub&gt; は N&lt;sub&gt;2&lt;/sub&gt; です」&lt;/span&gt;，那么要把它变成疑问句该怎么变呢？&lt;/p&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>名词及名词谓语句</title>
    <link href="https://coolcode.org/2017/09/20/my-japanese-learning-notes-8/"/>
    <id>https://coolcode.org/2017/09/20/my-japanese-learning-notes-8/</id>
    <published>2017-09-20T05:59:00.000Z</published>
    <updated>2018-03-25T04:21:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>在日语中，句子按照成分划分的话，分为主语「<ruby lang="ja">主語<rp> (</rp><rt>しゅご</rt><rp>) </rp></ruby>」、谓语「<ruby lang="ja">述語<rp> (</rp><rt>じゅつご</rt><rp>) </rp></ruby>」等成分。</p><p>而构成句子的词，按照词性分类的话，可以分为名词「<ruby lang="ja">名詞<rp> (</rp><rt>めいし</rt><rp>) </rp></ruby>」、形容词「<ruby lang="ja">形容詞<rp> (</rp><rt>けいようし</rt><rp>) </rp></ruby>」、动词「<ruby lang="ja">動詞<rp> (</rp><rt>どうし</rt><rp>) </rp></ruby>」、副词「<ruby lang="ja">副詞<rp> (</rp><rt>ふくし</rt><rp>) </rp></ruby>」、助词「<ruby lang="ja">助詞<rp> (</rp><rt>じょし</rt><rp>) </rp></ruby>」等。</p><a id="more"></a><h1 id="名词"><a href="#名词" class="headerlink" title="名词"></a>名词</h1><p>名词是表示事物概念的词类，它主要用于表示事物或人的名称，还可以表示状态、变化、动作。在句子中一般充当主语、补足语、连体修饰语，这时要后续助词，也可以后续判断词充当谓语。</p><p>日语的名词（包括代名词及数量词）比较简单，不像动词，形容词有许多活用形式，它在使用时没有词形上的变化。日语中称之为「<ruby lang="ja">体言<rp> (</rp><rt>たいげん</rt><rp>) </rp></ruby>」。</p><h1 id="名词修饰名词「の」"><a href="#名词修饰名词「の」" class="headerlink" title="名词修饰名词「の」"></a>名词修饰名词<span lang="ja">「の」</span></h1><p>格助词「の」接在名词后面，构成连体修饰语（定语）修饰后面的名词，相当于汉语的【的】，有时可不译。</p><p>它可以表示领属关系，例如：</p><blockquote><p><span lang="ja">日本の方</span><br><span lang="ja">京華大学の学生</span><br><span lang="ja">鈴木さんのガールフレンド</span></p></blockquote><p>它还可以表示前后两者之间是同位关系，即「の」前后的名词所指相同，一般「の」前面的名词为表示关系、性质的名词，「の」后面的名词为专有名词，如人名等。例如：</p><blockquote><p><span lang="ja">学長の張光輝</span><br><span lang="ja">友達の王さん</span><br><span lang="ja">後輩の高橋さん</span></p></blockquote><h1 id="名词谓语句「N1はN2です」"><a href="#名词谓语句「N1はN2です」" class="headerlink" title="名词谓语句「N1はN2です」"></a>名词谓语句<span lang="ja">「N<sub>1</sub>はN<sub>2</sub>です」</span></h1><p>名词N<sub>2</sub>后面接判断词「です」作谓语，构成名词谓语句，用于说明主语N<sub>1</sub>所指称的内容，相当于汉语的“N<sub>1</sub>是N<sub>2</sub>”。名词谓语句中的主语由凸显助词「は」提示，读作 [wa]。判断词<span lang="ja">「です」</span>根据所要表示的语法意义的不同而有词形上的变化，日语称为活用，比如<span lang="ja">「ではありません／じゃありません」</span>是它的否定形式，<span lang="ja">「じゃありません」</span>只用于口语。</p><p>名词谓语句还可以细分为判断句和属性句。</p><p>判断句中，存在 N<sub>1</sub> = N<sub>2</sub> 的关系。而属性句中，表示 N<sub>1</sub> 具有 N<sub>2</sub> 这一属性。</p><p>例如：</p><blockquote><p><span lang="ja">王さんの友達は鈴木さんです。</span>（判断句）<br><span lang="ja">鈴木さんは王さんの友達です。</span>（属性句）</p></blockquote><p>这两句看上去差不多，但实际上差别很大。</p><p>第一句话表示：<span lang="ja">「王さんの友達 = 鈴木さん」</span>这种关系。这种情况下，可以换成<span lang="ja">「鈴木さんが王さんの友達です」</span>。也就是说判断句可以从<span lang="ja">「N<sub>1</sub>はN<sub>2</sub>です」</span>替换为<span lang="ja">「N<sub>2</sub>がN<sub>1</sub>です」</span>的形式。</p><p>而第二句话表示，<span lang="ja">「鈴木さん」</span>的属性是<span lang="ja">「王さんの友達」</span>。属性句不能替换成<span lang="ja">「N<sub>2</sub>がN<sub>1</sub>です」</span>的形式。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在日语中，句子按照成分划分的话，分为主语「&lt;ruby lang=&quot;ja&quot;&gt;主語&lt;rp&gt; (&lt;/rp&gt;&lt;rt&gt;しゅご&lt;/rt&gt;&lt;rp&gt;) &lt;/rp&gt;&lt;/ruby&gt;」、谓语「&lt;ruby lang=&quot;ja&quot;&gt;述語&lt;rp&gt; (&lt;/rp&gt;&lt;rt&gt;じゅつご&lt;/rt&gt;&lt;rp&gt;) &lt;/rp&gt;&lt;/ruby&gt;」等成分。&lt;/p&gt;
&lt;p&gt;而构成句子的词，按照词性分类的话，可以分为名词「&lt;ruby lang=&quot;ja&quot;&gt;名詞&lt;rp&gt; (&lt;/rp&gt;&lt;rt&gt;めいし&lt;/rt&gt;&lt;rp&gt;) &lt;/rp&gt;&lt;/ruby&gt;」、形容词「&lt;ruby lang=&quot;ja&quot;&gt;形容詞&lt;rp&gt; (&lt;/rp&gt;&lt;rt&gt;けいようし&lt;/rt&gt;&lt;rp&gt;) &lt;/rp&gt;&lt;/ruby&gt;」、动词「&lt;ruby lang=&quot;ja&quot;&gt;動詞&lt;rp&gt; (&lt;/rp&gt;&lt;rt&gt;どうし&lt;/rt&gt;&lt;rp&gt;) &lt;/rp&gt;&lt;/ruby&gt;」、副词「&lt;ruby lang=&quot;ja&quot;&gt;副詞&lt;rp&gt; (&lt;/rp&gt;&lt;rt&gt;ふくし&lt;/rt&gt;&lt;rp&gt;) &lt;/rp&gt;&lt;/ruby&gt;」、助词「&lt;ruby lang=&quot;ja&quot;&gt;助詞&lt;rp&gt; (&lt;/rp&gt;&lt;rt&gt;じょし&lt;/rt&gt;&lt;rp&gt;) &lt;/rp&gt;&lt;/ruby&gt;」等。&lt;/p&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>日语五十音图（拗音）</title>
    <link href="https://coolcode.org/2017/09/19/my-japanese-learning-notes-7/"/>
    <id>https://coolcode.org/2017/09/19/my-japanese-learning-notes-7/</id>
    <published>2017-09-19T01:01:00.000Z</published>
    <updated>2018-03-25T03:02:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>现代日语中，拗音是指由い段辅音行假名和复元音「や、ゆ、よ」拼合起来的音节，共有三十三个。书写方式跟促音类似，横写时，小字体的「ゃ、ゅ、ょ」跟在「い段」辅音行假名的右下角，竖写时跟在「い段」辅音行假名的下右角。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2017/09/19/my-japanese-learning-notes-7/youonn.png" alt="拗音" title>                </div>                <div class="image-caption">拗音</div>            </figure><p>「ぢゃ、ぢゅ、ぢょ」的发音跟「じゃ、じゅ、じょ」相同，在现代日语中已经被废弃，不再使用。<br><a id="more"></a></p><p>除了上述拗音以外，为了在拼写外来词时，尽量接近原来的发音，日语中还引入了一些特殊的拗音。如下表：</p><table><thead><tr><th style="text-align:center">あ段</th><th style="text-align:center">い段</th><th style="text-align:center">う段</th><th style="text-align:center">え段</th><th style="text-align:center">お段</th></tr></thead><tbody><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">イェ</td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center">ウィ</td><td style="text-align:center"></td><td style="text-align:center">ウェ</td><td style="text-align:center">ウォ</td></tr><tr><td style="text-align:center">ヴァ</td><td style="text-align:center">ヴィ</td><td style="text-align:center">ヴ</td><td style="text-align:center">ヴェ</td><td style="text-align:center">ヴォ</td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">ヴュ</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">キェ</td><td style="text-align:center"></td></tr><tr><td style="text-align:center">クァ</td><td style="text-align:center">クィ</td><td style="text-align:center">クゥ</td><td style="text-align:center">クェ</td><td style="text-align:center">クォ</td></tr><tr><td style="text-align:center">グァ</td><td style="text-align:center">グィ</td><td style="text-align:center">グゥ</td><td style="text-align:center">グェ</td><td style="text-align:center">グォ</td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">シェ</td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">ジェ</td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center">スィ</td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center">ズィ</td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">チェ</td><td style="text-align:center"></td></tr><tr><td style="text-align:center">ツァ</td><td style="text-align:center">ツィ</td><td style="text-align:center"></td><td style="text-align:center">ツェ</td><td style="text-align:center">ツォ</td></tr><tr><td style="text-align:center"></td><td style="text-align:center">ティ</td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center">ディ</td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">テュ</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">デュ</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">トゥ</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">ドゥ</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">ニェ</td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">ヒェ</td><td style="text-align:center"></td></tr><tr><td style="text-align:center">ファ</td><td style="text-align:center">フィ</td><td style="text-align:center"></td><td style="text-align:center">フェ</td><td style="text-align:center">フォ</td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">フュ</td><td style="text-align:center"></td><td style="text-align:center"></td></tr></tbody></table><p>上面有些特殊拗音的发音在古日语中就有，但写法不一样，比如「は行」假名在古代有一段时期的发音跟上面的「ファ行」是一样的。甚至还有 [twa] [kwa] 这样的拗音，被称为合拗音，但现在已经不用了。上面这些特殊拗音现在只用在外来语中。</p><p>上面的特殊拗音中有一个「ヴ行」，「ヴ行」的音罗马音写作「va、vi、vu、ve、vo」，但是因为日语中没有咬唇音，也没有唇齿摩擦音，读起来的时候，牙齿跟嘴唇是不接触的（「ファ行」假名的发音也有这个特点，所以跟汉语的【发】还是不同的），那就只剩下两片嘴唇接触了，因此实际发音就变成了「ba，bi，bu，be，bo」，跟「ば行」假名的发音是一样的，所以现在外来语的拼写上都直接用「ば行」假名代替「ヴ行」假名了，比如小提琴，现在都写作：「バイオリン」，很少会见到「ヴァイオリン」的写法，但这样写也不算错，但读起来其实跟「バイオリン」是一样的。</p><hr><p>到这里，日语的五十音图部分就全部总结完了，细心的朋友可能会发现，里面缺少了对日语音调的总结，主要是我觉得这部分书上写的很清楚，也很容易理解，没有什么特殊的难点，主要是多听多练就能掌握了，所以，这里就不单独总结了。</p><p>从下一篇，就要开始总结语法和单词了，总算要进入正题了，想想还有点小激动呢。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;现代日语中，拗音是指由い段辅音行假名和复元音「や、ゆ、よ」拼合起来的音节，共有三十三个。书写方式跟促音类似，横写时，小字体的「ゃ、ゅ、ょ」跟在「い段」辅音行假名的右下角，竖写时跟在「い段」辅音行假名的下右角。&lt;/p&gt;
&lt;figure class=&quot;image-bubble&quot;&gt;
                &lt;div class=&quot;img-lightbox&quot;&gt;
                    &lt;div class=&quot;overlay&quot;&gt;&lt;/div&gt;
                    &lt;img src=&quot;/2017/09/19/my-japanese-learning-notes-7/youonn.png&quot; alt=&quot;拗音&quot; title&gt;
                &lt;/div&gt;
                &lt;div class=&quot;image-caption&quot;&gt;拗音&lt;/div&gt;
            &lt;/figure&gt;
&lt;p&gt;「ぢゃ、ぢゅ、ぢょ」的发音跟「じゃ、じゅ、じょ」相同，在现代日语中已经被废弃，不再使用。&lt;br&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>日语五十音图（促音）</title>
    <link href="https://coolcode.org/2017/09/17/my-japanese-learning-notes-6/"/>
    <id>https://coolcode.org/2017/09/17/my-japanese-learning-notes-6/</id>
    <published>2017-09-17T23:15:00.000Z</published>
    <updated>2018-03-25T01:59:38.000Z</updated>
    
    <content type="html"><![CDATA[<p>促音很特殊，本身不发音，却占一个音拍，就像音乐中的休止符一样。促音的写法是把「つ、ツ」写作普通假名的 1/4 大小的「っ、ッ」，因为促音不会出现在词首，所以横写时跟在它前面假名的右下角，竖写时跟在它上面假名的下右角。</p><p>除了「あっ」「えっ」这种感叹词，促音一般也不出现在词尾。所以在使用罗马音输入促音时，双写促音之后假名的辅音字母就可以了。当遇到「あっ」「えっ」这种促音在结尾的特殊情况时，使用 [xtu]、[xtsu]、[ltu]、[ltsu] 的方式中的任何一种都可以输入。</p><p>促音的发音要领，课本上写的很详细，我这里就不重复了。促音一般只出现在「か、さ、た、ぱ」四行假名的前面。但对于外来语来说，促音也可出现在「が、ざ、だ、ば」四行假名之前。「さ、ざ」行前的促音为摩擦促音，其它行前的促音为阻塞促音。</p><p>促音变化多出现于两字汉字词中。比如汉字词中第一个汉字的结尾为「ち、つ」，后续接「か、さ、た、は」行假名的汉字时，无一例外出现促音变化，且「は」行假名会同时发生半浊音变化，比如 「<ruby lang="ja">一<rp> (</rp><rt>いち</rt><rp>) </rp></ruby>」和「<ruby lang="ja">本<rp> (</rp><rt>ほん</rt><rp>) </rp></ruby>」组合以后会变成「<ruby lang="ja">一本<rp> (</rp><rt>いっぽん</rt><rp>) </rp></ruby>」。但是在和语词中，则不一定，比如「<ruby lang="ja">五日<rp> (</rp><rt>いつか</rt><rp>) </rp></ruby>」。以「く、き」为尾音的汉字与第一个音为「か」行假名的汉字结合成新词时，一般也会发生促音变化，但会有例外。</p><p>和语词中的促音现象虽然也很普遍，但是和语词的促音变化随意性很强，没什么明显的规律可循。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;促音很特殊，本身不发音，却占一个音拍，就像音乐中的休止符一样。促音的写法是把「つ、ツ」写作普通假名的 1/4 大小的「っ、ッ」，因为促音不会出现在词首，所以横写时跟在它前面假名的右下角，竖写时跟在它上面假名的下右角。&lt;/p&gt;
&lt;p&gt;除了「あっ」「えっ」这种感叹词，促音一般也
      
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>日语五十音图（长音）</title>
    <link href="https://coolcode.org/2017/09/17/my-japanese-learning-notes-5/"/>
    <id>https://coolcode.org/2017/09/17/my-japanese-learning-notes-5/</id>
    <published>2017-09-17T00:48:00.000Z</published>
    <updated>2018-03-25T00:58:24.000Z</updated>
    
    <content type="html"><![CDATA[<p>日语中一个假名占一拍，将假名的发音时间延长一倍即形成长音。</p><p>在书写上，用片假名书写的单词和用平假名书写的单词的长音表示方式是有区别的。</p><a id="more"></a><h1 id="片假名单词中的长音"><a href="#片假名单词中的长音" class="headerlink" title="片假名单词中的长音"></a>片假名单词中的长音</h1><p>片假名单词中，长音用“ー”表示。比如「セーター、ノート、サービス」等等。</p><h1 id="平假名单词中的长音"><a href="#平假名单词中的长音" class="headerlink" title="平假名单词中的长音"></a>平假名单词中的长音</h1><p>平假名单词中不同段的假名后面加不同的假名表示长音，如下表：</p><table><thead><tr><th style="text-align:center">段</th><th style="text-align:center">用来表示长音的假名</th></tr></thead><tbody><tr><td style="text-align:center">あ段</td><td style="text-align:center">あ</td></tr><tr><td style="text-align:center">い段</td><td style="text-align:center">い</td></tr><tr><td style="text-align:center">う段</td><td style="text-align:center">う</td></tr><tr><td style="text-align:center">え段</td><td style="text-align:center">い、え</td></tr><tr><td style="text-align:center">お段</td><td style="text-align:center">う、お</td></tr></tbody></table><p>罗马音的长音符号不重要，这里忽略。只要记住打字的时候片假名的长音符号“ー”用减号键输入，平假名的就按照对应假名读音的罗马音输入就行了。</p><h1 id="特殊情况"><a href="#特殊情况" class="headerlink" title="特殊情况"></a>特殊情况</h1><p>上面的规则听上去很简单，很容易掌握是吧。但是有时候，你可能会发现，有些明明是平假名单词，但是里面却有片假名中才有的长音符号“ー”。在《综合日语》课本上就有许多这种情况，比如：</p><blockquote><ul><li>52页“数字的连续”那一节，其中：<span lang="ja">「にー、しー、ごー」</span>。</li><li>77页<span lang="ja">「高橋」</span>最后喊<span lang="ja">「鈴木」</span>的时候是：<span lang="ja">「鈴木さーん！」</span>。</li><li>95页<span lang="ja">「黄・劉・孫」</span>最后的感叹<span lang="ja">「あー！」</span>。</li><li>174页新出单词表中<span lang="ja">「王府井」</span>的假名写的是<span lang="ja">「わんふーちん」</span>。</li><li>210页对话中<span lang="ja">「高橋」</span>对<span lang="ja">「鈴木」</span>的高超技术表示赞叹时说的<span lang="ja">「すごーい」</span>，还有后面表示不太明白时说的<span lang="ja">「えーっと」</span>。</li><li>264页<span lang="ja">「高橋・趙」</span>大声道<span lang="ja">「えー？！」</span>。</li><li>279页最后<span lang="ja">「王」</span>挂了电话之后<span lang="ja">「やったー！」</span>。</li><li>307页最后<span lang="ja">「王」</span>说<span lang="ja">「えー？」</span>，<span lang="ja">「鈴木」</span>说<span lang="ja">「うーん」</span>。</li></ul></blockquote><p>第一册课本翻下来，就发现了这么多的例外。我们总不能说这些都是错的吧。开始的时候，我确实也质疑过这些写法的正确性，还咨询里《综合日语》的主编老师，老师的回答是没有错误，这些都是正确的写法，但是并没有给出解释，只是说这种情况不多，见一个记住一个就可以了。</p><p>不过我的性格天生是爱刨根问底，在找不到切实的文献解释的情况下，我也想要自己总结一下，至于对不对，就由各位看官来评判了。</p><p>首先，如果一个平假名的单词本身不包含长音，但是发音时却需要延长一拍，或者说表示发音时延长了一拍，那么这时候，用长音符号“ー”来表示长音。比如上面的这几个数字<span lang="ja">「にー、しー、ごー」</span>和<span lang="ja">「鈴木さーん！」</span>的<span lang="ja">「さーん！」、「すごーい」</span>等就属于这种情况。因为如果要用平假名单词长音表示方式来添加长音的话，这个词就变样子了，甚至会变成另外意思的单词。所以要表示这种词的长音，只能用这种方式。</p><p>其次，用来表示语气的词，比如「あー」「えー」本质上跟「ああ」「ええ」没有什么区别，你会发现同一篇课文中就有这两种写法混用的情况。那为什么非要用两种不同的形式呢。据我猜测，可能用「あー」「えー」写出来的词，要表达的的情绪可能更激动更大声更夸张一点吧。</p><p>最后，本来通常应该用片假名书写的外来词，比如「わんふーちん」，如果其中含有长音符号，当把它们写作平假名形式时，长音符号保持不变，而不是按照平假名长音规则把长音符号改成对应的平假名。</p><p>最后这种情况还存在相反的情况，即如果一个平假名的单词，其中包含了长音，当因为某些原因（比如表示强调，或者作为标题）把它写作片假名形式时，用来表示长音的假名不需要改写成片假名的长音符号“ー”，直接把原来的平假名直接转换成片假名即可。</p><h1 id="长音的来源"><a href="#长音的来源" class="headerlink" title="长音的来源"></a>长音的来源</h1><p>片假名单词的长音很简单，因为片假名单词一般都是用来书写外来词的，所以片假名中的长音符号表示的就是外来词中需要拖长的读音。</p><p>但是平假名长音的来源就有些复杂了。它有两种来源，一种跟片假名单词中的长音来源类似，就是用来表示本来发音就是拖长的，所以，把这个音写成假名时，用代表长音的假名来表示。另一种是，本来发音并不是长音，而是两个不同假名的发音，但由于这两个假名在连读发音时听上去比较相似，自然演变成了长音。这两种来源的不同，造成了长音假名的表示方式出现了区别。</p><h2 id="「お段」为何有「お」和「う」两种长音表示"><a href="#「お段」为何有「お」和「う」两种长音表示" class="headerlink" title="「お段」为何有「お」和「う」两种长音表示"></a>「お段」为何有「お」和「う」两种长音表示</h2><p>先来看个例子，比如「<ruby lang="ja">大きい<rp> (</rp><rt>おおきい</rt><rp>) </rp></ruby>」，意思是大的。可这个词古时候是写作「おほきい」的，但随着日语的演化，「ほ」在非词首位置的发音变成了「お」（之前讲「は、へ」发音时也提过，讲浊音的那一节也提过），为了让发音和书写统一起来，后来「は行」假名在非词首位置时都替换成了「わ、い、う、え、お」，但是并不是说日语单词中间有「は行」假名的情况完全不存在，比如「<ruby lang="ja">日本<rp> (</rp><rt>にほん</rt><rp>) </rp></ruby>」「<ruby lang="ja">絵本<rp> (</rp><rt>えほん</rt><rp>) </rp></ruby>」「<ruby lang="ja">記念品<rp> (</rp><rt>きねんひん</rt><rp>) </rp></ruby>」等词中就有「ほ」「ひ」这些「は行」假名，我猜测可能是汉字词不受这个影响吧。经过这个变化之后，「おほきい」就成了「おおきい」了，两个「お」连读的话，发音自然就变成了长音。所以，「お段」后面加「お」得到的长音，是来自上面提到的第二种来源的。</p><p>其它「あ段」加「あ」，「い段」加「い」，「う段」加「う」，「え段」加「え」的长音表达方式则全部来自上面所提到的第一种来源。那为了表示「お段」两种长音来源的区别，就规定第一种来源的「お段」长音用「う」来表示。所以就有了「お段」长音的两种表示方法。</p><h2 id="「え段」为何有「え」和「い」两种长音表示"><a href="#「え段」为何有「え」和「い」两种长音表示" class="headerlink" title="「え段」为何有「え」和「い」两种长音表示"></a>「え段」为何有「え」和「い」两种长音表示</h2><p>上面一段已经提到，「え段」加「え」的长音就是表示第一种来源，那么「え段」后面跟「い」，发生连读时变成长音，就属于第二种来源了。</p><p>而事实上，「え段」后面加「い」得到的长音，在日本人说日语的时候也经常感觉它们是读成两个音的，比如「<ruby lang="ja">綺麗<rp> (</rp><rt>きれい</rt><rp>) </rp></ruby>」，本来「れい」应该作为一个长音来读，但是有不少日本人发音的时候，是读成「き・れ・い」，「い」被当成单独的音来读了。</p><h1 id="长音的辨识"><a href="#长音的辨识" class="headerlink" title="长音的辨识"></a>长音的辨识</h1><p>虽然上面的长音规则看上去很简单，但并不是所有看上去符合上面平假名长音组合的音都是长音。比如动词的词尾「う」，即使跟前面假名组合之后符合长音规则，也不是长音。但是形容词的词尾「い」，如果跟前面的假名组合之后符合长音规则，就会被算作长音。</p><p>另外，对于音读的汉字词来说，长音的发音是有一定规律可循的，一般来说，如果字所对应的汉语发音的韵母部分是复韵母（|ao|、|iao|等）或者是后鼻音复韵母（|ang|、|eng|、|ing|、|ong|）则对应的汉字的日文发音的最后为长音。如果是前鼻音复韵母（|an|、|en|、|in|、|un|、|iun|）则对应的汉字的日文发音最后为拨音。比如：</p><table><thead><tr><th style="text-align:center">汉字词</th><th style="text-align:center">拼音</th><th style="text-align:center">假名</th></tr></thead><tbody><tr><td style="text-align:center"><span lang="ja">先生</span></td><td style="text-align:center">xian sheng</td><td style="text-align:center">せんせい</td></tr><tr><td style="text-align:center"><span lang="ja">無料</span></td><td style="text-align:center">wu liao</td><td style="text-align:center">むりょう</td></tr><tr><td style="text-align:center"><span lang="ja">東京</span></td><td style="text-align:center">dong jing</td><td style="text-align:center">とうきょう</td></tr><tr><td style="text-align:center"><span lang="ja">研究</span></td><td style="text-align:center">yan jiu</td><td style="text-align:center">けんきゅう</td></tr><tr><td style="text-align:center"><span lang="ja">課程</span></td><td style="text-align:center">ke cheng</td><td style="text-align:center">かてい</td></tr></tbody></table><p>当然，这只是说大部分情况符合上面的规律，例外也是有的。比如：</p><table><thead><tr><th style="text-align:center">汉字词</th><th style="text-align:center">拼音</th><th style="text-align:center">假名</th></tr></thead><tbody><tr><td style="text-align:center"><span lang="ja">教授</span></td><td style="text-align:center">jiao shou</td><td style="text-align:center">きょう<strong>じゅ</strong></td></tr><tr><td style="text-align:center"><span lang="ja">景色</span></td><td style="text-align:center">jing se</td><td style="text-align:center"><strong>け</strong>しき</td></tr></tbody></table><p>这些词不规律的元音涉及到古汉语的读音变化，就单独记忆吧。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;日语中一个假名占一拍，将假名的发音时间延长一倍即形成长音。&lt;/p&gt;
&lt;p&gt;在书写上，用片假名书写的单词和用平假名书写的单词的长音表示方式是有区别的。&lt;/p&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>日语五十音图（拨音）</title>
    <link href="https://coolcode.org/2017/09/15/my-japanese-learning-notes-4/"/>
    <id>https://coolcode.org/2017/09/15/my-japanese-learning-notes-4/</id>
    <published>2017-09-15T16:13:00.000Z</published>
    <updated>2018-03-24T06:27:31.000Z</updated>
    
    <content type="html"><![CDATA[<p>在讲拨音之前，先说说【音拍「モーラ」】。日语在节奏上具有等时性（等拍性）。每个假名发音时间基本上是一样长的，也就是每个假名占一拍，这种节奏上的单位就被成为【音拍「モーラ」】。本篇所讲的拨音也是单独占一拍的。后面讲的长音，促音也是各占一拍。但是最后要讲的拗音，虽然由 2 个假名组成（一大一小），但是这两个假名合起来只占一拍，即一个拗音占一拍。</p><a id="more"></a><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2017/09/15/my-japanese-learning-notes-4/gojyuonn.png" alt="日语五十音图" title>                </div>                <div class="image-caption">日语五十音图</div>            </figure><p>在上面的五十音图上，我们会发现除了五段十行，最后还有一个单独的音，这个单独的音就是拨音。</p><p>拨音是一个特殊的音，发音时占一拍的时长，它在发音时受其前后语音环境的影响较大，但都具有通过鼻腔进行共鸣这一特征。</p><p>它们在「あ、い、う、え、お」这五段的假名后面时，发音类似于汉语中的 |ang|、|in|、|un|、|en|、|ong|，也就是说跟「あ、お」这两段组合时，发后鼻音，跟 「い、う、え」这三段组合时，发前鼻音。</p><p>书上还列出了几条规则来说明拨音后面的音对拨音发音的影响，其实这些就不用刻意去记了。因为你分析一下就会发现，你只要让拨音自然而然的过度到后面的音上面，就自然会符合书上列出来的那些规律。</p><p>另外，有一点需要注意，书上写的“口腔通道是关闭的”，并不是指发音时是闭口的，而是指气流在口腔中是否是受阻的。是否闭口，是看是否同化为 [m]。比如辅音 [m] [b] [p] 本来就是闭口起始音，所以从前面的拨音过度到这些辅音时，口型自然就变成了闭口的，发音自然就同化为 [m] 了。</p><p>而 [n] [d] [t] 发音起始时，都是嘴唇张开，舌抵上腭的，所以从拨音过度到这些辅音时，发音自然而然就被同化为 [n] 了。</p><p>最后，[k] [g] [&#331;] 发音起始时，都是嘴唇张开，舌后部抬起贴住软腭，所以从拨音过度到这些辅音时，发音自然而然就被同化为 [&#331;] 了。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在讲拨音之前，先说说【音拍「モーラ」】。日语在节奏上具有等时性（等拍性）。每个假名发音时间基本上是一样长的，也就是每个假名占一拍，这种节奏上的单位就被成为【音拍「モーラ」】。本篇所讲的拨音也是单独占一拍的。后面讲的长音，促音也是各占一拍。但是最后要讲的拗音，虽然由 2 个假名组成（一大一小），但是这两个假名合起来只占一拍，即一个拗音占一拍。&lt;/p&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>日语五十音图（浊音、半浊音）</title>
    <link href="https://coolcode.org/2017/09/14/my-japanese-learning-notes-3/"/>
    <id>https://coolcode.org/2017/09/14/my-japanese-learning-notes-3/</id>
    <published>2017-09-14T19:09:00.000Z</published>
    <updated>2018-03-24T06:27:31.000Z</updated>
    
    <content type="html"><![CDATA[<p>日语中跟五十音图中的清音对应的有 4 行浊音，1 行半浊音。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2017/09/14/my-japanese-learning-notes-3/dakuonntohanndakuonn.png" alt="日语中的浊音和半浊音" title>                </div>                <div class="image-caption">日语中的浊音和半浊音</div>            </figure><a id="more"></a><h1 id="浊音和半浊音符号"><a href="#浊音和半浊音符号" class="headerlink" title="浊音和半浊音符号"></a>浊音和半浊音符号</h1><p>「か」、「さ」、「た」、「は」行的假名右上角加上两个小点就变成了浊音的假名，「は行」假名的右上角加上一个小圈就变成了半浊音的假名。那两个小点叫【浊音符号】，那一个小圈叫【半浊音符号】。</p><h1 id="浊音发展史"><a href="#浊音发展史" class="headerlink" title="浊音发展史"></a>浊音发展史</h1><p>对于现在学过日语同学来说，这都是常识，是理所当然的事情了。但是在古时候，其实是没有浊音和半浊音符号的。</p><p>而且古时候假名的读音，跟现在也不一样，中间发生了许多的变迁，这里就不展开说了，感兴趣的可以去看：<a href="https://ja.wikipedia.org/wiki/%E7%8F%BE%E4%BB%A3%E4%BB%AE%E5%90%8D%E9%81%A3%E3%81%84" target="_blank" rel="noopener">現代仮名遣い</a>。</p><p>在古时候，清音和浊音是没什么区分的，只能通过文章的上下文来推断是读清音还是浊音，或者用万叶假名表示，跟平片假名不同，万叶假名是没有简化过的用于表音的汉字。</p><p>从平安时代开始，浊音符号才开始出现的，主要是大和尚用于标注佛经读音的。但是那个时候的浊音符号各种各样的都有，有一个大黑点「●」，有两个大黑点「●●」，还有三角形「△」，短横线「－」等等。而且位置也是不确定的，有放右上的，右中的，还有右下的，直到室町时代后期，浊音符号才逐渐统一成右上角的位置，形状也逐渐统一成类似于现在的两个小点。而浊音从这个时候才开始作为一个音素而单独存在。</p><p>而半浊音符号据说是来日本的葡萄牙传教士发明的，感兴趣的可以看看「日本人の知らない日本語」这本漫画或者日剧。</p><h1 id="浊音的发音"><a href="#浊音的发音" class="headerlink" title="浊音的发音"></a>浊音的发音</h1><p>浊辅音在发音时，声带是震动的。与此相对的，清辅音在发音时，声带是不震动的。虽然，[g]、[d]、[b] 听上去跟汉语拼音的 |g|、|d|、|b| 很像，实际上却是不同的。汉语拼音中的 |g|、|d|、|b| 实际上是不送气音，而不是浊音，而与其对应的 |k|、|t|、|p| 是送气音。也就是说汉语中的 |k|、|g|、|t|、|d|、|p|、|b| 都是清辅音。</p><p>而日语中的「か行」、「た行」、「ぱ行」中的辅音位于词头时读送气音，在词中和词尾时通常读不送气音。虽然不送气音听上去跟汉语的 |g|，|d|，|b| 很像，但是它们跟日语中的浊音有本质的区别，但是对于母语是汉语的日语初学者来说，往往是分不清它们之间的区别的。之所以分不清楚是因为汉语是通过辅音送气不送气来辨音的，而日语是通过辅音发声时声带是否震动来辨音的。</p><p>这只能靠多听，多练来弥补了。或者可以先学学法语，因为法语中也存在这个问题，而且比日语更严重。因为日语中的清音你就算全部读成送气音也不算错。但是在法语中，如果你把该读不送气音的清辅音读成送气音的清辅音，或者读成了不送气的浊辅音，那就都算你错了。所以，相对来说，日语还是简单。</p><h1 id="鼻浊音"><a href="#鼻浊音" class="headerlink" title="鼻浊音"></a>鼻浊音</h1><p>这还没完。「が行」假名出现在词首以外的位置时（即词中或词末时）原则上要发成鼻浊音的。鼻浊音的辅音听上去像 |ng| 的发音，写成英语音标是 [&#331;]。有了鼻浊音，「か行」和「が行」倒是容易分清了，在词头，「か」读送气音，「が」读不送气音，中国人好区分。在词中和词尾，「か」甭管是读送气音还是不送气音，跟「が」的鼻浊音相比都是可以很明显的区别开的。但是，并不是所有的人都会发鼻浊音，也不是所有的人都愿意发鼻浊音，据说现在日本人中发鼻浊音的人只有 20% 左右了，所以想通过鼻浊音来区分是「か行」和「が行」的发音，恐怕也只有 20% 左右的成功率了。另外，也有一些人其实分不清鼻浊音跟「な行」发音的区别，所以虽然跟「か行」区分开了，如果跟「な行」再混了，也是个麻烦。</p><h1 id="几个特殊音"><a href="#几个特殊音" class="headerlink" title="几个特殊音"></a>几个特殊音</h1><p>在这一组浊音中，有 4 个假名需要注意，它们分别是「<ruby>じ<rp> (</rp><rt>ji</rt><rp>) </rp></ruby>、<ruby>ず<rp> (</rp><rt>zu</rt><rp>) </rp></ruby>、<ruby>ぢ<rp> (</rp><rt>ji</rt><rp>) </rp></ruby>、<ruby>づ<rp> (</rp><rt>zu</rt><rp>) </rp></ruby>」。其中「<ruby>じ<rp> (</rp><rt>ji</rt><rp>) </rp></ruby>、<ruby>ぢ<rp> (</rp><rt>ji</rt><rp>) </rp></ruby>」发音是相同的，都跟“鸡”发音相似。「<ruby>ず<rp> (</rp><rt>zu</rt><rp>) </rp></ruby>、<ruby>づ<rp> (</rp><rt>zu</rt><rp>) </rp></ruby>」发音是相同的，都跟“嗞”发音相似。其他的按照规则拼读就可以了。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;日语中跟五十音图中的清音对应的有 4 行浊音，1 行半浊音。&lt;/p&gt;
&lt;figure class=&quot;image-bubble&quot;&gt;
                &lt;div class=&quot;img-lightbox&quot;&gt;
                    &lt;div class=&quot;overlay&quot;&gt;&lt;/div&gt;
                    &lt;img src=&quot;/2017/09/14/my-japanese-learning-notes-3/dakuonntohanndakuonn.png&quot; alt=&quot;日语中的浊音和半浊音&quot; title&gt;
                &lt;/div&gt;
                &lt;div class=&quot;image-caption&quot;&gt;日语中的浊音和半浊音&lt;/div&gt;
            &lt;/figure&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>日语五十音图（辅音）</title>
    <link href="https://coolcode.org/2017/09/14/my-japanese-learning-notes-2/"/>
    <id>https://coolcode.org/2017/09/14/my-japanese-learning-notes-2/</id>
    <published>2017-09-14T03:06:00.000Z</published>
    <updated>2018-03-24T06:27:31.000Z</updated>
    
    <content type="html"><![CDATA[<p>日语五十音图中，除了第一行是元音行以外，其它的九行都是辅音行。虽然叫辅音行，但并不是说辅音行中的每一个音都是辅音，实际上，辅音行中的音是由辅音和元音拼读而成的组合音。五十音图中的辅音行的发音都是清音。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2017/09/14/my-japanese-learning-notes-2/gojyuonn.png" alt="日语五十音图" title>                </div>                <div class="image-caption">日语五十音图</div>            </figure><a id="more"></a><p>比如「か行」假名所表示的音是由清辅音 [k] 和「あ行」的 5 个元音拼成的。所以 9 个辅音行所对应的辅音实际上分别是 [k]，[s]，[t]，[n]，[h]，[m]，[y]，[r]，[w] 这 9 个辅音。</p><p>但这并不是说，这 9 行就只有这 9 个辅音，这 9 个辅音只是作为这 9 行的代表而已。实际上这 9 行一共对应了 14 个辅音，其中多出来的这 5 个辅音只跟某一行中某一个段的元音进行拼读。所以，在学习五十音的时候，你会发现辅音行中某些音的读法是特殊的。这几个音分别是：「<ruby>し<rp> (</rp><rt>shi</rt><rp>) </rp></ruby>」，「<ruby>ち<rp> (</rp><rt>chi</rt><rp>) </rp></ruby>」，「<ruby>つ<rp> (</rp><rt>tsu</rt><rp>) </rp></ruby>」，「<ruby>ひ<rp> (</rp><rt>hi</rt><rp>) </rp></ruby>」，「<ruby>ふ<rp> (</rp><rt>fu</rt><rp>) </rp></ruby>」，另外，还有一个「<ruby>す<rp> (</rp><rt>su</rt><rp>) </rp></ruby>」，它的发音也比较特别。除了这 6 个音的发音较特殊以外，其它的音直接按照汉语拼音的方式去拼读就可以了。</p><p>下面先对这几个特殊的音做一下总结。</p><h1 id="「」"><a href="#「」" class="headerlink" title="「」"></a>「<ruby>し<rp> (</rp><rt>shi</rt><rp>) </rp></ruby>」</h1><p>「し」在「さ行」的「い段」上，它的辅音 [sh] 的发音跟汉语中的 |x| 相似，所以「<ruby>し<rp> (</rp><rt>shi</rt><rp>) </rp></ruby>」的发音跟汉语中的 |xi| 是相似的。但是在发音时呼气较弱，形成的摩擦也较弱。</p><h1 id="「」-1"><a href="#「」-1" class="headerlink" title="「」"></a>「<ruby>す<rp> (</rp><rt>su</rt><rp>) </rp></ruby>」</h1><p>「す」在「さ行」的「う段」上，虽然「す」的罗马音写作 [su]，但发音却跟汉语中的 |su| 不同。因为日语中的 [u] 的发音是小口型的，嘴唇是不突出的，所以在拼读之后，发音介于汉语中的 |si| 和 |su| 之间，更接近于 |si| 的发音，但并不完全相同。</p><h1 id="「」-2"><a href="#「」-2" class="headerlink" title="「」"></a>「<ruby>ち<rp> (</rp><rt>chi</rt><rp>) </rp></ruby>」</h1><p>「ち」在「た行」的「い段」上，「ち」的辅音 [ch] 的发音跟汉语中的 |q| 相似，所以「<ruby>ち<rp> (</rp><rt>chi</rt><rp>) </rp></ruby>」的发音跟汉语中的 |qi| 是相似的。</p><h1 id="「」-3"><a href="#「」-3" class="headerlink" title="「」"></a>「<ruby>つ<rp> (</rp><rt>tsu</rt><rp>) </rp></ruby>」</h1><p>「つ」在「た行」的「う段」上，「つ」的辅音 [ts] 的发音跟汉语中的 |c| 相似，但是「<ruby>つ<rp> (</rp><rt>tsu</rt><rp>) </rp></ruby>」的发音却有着跟「<ruby>す<rp> (</rp><rt>su</rt><rp>) </rp></ruby>」一样的情况，它的发音跟汉语中的 |cu| 不同。它跟发音介于 |ci| 和 |cu| 之间，更接近于汉语中 |ci| 的音，但不完全相同。</p><h1 id="「」-4"><a href="#「」-4" class="headerlink" title="「」"></a>「<ruby>ひ<rp> (</rp><rt>hi</rt><rp>) </rp></ruby>」</h1><p>「ひ」在「は行」的「い段」上，它的罗马音虽然写作 [hi]，但是它的发音由于受到了元音 [i] 的影响，辅音 [h] 发生腭化，即舌面接近硬腭，所以听上去接近于汉语中【<ruby>嘿<rp> (</rp><rt>hēi</rt><rp>) </rp></ruby>】和【<ruby>西<rp> (</rp><rt>xī</rt><rp>) </rp></ruby>】之间的一个发音，这个音在汉语中找不到对应的字，所以，我也没办法用汉语来准确描述，只能多听听录音，跟着练习了。</p><h1 id="「」-5"><a href="#「」-5" class="headerlink" title="「」"></a>「<ruby>ふ<rp> (</rp><rt>fu</rt><rp>) </rp></ruby>」</h1><p>「ふ」也在「は行」的「う段」上，它的辅音 [f] 是一个吹气音，而不是一个咬唇音，所以它的「<ruby>ふ<rp> (</rp><rt>fu</rt><rp>) </rp></ruby>」的发音介于汉语的【<ruby>夫<rp> (</rp><rt>fū</rt><rp>) </rp></ruby>】和【<ruby>呼<rp> (</rp><rt>hū</rt><rp>) </rp></ruby>】之间。</p><p>除了这 6 个特殊音以外，五十音图的最后三行也有比较特殊的地方需要单独说明一下。</p><h1 id="「や行」"><a href="#「や行」" class="headerlink" title="「や行」"></a>「や行」</h1><p>「や行」只有三个音，分别在「あ段」、「う段」和「お段」上，至于「い段」和「え段」，对应的仍然是「い」和「え」这两个音。「や行」中的辅音 [y] 和后面 「わ行」中的辅音 [w]，由于摩擦十分微弱，性质更接近元音，所以也被称为半元音。但在音节上，日语的半元音所占的时长很短，从半元音到元音的过度是在瞬间完成的。简单的说，就是在发音时，半元音跟元音一样，只占一个音节而不是两个音节的时长。在后面拗音的学习中，我们会发现，拗音实际上就是由除「や行」和「わ行」以外的行中「い段」上对应的辅音跟这三个音拼读而成的。</p><h1 id="「ら行」"><a href="#「ら行」" class="headerlink" title="「ら行」"></a>「ら行」</h1><p>「ら行」中，[r] 这个辅音虽然看上去像汉语拼音中的 |r|，但是发音却跟汉语拼音中的 |l| 相似。因为日语中没有卷舌音，所以 「<ruby>ら<rp> (</rp><rt>ra</rt><rp>) </rp></ruby>」、「<ruby>り<rp> (</rp><rt>ri</rt><rp>) </rp></ruby>」、「<ruby>る<rp> (</rp><rt>ru</rt><rp>) </rp></ruby>」、「<ruby>れ<rp> (</rp><rt>re</rt><rp>) </rp></ruby>」、「<ruby>ろ<rp> (</rp><rt>ro</rt><rp>) </rp></ruby>」 的发音听上去跟汉语的【<ruby>啦<rp> (</rp><rt>lā</rt><rp>) </rp></ruby>】、【<ruby>哩<rp> (</rp><rt>li</rt><rp>) </rp></ruby>】、【<ruby>噜<rp> (</rp><rt>lū</rt><rp>) </rp></ruby>】、【<ruby>唻<rp> (</rp><rt>lài</rt><rp>) </rp></ruby>】、【<ruby>咯<rp> (</rp><rt>lo</rt><rp>) </rp></ruby>】几乎是一样的。</p><h1 id="「わ行」"><a href="#「わ行」" class="headerlink" title="「わ行」"></a>「わ行」</h1><p>「わ行」也比较特殊，只有 2 个音，分别在「あ段」和「お段」上，而「い段」和「え段」原本是有两个假名「<ruby>ゐ<rp> (</rp><rt>wi</rt><rp>) </rp></ruby>」和「<ruby>ゑ<rp> (</rp><rt>we</rt><rp>) </rp></ruby>」的，可是它们的读音后来同化成了「い」和「え」，所以在现代日语中被取消了。这两个音只有在古日语中才能见到了，比如下面这一首和歌：</p>    <div id="cplayer-27672519"></div>    <script>      (function(){        function loadcplayer() {          if (typeof window.cplayerList === 'undefined') window.cplayerList = {};          if (typeof window.cplayerList["cplayer-27672519"] !== 'undefined') return;          if (!cplayer.prototype.add163) cplayer.prototype.add163 = function add163(id) {            if (!id) throw new Error("Unable Property.");            return fetch("https://music.huaji8.top/?id=" + id).then(function(res){return res.json()}).then(function(data){              let obj = {                name: data.info.songs[0].name,                artist: data.info.songs[0].ar.map(function(ar){ return ar.name }).join(','),                poster: data.pic.url,                lyric: data.lyric.lyric,                sublyric: data.lyric.tlyric,                src: data.url.url              }              this.add(obj);              return obj;            }.bind(this))          }          window.cplayerList["cplayer-27672519"] = new cplayer({            element: document.getElementById("cplayer-27672519"),            playlist: [{"name":"《百人一首》第17篇","artist":"在原業平朝臣","poster":"chihayaburu.jpeg","src":"chihayaburu.mp3"}],            generateBeforeElement: true,            deleteElementAfterGenerate: true,            autoplay: false          });                  }                if (typeof window.cplayer === 'undefined' && !document.getElementById("cplayer-script")) {          var js = document.createElement("script");          js.src = 'https://cdn.jsdelivr.net/gh/MoePlayer/cPlayer/dist/cplayer.js';          js.id = "cplayer-script";          js.addEventListener("load", loadcplayer);          document.body.appendChild(js);        } else {          window.addEventListener("load", loadcplayer);        }      })()    </script><blockquote><p><ruby lang="ja">千早振<rp> (</rp><rt>ちはやぶ</rt><rp>) </rp></ruby>る<br><ruby lang="ja">神代<rp> (</rp><rt>かみよ</rt><rp>) </rp></ruby>も<ruby lang="ja">聞<rp> (</rp><rt>き</rt><rp>) </rp></ruby>かず<br><ruby lang="ja">竜田川<rp> (</rp><rt>たつたがわ</rt><rp>) </rp></ruby><br>からくれな<strong><ruby lang="ja">ゐ<rp> (</rp><rt>い</rt><rp>) </rp></ruby></strong>に<br><ruby lang="ja">水<rp> (</rp><rt>みず</rt><rp>) </rp></ruby>くくるとは</p><footer><strong><ruby lang="ja">在原業平朝臣<rp> (</rp><rt>ありわらのなりひらあそん</rt><rp>) </rp></ruby></strong><cite>《<ruby lang="ja">百人一首<rp> (</rp><rt>ひゃくにんいっしゅ</rt><rp>) </rp></ruby>》</cite></footer></blockquote><p>另外，「<ruby>を<rp> (</rp><rt>wo</rt><rp>) </rp></ruby>」原本的读音是 [wo]，它在现代日语中同化成了「<ruby>お<rp> (</rp><rt>o</rt><rp>) </rp></ruby>」的发音。「<ruby>を<rp> (</rp><rt>wo</rt><rp>) </rp></ruby>」在现代汉语中不用于单词拼写，只用来做格助词使用。所以，通常你是看不到它的片假名「<ruby>ヲ<rp> (</rp><rt>wo</rt><rp>) </rp></ruby>」出现在句子中的。除非一个句子表示强调，全部用片假名书写时，你才能看见这个罕见的「<ruby>ヲ<rp> (</rp><rt>wo</rt><rp>) </rp></ruby>」。</p><h1 id="「は」和「へ」"><a href="#「は」和「へ」" class="headerlink" title="「は」和「へ」"></a>「は」和「へ」</h1><p>最后再补充一点，「は」和「へ」这两个假名在作为助词使用时，读音会发生变化，其中「は」会读作 [wa]，跟「わ」的发音相同。而「へ」会读作 [e]，跟「え」的读音相同。但是如果在用罗马音输入法打字输入的时候，仍然是使用 [ha] 和 [he] 来输入。至于为什么是这样子的，简单的说，这被叫做「は」行转呼，它是语言发展中的一个历史遗留问题，要详细的说，这个篇幅就长了，这里就不展开了，上网搜索一下助词「は・へ」为何读作「わ・え」就能查到了。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;日语五十音图中，除了第一行是元音行以外，其它的九行都是辅音行。虽然叫辅音行，但并不是说辅音行中的每一个音都是辅音，实际上，辅音行中的音是由辅音和元音拼读而成的组合音。五十音图中的辅音行的发音都是清音。&lt;/p&gt;
&lt;figure class=&quot;image-bubble&quot;&gt;
                &lt;div class=&quot;img-lightbox&quot;&gt;
                    &lt;div class=&quot;overlay&quot;&gt;&lt;/div&gt;
                    &lt;img src=&quot;/2017/09/14/my-japanese-learning-notes-2/gojyuonn.png&quot; alt=&quot;日语五十音图&quot; title&gt;
                &lt;/div&gt;
                &lt;div class=&quot;image-caption&quot;&gt;日语五十音图&lt;/div&gt;
            &lt;/figure&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>日语五十音图（元音）</title>
    <link href="https://coolcode.org/2017/09/12/my-japanese-learning-notes-1/"/>
    <id>https://coolcode.org/2017/09/12/my-japanese-learning-notes-1/</id>
    <published>2017-09-12T18:39:00.000Z</published>
    <updated>2018-03-24T06:27:31.000Z</updated>
    
    <content type="html"><![CDATA[<p>《综合日语》开篇就讲五十音图，没有什么日语发展史，日语文字构成之类的介绍，简单粗暴很实用。本篇学习笔记，当然也以实用为主，所以同样略过这些内容。对这些内容感兴趣的同学，直接上网找各种日语入门的视频教程看就好了，网上各种的日语公开课里面数这部分内容介绍的最多最详细了。下面直接进入正题。</p><a id="more"></a><h1 id="日语发音很简单"><a href="#日语发音很简单" class="headerlink" title="日语发音很简单"></a>日语发音很简单</h1><p>汉语、英语、法语、韩语、日语的发音我都学过（但不代表都学会了），在这些语言中，日语的发音其实是最简单最好学的。</p><p>为什么说日语的发音最简单呢？因为日语的元音最少，只有 5 个，而且全是单元音，或者说压根儿就没有单元音，双元音之类的概念。而上面列举的其它几种语言相对来说就没有这么简单了，比如汉语普通话的发音不是按照元音和辅音来划分的，而是分为声母和韵母，其中韵母（相当于元音，虽然有些区别）就有 39 个，还分为单韵母，复韵母，鼻韵母等，其中有一些音对于普通话不好的中国人来说都分不清楚。英语英标相对简单一些，但是也有 20 个元音（美式英语有 18 个元音），而且还有长短单双之分。法语音标元音有 16 个，看上去英语音标还少一些，但是法语的 16 个元音都是单元音（也就是发音时口型是不变的），有几个元音的发音听起来太相近了，连法国人自己也分不清，以至于现在有几个元音都合并了，变成了 14 或 15 个，但这个数量相对于日语元音仍然是 3 倍的数量。韩语元音有 21 个，单元音 10 个，双元音 11 个，其中有几个音对于韩语初学者来说听起来也是傻傻的分不清楚。而日语的 5 个元音发音不但简单，而且区分明显，不说听一遍就能学会正确的发音方式吧，但是至少听出哪个音是没问题的。</p><h1 id="五十音图的划分"><a href="#五十音图的划分" class="headerlink" title="五十音图的划分"></a>五十音图的划分</h1><p>日语五十音图可以被划分成 10 行 5 段，其中第一行就是这五个元音，它们分别是：「あ」「い」「う」「え」「お」。每一行都用该行第一个假名来命名，比如第一行就叫做「あ行」。而每一段，都用该段的第一个假名来命名，所以五段分别为：「あ段」，「い段」，「う段」，「え段」，「お段」。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2017/09/12/my-japanese-learning-notes-1/gojyuonn.png" alt="日语五十音图" title>                </div>                <div class="image-caption">日语五十音图</div>            </figure><h1 id="あ段元音"><a href="#あ段元音" class="headerlink" title="あ段元音"></a>あ段元音</h1><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">「あ」的发音跟汉语的 |a| 相似。它的罗马音写作 [a]。</span><br><span class="line">「い」的发音跟汉语的 |i| 相似。它的罗马音写作 [i]。</span><br><span class="line">「う」的发音跟汉语的 |u| 相似。它的罗马音写作 [u]。</span><br><span class="line">「え」的发音跟汉语的 |ai| 相似。它的罗马音写作 [e]。</span><br><span class="line">「お」的发音跟汉语的 |ao| 相似。它的罗马音写作 [o]。</span><br></pre></td></tr></table></figure><h1 id="口型不变"><a href="#口型不变" class="headerlink" title="口型不变"></a>口型不变</h1><p>日语的元音都是单元音，发音从开始到结束口型是始终不变的，所以「え」和「お」这两个音不会像汉语中的复韵母 |ai| 和 |ao| 一样从一个元音滑向另一个元音。它们更像法语的 [&#603;] 和 [&#596;]。对于不懂法语的同学，可以想象一下汉语中发出惊讶之音的【<ruby>哎<rp> (</rp><rt>āi</rt><rp>) </rp></ruby>】字和【<ruby>噢<rp> (</rp><rt>ō</rt><rp>) </rp></ruby>】字的短促发音就好了。虽然【<ruby>噢<rp> (</rp><rt>ō</rt><rp>) </rp></ruby>】的拼音写作 |o|，但是你会发现它跟【<ruby>波<rp> (</rp><rt>bō</rt><rp>) </rp></ruby>】里面的 |o| 的发音还是不一样的，它更像是 |ao| 的发音，只不过没有过度音罢了。</p><h1 id="口型小"><a href="#口型小" class="headerlink" title="口型小"></a>口型小</h1><p>日语的每个元音的发音口型都很小，不像汉语，英语的发音那样夸张。所以，上面说的发音相似只是听上去相似，而不是说发音方式相同。你几乎可以用同一个不变的小口型加上不同的舌位来发出这五个音来。</p><h1 id="发音短促"><a href="#发音短促" class="headerlink" title="发音短促"></a>发音短促</h1><p>日语的元音都是很短促的，不像汉语拼音一样可以随便拖长，日语发音如果拖长了就变成了长音，而长音有自己特别的表示方式，今天暂且不表。</p><p>关于日语假名的写法，这里就不做总结了。见得多了自然就认识了，练的多了自然就会写了。下一篇将总结辅音和辅音行的发音。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;《综合日语》开篇就讲五十音图，没有什么日语发展史，日语文字构成之类的介绍，简单粗暴很实用。本篇学习笔记，当然也以实用为主，所以同样略过这些内容。对这些内容感兴趣的同学，直接上网找各种日语入门的视频教程看就好了，网上各种的日语公开课里面数这部分内容介绍的最多最详细了。下面直接进入正题。&lt;/p&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>我的日语学习笔记</title>
    <link href="https://coolcode.org/2017/09/12/my-japanese-learning-notes/"/>
    <id>https://coolcode.org/2017/09/12/my-japanese-learning-notes/</id>
    <published>2017-09-12T11:21:00.000Z</published>
    <updated>2018-03-24T06:27:31.000Z</updated>
    
    <content type="html"><![CDATA[<p>从开始系统学习日语到现在已经有半年了，《综合日语》四本书已经学完了第一册，《综合日语》第二册也学了有五分之一了，但是发现学得多了，之前学的知识，有些就忘了，有些就记混了。所以打算从现在起，把之前学过的内容，结合自己看过的各种书籍和查找的各种资料加上一些自己理解的内容，从头开始整理成笔记，希望可以通过这种方式来提高自己学习日语的效率。</p><a id="more"></a><h1 id="我的日语教材"><a href="#我的日语教材" class="headerlink" title="我的日语教材"></a>我的日语教材</h1><p>课本我们用的是《综合日语》系列教材修订版，共有四册，其中，第一、二册对应的是日语<ruby>初级<rp> (</rp><rt>N5-N4</rt><rp>) </rp></ruby>，第三，四册对应的是日语<ruby>中级<rp> (</rp><rt>N3-N2</rt><rp>) </rp></ruby>。另外还有两册《高年级综合日语》，对应的是日语<ruby>高级<rp> (</rp><rt>N1</rt><rp>) </rp></ruby>。不过我报的课程只包括四册《综合日语》的学习，不包含《高年级综合日语》的学习，所以到后面就只能自学了。这套教材有配套的录音和练习册，不过录音有些地方跟修订版的内容有些出入，可能是没更新的原因，不过这不是什么大问题，基本上没有影响。另外，负责编写这套书的何琳老师还开了一个<a href="https://mp.weixin.qq.com/mp/profile_ext?action=home&amp;__biz=MzI0NzQ2ODEwOQ==#wechat_redirect" target="_blank" rel="noopener">微信公众号</a>和微信学习群，想要加入去微信里搜索综合日语就行了，目前在公众号里有何琳老师亲自讲解录制的关于这套书前两册的视频资料，而且公众号里的内容每天都有更新，对于自学者来说非常有用。</p><h1 id="我的日语课外书"><a href="#我的日语课外书" class="headerlink" title="我的日语课外书"></a>我的日语课外书</h1><p>除了这套课本，我还买了一些日语学习的书籍，下面列一下自己的书单：</p><blockquote><p>《别笑！我是日语学习书》<br>《别笑！我是日语语法书》<br>《别笑！我是日语会话书》<br>《别笑！我是日语写作书》<br>《别笑！我是日语单词书》<br>《WoW！日语单词还能这么学！》<br>《超奇迹 分类记 15000日语单词》<br>《初级日语语法精解》<br>《中高级日语语法精解》<br>《超简单 手绘日语动词》<br>《彩绘日语常用拟声、拟态词》<br>《彩绘日语助词》<br>《从日本中小学课本学日文》<br>《从日本人的生活秘密学日语》<br>《不出国，跟日本中小学生一起上课学口语》<br>《跟着日本语文课本学日语》<br>《小心日语汉字有陷阱》<br>《一辈子够用的日语口语大全》<br>《一辈子够用的日语日记表现大全》<br>《每天读一点日文——日本民间故事大全集》<br>……</p></blockquote><p>还有一些就不一一列出了。别看买了这么多，真正看过的没几本（随便翻一翻不算看的话），看完的一本都没有。因为以我买书时的日语水平（大概也就是刚过五十音吧）来说，大部分书都太难了。也许等我把《综合日语》前两册都学完之后，再看这些书可能会比较容易一些吧。希望到时候能看完其中的几本。</p><h1 id="我的日语词典及日语学习资源"><a href="#我的日语词典及日语学习资源" class="headerlink" title="我的日语词典及日语学习资源"></a>我的日语词典及日语学习资源</h1><p>学日语当然还需要一本词典，我很早买过一本《外研社日汉双解学习词典》，不过没翻过几次，因为太厚重了，查起来实在是不太方便。电子辞典虽然很方便，但是<ruby>卡西欧<rp> (</rp><rt>CASIO</rt><rp>) </rp></ruby>太贵了，要 2000~3000 多块钱，我上面那一堆书加在一起也不过 500 块钱而已，所以实在舍不得买这种东西。后来发现日版的 NDSiLL 自带一个《<ruby>明镜国语辞典<rp> (</rp><rt><span lang="ja">明鏡国語辞典</span></rt><rp>) </rp></ruby>》的软件，作为日语电子辞典用很不错，而且目前二手的 NDSiLL 只有 200 ~ 300 块钱，比<ruby>卡西欧<rp> (</rp><rt>CASIO</rt><rp>) </rp></ruby>要便宜 10 倍，而比 NDSiLL 小一点的 NDSi 更便宜，只有 100 ~ 200 块钱左右，虽然 NDSi 不自带《<ruby>明镜国语辞典<rp> (</rp><rt><span lang="ja">明鏡国語辞典</span></rt><rp>) </rp></ruby>》，但是可以运行的电子辞典软件也有两款，分别是《<ruby>{DS乐引辞典<rp> (</rp><rt><span lang="ja">DS楽引辞典</span></rt><rp>) </rp></ruby>》和《<ruby>汉字速查DS乐引辞典<rp> (</rp><rt><span lang="ja">漢字そのままDS楽引辞典</span></rt><rp>) </rp></ruby>》，后面这一款《<ruby>汉字速查DS乐引辞典<rp> (</rp><rt><span lang="ja">漢字そのままDS楽引辞典</span></rt><rp>) </rp></ruby>》包含了《<ruby>明镜国语辞典<rp> (</rp><rt><span lang="ja">明鏡国語辞典</span></rt><rp>) </rp></ruby>》和《ジーニアス英和和英辞典》，可以直接手写汉字来查词，感觉比较好用。</p><div lang="ja"><br><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2017/09/12/my-japanese-learning-notes/DS_Rakubiki_Jiten.png" alt="《DS楽引辞典》" title>                </div>                <div class="image-caption">《DS楽引辞典》</div>            </figure><br><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="/2017/09/12/my-japanese-learning-notes/Kanji_Sonomama_DS_Rakubiki_Jiten.png" alt="《漢字そのままDS楽引辞典》" title>                </div>                <div class="image-caption">《漢字そのままDS楽引辞典》</div>            </figure><br></div><p>NDSi/NDSiLL 除了电子辞典以外，还有许多日语学习、日语能力测试、日文电子书软件，例如：</p><blockquote><p>《优美的日语书写及说话方法》<br>《用笔触说你好 笔谈君》<br>《日语鉴定 DS》<br>《学研DS 成人的学习 金田一老师的日本语课程》<br>《难读汉字 DS 难读字 四字成语 谚语》<br>《问题日语校正》<br>《画圆记忆法 津川式汉字记忆术》<br>《似懂非懂的汉字联系DS》<br>《一口气读完的日本文学100篇》<br>《图书馆DS 名作&amp;推理&amp;怪谈&amp;文学》<br>《大家来读书DS 源氏物语+小品文学》<br>《大家来读书DS 捕物帐》<br>《100部经典名著合集》<br>《DS文学全集》<br>……</p></blockquote><p>不过这里面大部分软件都需要用户有一定的日语基础，否则看也看不懂。</p><p>除了专门的电子辞典以外，下面这几个日文辞典网站也不错：</p><blockquote><p><a href="http://dictionary.infoseek.ne.jp/" target="_blank" rel="noopener">http://dictionary.infoseek.ne.jp/</a><br><a href="https://dictionary.goo.ne.jp/" target="_blank" rel="noopener">https://dictionary.goo.ne.jp/</a><br><a href="http://www.weblio.jp/" target="_blank" rel="noopener">http://www.weblio.jp/</a><br><a href="https://www.sanseido.biz/" target="_blank" rel="noopener">https://www.sanseido.biz/</a><br><a href="http://www.gavo.t.u-tokyo.ac.jp/ojad/" target="_blank" rel="noopener">http://www.gavo.t.u-tokyo.ac.jp/ojad/</a></p></blockquote><p>最后这个 OJAD 的词典网站有点特别，它是专门用来查日语声调的。</p><p>我还装了一个在电脑上可以使用的离线词典软件——<ruby>灵格斯<rp> (</rp><rt>Lingoes</rt><rp>) </rp></ruby>，它有一些配套的日文辞典，安装上就可以直接用了。</p><p>手机的话，我装了个沪江小D词典，感觉也挺好用的。我手机上还装了几个感觉比较好用的日语学习软件：</p><blockquote><p>沪江开心词场<br>词道（日语版）<br>忆术家<br>日语语法酷<br>沪江听力酷<br>CCtalk<br>沪江学习</p></blockquote><p>这些都是免费的，不过其中某些软件里面的资源有一部分是收费的，不过说实话，这里面的免费的资源我都用不过来了。</p><p>哔哩哔哩上还有许多日语学习的初级和中级课程的视频，比如：</p><blockquote><p><a href="https://www.bilibili.com/video/av3060477" target="_blank" rel="noopener">【日语课程】标日初级精讲BY萌萌哒葉子先生</a><br><a href="https://www.bilibili.com/video/av4159781" target="_blank" rel="noopener">【日语入门初级课程合集】-新编中日交流标准日本语</a><br><a href="https://www.bilibili.com/video/av20515657" target="_blank" rel="noopener">新版标准日本语初级上册（全）</a><br><a href="https://www.bilibili.com/video/av20738828" target="_blank" rel="noopener">新标准日本语 初级下册 25-26课</a><br><a href="https://www.bilibili.com/video/av20769497" target="_blank" rel="noopener">新标准日本语 初级下册 27课</a><br><a href="https://www.bilibili.com/video/av2044726" target="_blank" rel="noopener">【日语学习】新版标准日本语初级（上）：第一单元</a><br><a href="https://www.bilibili.com/video/av2050498" target="_blank" rel="noopener">【日语学习】新版标准日本语初级（上）：第二单元</a><br><a href="https://www.bilibili.com/video/av2093709" target="_blank" rel="noopener">【日语学习】新版标准日本语初级（上）：第三单元</a><br><a href="https://www.bilibili.com/video/av2140110" target="_blank" rel="noopener">【日语学习】新版标准日本语初级（上）：第四单元</a><br><a href="https://www.bilibili.com/video/av2234593" target="_blank" rel="noopener">【日语学习】新版标准日本语初级（上）：第五单元</a><br><a href="https://www.bilibili.com/video/av2774966" target="_blank" rel="noopener">【日语学习】新版标准日本语初级（上）：第六单元</a><br><a href="https://www.bilibili.com/video/av2831450" target="_blank" rel="noopener">【日语学习】新版标准日本语初级（下）：第七单元</a><br><a href="https://www.bilibili.com/video/av2831539" target="_blank" rel="noopener">【日语学习】新版标准日本语初级（下）：第八单元</a><br><a href="https://www.bilibili.com/video/av2831548" target="_blank" rel="noopener">【日语学习】新版标准日本语初级（下）：第九单元</a><br><a href="https://www.bilibili.com/video/av2832768" target="_blank" rel="noopener">【日语学习】新版标准日本语初级（下）：第十单元</a><br><a href="https://www.bilibili.com/video/av2832870" target="_blank" rel="noopener">【日语学习】新版标准日本语初级（下）：第十一单元</a><br><a href="https://www.bilibili.com/video/av2832871" target="_blank" rel="noopener">【日语学习】新版标准日本语初级（下）：第十二单元</a><br><a href="https://www.bilibili.com/video/av4471049" target="_blank" rel="noopener">新标日中级上 (1)</a><br><a href="https://www.bilibili.com/video/av4478225" target="_blank" rel="noopener">新标日中级上 (2)</a><br><a href="https://www.bilibili.com/video/av4530545" target="_blank" rel="noopener">新标日中级下 (1)</a><br><a href="https://www.bilibili.com/video/av4531290" target="_blank" rel="noopener">新标日中级下 (2)</a><br><a href="https://www.bilibili.com/video/av2934342" target="_blank" rel="noopener">【日语学习】N2考级辅导</a><br><a href="https://www.bilibili.com/video/av2995976" target="_blank" rel="noopener">【日语学习】N1考级辅导</a></p></blockquote><p>不知道什么时候才能看完。</p><p>今天先总结到这里吧，下次的笔记就从五十音开始吧，希望自己能够一直坚持下去。</p><blockquote><p>这是去年我首发在简书上的日语学习笔记专题《现学现卖学日语》，当时我的博客还没有改版，不支持使用 Markdown 格式，现在我把博客系统换成了 <a href="//hexo.io">Hexo</a>，不但可以使用 Markdown 排版了，而且还专门为中日文混合排版优化了主题，所以终于可以转载回来了。为了让内容有更好的显示效果，内容和排版上可能都会有所修改。</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;从开始系统学习日语到现在已经有半年了，《综合日语》四本书已经学完了第一册，《综合日语》第二册也学了有五分之一了，但是发现学得多了，之前学的知识，有些就忘了，有些就记混了。所以打算从现在起，把之前学过的内容，结合自己看过的各种书籍和查找的各种资料加上一些自己理解的内容，从头开始整理成笔记，希望可以通过这种方式来提高自己学习日语的效率。&lt;/p&gt;
    
    </summary>
    
      <category term="日语" scheme="https://coolcode.org/categories/%E6%97%A5%E8%AF%AD/"/>
    
    
      <category term="日语" scheme="https://coolcode.org/tags/%E6%97%A5%E8%AF%AD/"/>
    
  </entry>
  
  <entry>
    <title>秒杀 tj/co 的 hprose 协程库</title>
    <link href="https://coolcode.org/2016/11/22/hprose-coroutine-library-for-javascript/"/>
    <id>https://coolcode.org/2016/11/22/hprose-coroutine-library-for-javascript/</id>
    <published>2016-11-22T16:00:00.000Z</published>
    <updated>2018-03-14T04:51:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>ES6 中引入了 Generator，Generator 通过封装之后，可以作为协程来进行使用。</p><p>其中对 Generator 封装最为著名的当属 tj/co，但是 tj/co 跟 ES2016 的 async/await 相比的话，还存在一些比较严重的缺陷。</p><p>hprose 中也引入了对 Generator 封装的协程支持，但是比 tj/co 更加完善，下面我们就来详细介绍一下它们之间的差别。</p><a id="more"></a><h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p><a href="https://github.com/tj/co" target="_blank" rel="noopener"><code>tj/co</code></a> 有以下几个方面的问题：</p><p>首先，<code>tj/co</code> 库中的 <code>yield</code> 只支持 thunk 函数，生成器函数，promise 对象，以及数组和对象，但是不支持普通的基本类型的数据，比如 <code>null</code>, 数字，字符串等都不支持。这对于 <code>yield</code> 一个类型不确定的变量来说，是很不方便的。而且这跟 <code>await</code> 也是不兼容的。</p><p>其次，在 <code>yield</code> 数组和对象时，<code>tj/co</code> 库会自动对数组中的元素和对象中的字段递归的遍历，将其中的所有的 <code>Promise</code> 元素和字段替换为实际值，这对于简单的数据来说，会方便一些。但是对于带有循环引用的数组和对象来说，会导致无法获取到结果，这是一个致命的问题。即使对于不带有循环引用结构的数组和对象来说，如果该数组和对象比较复杂，这也会消耗大量的时间。而且这跟 <code>await</code> 也是不兼容的。</p><p>再次，对于 thunk 函数，<code>tj/co</code> 库会认为回调函数第一个参数必须是表示错误，从第二个参数开始才表示返回值。而这对于回调函数只有一个返回值参数的函数，或者回调函数的第一个参数不表示错误的函数来说，<code>tj/co</code> 库就无法使用了。</p><p>而 <code>hprose.co</code> 对 <code>yield</code> 的支持则跟 <code>await</code> 完全兼容，支持对所有类型的数据进行 <code>yield</code>。</p><p>当 <code>hprose.co</code> 对 chunk 函数进行 <code>yield</code> 时，如果回调函数第一个参数是 <code>Error</code> 类型的对象才会被当做错误处理。如果回调函数只有一个参数且不是 <code>Error</code> 类型的对象，则作为返回值对待。如果回调函数有两个以上的参数，如果第一个参数为 <code>null</code> 或 <code>undefined</code>，则第一个参数被当做无错误被忽略，否则，全部回调参数都被当做返回值对待。如果被当做返回值的回调参数有多个，则这多个参数被当做数组结果对待，如果只有一个，则该参数被直接当做返回值对待。</p><p>下面我们来举例说明一下：</p><h1 id="yield-基本类型"><a href="#yield-基本类型" class="headerlink" title="yield 基本类型"></a>yield 基本类型</h1><p>首先我们来看一下 <code>tj/co</code> 库的例子：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> co = <span class="built_in">require</span>(<span class="string">'co'</span>);</span><br><span class="line"></span><br><span class="line">co(<span class="function"><span class="keyword">function</span>*(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">yield</span> <span class="built_in">Promise</span>.resolve(<span class="string">"promise"</span>));</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">yield</span> <span class="function"><span class="keyword">function</span> *(<span class="params"></span>) </span>&#123; <span class="keyword">return</span> <span class="string">"generator"</span> &#125;);</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">yield</span> <span class="keyword">new</span> <span class="built_in">Date</span>());</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">yield</span> <span class="number">123</span>);</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">yield</span> <span class="number">3.14</span>);</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">yield</span> <span class="string">"hello"</span>);</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">yield</span> <span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">        <span class="built_in">console</span>.error(e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>该程序运行结果为：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">promise</span><br><span class="line">generator</span><br><span class="line">TypeError: You may only yield a function, promise, generator, array, or object, but the following object was passed: &quot;Sat Nov 19 2016 14:51:09 GMT+0800 (CST)&quot;</span><br><span class="line">    at next (/usr/local/lib/node_modules/co/index.js:101:25)</span><br><span class="line">    at onFulfilled (/usr/local/lib/node_modules/co/index.js:69:7)</span><br><span class="line">    at process._tickCallback (internal/process/next_tick.js:103:7)</span><br><span class="line">    at Module.runMain (module.js:577:11)</span><br><span class="line">    at run (bootstrap_node.js:352:7)</span><br><span class="line">    at startup (bootstrap_node.js:144:9)</span><br><span class="line">    at bootstrap_node.js:467:3</span><br></pre></td></tr></table></figure><p>其实除了前两个，后面的几个基本类型的数据都不能被 <code>yield</code>。如果我们把上面代码的第一句改为：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> co = <span class="built_in">require</span>(<span class="string">'hprose'</span>).co;</span><br></pre></td></tr></table></figure><p>后面的代码都不需要修改，我们来看看运行结果：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">promise</span><br><span class="line">generator</span><br><span class="line">2016-11-19T06:54:30.081Z</span><br><span class="line">123</span><br><span class="line">3.14</span><br><span class="line">hello</span><br><span class="line">true</span><br></pre></td></tr></table></figure><p>也就是说，<code>hprose.co</code> 支持对所有类型进行 <code>yield</code> 操作。下面我们再来看看 async/await 是什么效果：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">(<span class="keyword">async</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">await</span> <span class="built_in">Promise</span>.resolve(<span class="string">"promise"</span>));</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">await</span> <span class="function"><span class="keyword">function</span> *(<span class="params"></span>) </span>&#123; <span class="keyword">return</span> <span class="string">"generator"</span> &#125;);</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">await</span> <span class="keyword">new</span> <span class="built_in">Date</span>());</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">await</span> <span class="number">123</span>);</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">await</span> <span class="number">3.14</span>);</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">await</span> <span class="string">"hello"</span>);</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">await</span> <span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">        <span class="built_in">console</span>.error(e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><p>上面的代码基本上就是把 <code>co(function*...)</code> 替换成了 <code>async function...</code>，把 <code>yield</code> 替换成了 <code>await</code>。</p><p>我们来运行上面的程序，注意，对于当前版本的 node 运行时需要加上 <code>--harmony_async_await</code> 参数，运行结果如下：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">promise</span><br><span class="line">[Function]</span><br><span class="line">2016-11-19T08:16:25.316Z</span><br><span class="line">123</span><br><span class="line">3.14</span><br><span class="line">hello</span><br><span class="line">true</span><br></pre></td></tr></table></figure><p>我们可以看出，<code>await</code> 和 <code>hprose.co</code> 除了对生成器的处理不同以外，其它的都相同。对于生成器函数，<code>await</code> 是按原样返回的，而 <code>hprose.co</code> 则是按照 <code>tj/co</code> 的方式处理。也就是说 <code>hprose.co</code> 综合了 <code>await</code> 和 <code>tj/co</code> 的全部优点。使用 <code>hprose.co</code> 比使用 <code>await</code> 或 <code>tj/co</code> 都方便。</p><h1 id="yield-数组或对象"><a href="#yield-数组或对象" class="headerlink" title="yield 数组或对象"></a>yield 数组或对象</h1><p>我们来看第二个让 <code>tj/co</code> 崩溃的例子：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> co = <span class="built_in">require</span>(<span class="string">'co'</span>);</span><br><span class="line"></span><br><span class="line">co(<span class="function"><span class="keyword">function</span>*(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">var</span> a = [];</span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; <span class="number">1000000</span>; i++) &#123;</span><br><span class="line">            a[i] = i;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">var</span> start = <span class="built_in">Date</span>.now();;</span><br><span class="line">        <span class="keyword">yield</span> a;</span><br><span class="line">        <span class="keyword">var</span> end = <span class="built_in">Date</span>.now();;</span><br><span class="line">        <span class="built_in">console</span>.log(end - start);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">        <span class="built_in">console</span>.error(e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">co(<span class="function"><span class="keyword">function</span>*(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">var</span> a = [];</span><br><span class="line">        a[<span class="number">0</span>] = a;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">yield</span> a);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">        <span class="built_in">console</span>.error(e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">co(<span class="function"><span class="keyword">function</span>*(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">var</span> o = &#123;&#125;;</span><br><span class="line">        o.self = o;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">yield</span> o);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">        <span class="built_in">console</span>.error(e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>运行该程序，我们会看到程序会卡一会儿，然后出现下面的结果：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">2530</span><br><span class="line">(node:70754) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): RangeError: Maximum call stack size exceeded</span><br><span class="line">(node:70754) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.</span><br><span class="line">(node:70754) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): RangeError: Maximum call stack size exceeded</span><br></pre></td></tr></table></figure><p>上面的 <code>2530</code> 是第一个 <code>co</code> 程序段输出的结果，也就是说这个 <code>yield</code> 要等待 2.5 秒才能返回结果。而后面两个 <code>co</code> 程序段则直接调用栈溢出了。如果在实际应用中，出现了这样的数据，使用 <code>tj/co</code> 你的程序就会变得很慢，或者直接崩溃了。</p><p>下面看看 <code>hprose.co</code> 的效果，同样只替换第一句话为：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> co = <span class="built_in">require</span>(<span class="string">'hprose'</span>).co;</span><br></pre></td></tr></table></figure><p>后面的代码都不需要修改，我们来看看运行结果：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">7</span><br><span class="line">[ [Circular] ]</span><br><span class="line">&#123; self: [Circular] &#125;</span><br></pre></td></tr></table></figure><p>第一个 <code>co</code> 程序段用时很短，只需要 <code>7</code> ms。注意，这还是包含了后面两个程序段的时间，因为这三个协程是并发的，如果去掉后面两个程序段，你看的输出可能是 <code>1</code> ms 或者 <code>0</code> ms。而后面两个程序段也完美的返回了带有循环引用的数据。这才是我们期望的结果。</p><p>我们再来看看 <code>async/await</code> 下是什么效果，程序代码如下：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">(<span class="keyword">async</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">var</span> a = [];</span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; <span class="number">1000000</span>; i++) &#123;</span><br><span class="line">            a[i] = i;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">var</span> start = <span class="built_in">Date</span>.now();</span><br><span class="line">        <span class="keyword">await</span> a;</span><br><span class="line">        <span class="keyword">var</span> end = <span class="built_in">Date</span>.now();</span><br><span class="line">        <span class="built_in">console</span>.log(end - start);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">        <span class="built_in">console</span>.error(e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)();</span><br><span class="line"></span><br><span class="line">(<span class="keyword">async</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">var</span> a = [];</span><br><span class="line">        a[<span class="number">0</span>] = a;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">await</span> a);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">        <span class="built_in">console</span>.error(e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)();</span><br><span class="line"></span><br><span class="line">(<span class="keyword">async</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">var</span> o = &#123;&#125;;</span><br><span class="line">        o.self = o;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">await</span> o);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">        <span class="built_in">console</span>.error(e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><p>运行结果如下：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">14</span><br><span class="line">[ [Circular] ]</span><br><span class="line">&#123; self: [Circular] &#125;</span><br></pre></td></tr></table></figure><p>我们发现 <code>async/await</code> 的输出结果跟 <code>hprose.co</code> 是一致的，但是在性能上，<code>hprose.co</code> 则比 <code>async/await</code> 还要快 1 倍。因此，第二个回合，<code>hprose.co</code> 仍然是完胜 <code>tj/co</code> 和 <code>async/await</code>。</p><h1 id="yield-thunk-函数"><a href="#yield-thunk-函数" class="headerlink" title="yield thunk 函数"></a>yield thunk 函数</h1><p>我们再来看看 <code>tj/co</code> 和 <code>tj/thunkify</code> 是多么的让人抓狂，以及 <code>hprose.co</code> 和 <code>hprose.thunkify</code> 是如何优雅的解决 <code>tj/co</code> 和 <code>tj/thunkify</code> 带来的这些让人抓狂的问题的。</p><p>首先我们来看第一个问题：</p><p><code>tj/thunkify</code> 返回的 thunk 函数的执行结果是一次性的，不能像 <code>promise</code> 结果那样被使用多次，我们来看看下面这个例子：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> co = <span class="built_in">require</span>(<span class="string">"co"</span>);</span><br><span class="line"><span class="keyword">var</span> thunkify = <span class="built_in">require</span>(<span class="string">"thunkify"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> sum = thunkify(<span class="function"><span class="keyword">function</span>(<span class="params">a, b, callback</span>) </span>&#123;</span><br><span class="line">    callback(<span class="literal">null</span>, a + b);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">co(<span class="function"><span class="keyword">function</span>*(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> result = sum(<span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">yield</span> result);</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">yield</span> sum(<span class="number">2</span>, <span class="number">3</span>));</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">yield</span> result);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>这个例子很简单，输出结果你猜是啥？</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">3</span><br><span class="line">5</span><br><span class="line">3</span><br></pre></td></tr></table></figure><p>是上面的结果吗？恭喜你，答错了！不过，这不是你的错，而是 <code>tj/thunkify</code> 的错，它的结果是：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">3</span><br><span class="line">5</span><br></pre></td></tr></table></figure><p>什么？最后的 <code>console.log(yield result)</code> 输出结果哪儿去了？不好意思，<code>tj/thunkify</code> 解释说是为了防止 <code>callback</code> 被重复执行，所以就只能这么玩了。可是真的是这样吗？</p><p>我们来看看使用 <code>hprose.co</code> 和 <code>hprose.thunkify</code> 的执行结果吧，把开头两行换成下面三行：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> hprose = <span class="built_in">require</span>(<span class="string">"hprose"</span>);</span><br><span class="line"><span class="keyword">var</span> co = hprose.co;</span><br><span class="line"><span class="keyword">var</span> thunkify = hprose.thunkify;</span><br></pre></td></tr></table></figure><p>其它代码都不用改，运行它，你会发现预期的结果出来了，就是：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">3</span><br><span class="line">5</span><br><span class="line">3</span><br></pre></td></tr></table></figure><p>可能你还不服气，你会说，<code>tj/thunkify</code> 这样做是为了防止类似被 <code>thunkify</code> 的函数中，回调被多次调用时，<code>yield</code> 的结果不正确，比如：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> sum = thunkify(<span class="function"><span class="keyword">function</span>(<span class="params">a, b, callback</span>) </span>&#123;</span><br><span class="line">    callback(<span class="literal">null</span>, a + b);</span><br><span class="line">    callback(<span class="literal">null</span>, a + b + a);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">co(<span class="function"><span class="keyword">function</span>*(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> result = sum(<span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">yield</span> result);</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">yield</span> sum(<span class="number">2</span>, <span class="number">3</span>));</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">yield</span> result);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>如果 <code>tj/thunkify</code> 不这样做，结果可能就会变成：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td></tr></table></figure><p>可是真的是这样吗？你会发现，即使改成上面的样子，<code>hprose.thunkify</code> 配合 <code>hprose.co</code> 返回的结果仍然是：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">3</span><br><span class="line">5</span><br><span class="line">3</span><br></pre></td></tr></table></figure><p>跟预期的一样，回调函数并没有重复执行，错误的结果并没有出现。而且当需要重复 <code>yield</code> 结果函数时，还能够正确得到结果。</p><p>最后我们再来看一下，<code>tj/thunkify</code> 这样做真的解决了问题了吗？我们把代码改成下面这样：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> sum = thunkify(<span class="function"><span class="keyword">function</span>(<span class="params">a, b, callback</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"call sum("</span> + <span class="built_in">Array</span>.prototype.join.call(<span class="built_in">arguments</span>) + <span class="string">")"</span>);</span><br><span class="line">    callback(<span class="literal">null</span>, a + b);</span><br><span class="line">    callback(<span class="literal">null</span>, a + b + a);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">co(<span class="function"><span class="keyword">function</span>*(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> result = sum(<span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">yield</span> result);</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">yield</span> sum(<span class="number">2</span>, <span class="number">3</span>));</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">yield</span> result);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>然后替换不同的 <code>co</code> 和 <code>thunkify</code>，然后执行，我们会发现，<code>tj</code> 版本的输出如下：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">call sum(1,2,function ()&#123;</span><br><span class="line">        if (called) return;</span><br><span class="line">        called = true;</span><br><span class="line">        done.apply(null, arguments);</span><br><span class="line">      &#125;)</span><br><span class="line">3</span><br><span class="line">call sum(2,3,function ()&#123;</span><br><span class="line">        if (called) return;</span><br><span class="line">        called = true;</span><br><span class="line">        done.apply(null, arguments);</span><br><span class="line">      &#125;)</span><br><span class="line">5</span><br><span class="line">call sum(1,2,function ()&#123;</span><br><span class="line">        if (called) return;</span><br><span class="line">        called = true;</span><br><span class="line">        done.apply(null, arguments);</span><br><span class="line">      &#125;,function ()&#123;</span><br><span class="line">        if (called) return;</span><br><span class="line">        called = true;</span><br><span class="line">        done.apply(null, arguments);</span><br><span class="line">      &#125;)</span><br></pre></td></tr></table></figure><p>而 <code>hprose</code> 版本的输出结果如下：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">call sum(1,2,function () &#123;</span><br><span class="line">                thisArg = this;</span><br><span class="line">                results.resolve(arguments);</span><br><span class="line">            &#125;)</span><br><span class="line">3</span><br><span class="line">call sum(2,3,function () &#123;</span><br><span class="line">                thisArg = this;</span><br><span class="line">                results.resolve(arguments);</span><br><span class="line">            &#125;)</span><br><span class="line">5</span><br><span class="line">3</span><br></pre></td></tr></table></figure><p>从这里，我们可以看出，<code>tj</code> 版本的程序在执行第二次 <code>yield result</code> 时，简直错的离谱，它不但没有让我们得到预期的结果，反而还重复执行了 <code>thunkify</code> 后的函数，而且带入的参数也完全不对了，所以，这是一个完全错误的实现。</p><p>而从 <code>hprose</code> 版本的输出来看，<code>hprose</code> 不但完美的避免了回调被重复执行，而且保证了被 <code>thunkify</code> 后的函数执行的结果被多次 <code>yield</code> 时，也不会被重复执行，而且还能够得到预期的结果，可以实现跟返回 promise 对象一样的效果。</p><p><code>tj</code> 因为没有解决他所实现的 <code>thunkify</code> 函数带来的这些问题，所以在后期推荐大家放弃 <code>thunkify</code>，转而投奔到返回 <code>promise</code> 对象的怀抱中，而实际上，这个问题并非是不能解决的。</p><p><code>hprose</code> 在对 <code>thunkify</code> 函数的处理上，再次完胜 <code>tj</code>。而这个回合中，<code>async/await</code> 就不用提了，因为 <code>async/await</code> 完全不支持对 thunk 函数进行 <code>await</code>。</p><p>这还不是 <code>hprose.co</code> 和 <code>hprose.thunkify</code> 的全部呢，再继续看下面这个例子：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> sum = thunkify(<span class="function"><span class="keyword">function</span>(<span class="params">a, b, callback</span>) </span>&#123;</span><br><span class="line">    callback(a + b);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">co(<span class="function"><span class="keyword">function</span>*(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> result = sum(<span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">yield</span> result);</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">yield</span> sum(<span class="number">2</span>, <span class="number">3</span>));</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">yield</span> result);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>这里开头对 <code>hprose</code> 和 <code>tj</code> 版本的不同 <code>co</code> 和 <code>thunkify</code> 实现的引用就省略了，请大家自行脑补。</p><p>上面这段程序，如果使用 <code>tj</code> 版本的 <code>co</code> 和 <code>thunkify</code> 实现，运行结果是这样的：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">(node:75927) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): 3</span><br><span class="line">(node:75927) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.</span><br></pre></td></tr></table></figure><p>而如果使用 <code>hprose</code> 版本的 <code>co</code> 和 <code>thunkify</code> 实现，运行结果是这样的：</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">3</span><br><span class="line">5</span><br><span class="line">3</span><br></pre></td></tr></table></figure><p><code>hprose</code> 版本的运行结果再次符合预期，而 <code>tj</code> 版本的运行结果再次让人失望之极。</p><p>进过上面三个回合的较量，我们发现 hprose 的协程完胜 <code>tj</code> 和 <code>async/await</code>，而且 <code>tj</code> 的实现是惨败，<code>async/await</code> 虽然比 <code>tj</code> 稍微好那么一点，但是跟 <code>hprose</code> 所实现协程比起来，也是望尘莫及。</p><p>所以，用 <code>tj/co</code> 和 <code>async/await</code> 感觉很不爽的同学，可以试试 <code>hprose.co</code> 了，绝对让你爽歪歪。</p><p>hprose 有 4 个 JavaScript 版本，它们都支持上面的协程库，它们的地址分别是：</p><ul><li><a href="https://github.com/hprose/hprose-nodejs" target="_blank" rel="noopener">hprose for Node.js</a> 【<a href="https://git.coding.net/andot/hprose-nodejs.git" target="_blank" rel="noopener">coding镜像</a>】</li><li><a href="https://github.com/hprose/hprose-html5" target="_blank" rel="noopener">hprose for HTML5</a> 【<a href="https://git.coding.net/andot/hprose-html5.git" target="_blank" rel="noopener">coding镜像</a>】</li><li><a href="https://github.com/hprose/hprose-js" target="_blank" rel="noopener">hprose for JavaScript</a> 【<a href="https://git.coding.net/andot/hprose-js.git" target="_blank" rel="noopener">coding镜像</a>】</li><li><a href="https://github.com/hprose/hprose-wx" target="_blank" rel="noopener">hprose for 微信小程序</a> 【<a href="https://git.coding.net/andot/hprose-wx.git" target="_blank" rel="noopener">coding镜像</a>】</li></ul><p>另外，如果你不需要使用 hprose 序列化和远程调用的话，下面还有一个专门的从 hprose 中精简出来的 Promise A+ 实现和协程库：<a href="https://github.com/andot/future-js" target="_blank" rel="noopener">Future.js</a> 【<a href="https://git.oschina.net/hprose/future-js" target="_blank" rel="noopener">coding镜像</a>】</p><p>当然该协程库的功能不止于此，更多介绍请参见：</p><ul><li><a href="https://github.com/hprose/hprose-nodejs/wiki/Promise-%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B" target="_blank" rel="noopener">Promise 异步编程</a></li><li><a href="https://github.com/hprose/hprose-nodejs/wiki/%E5%8D%8F%E7%A8%8B" target="_blank" rel="noopener">协程</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;ES6 中引入了 Generator，Generator 通过封装之后，可以作为协程来进行使用。&lt;/p&gt;
&lt;p&gt;其中对 Generator 封装最为著名的当属 tj/co，但是 tj/co 跟 ES2016 的 async/await 相比的话，还存在一些比较严重的缺陷。&lt;/p&gt;
&lt;p&gt;hprose 中也引入了对 Generator 封装的协程支持，但是比 tj/co 更加完善，下面我们就来详细介绍一下它们之间的差别。&lt;/p&gt;
    
    </summary>
    
      <category term="编程" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/"/>
    
      <category term="JavaScript" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/JavaScript/"/>
    
    
      <category term="hprose" scheme="https://coolcode.org/tags/hprose/"/>
    
      <category term="协程" scheme="https://coolcode.org/tags/%E5%8D%8F%E7%A8%8B/"/>
    
      <category term="coroutine" scheme="https://coolcode.org/tags/coroutine/"/>
    
      <category term="async" scheme="https://coolcode.org/tags/async/"/>
    
      <category term="promise" scheme="https://coolcode.org/tags/promise/"/>
    
  </entry>
  
  <entry>
    <title>序列化和反序列化浅析</title>
    <link href="https://coolcode.org/2016/11/08/a-brief-analysis-of-serialization-and-deserialization/"/>
    <id>https://coolcode.org/2016/11/08/a-brief-analysis-of-serialization-and-deserialization/</id>
    <published>2016-11-08T16:00:00.000Z</published>
    <updated>2016-11-10T16:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>序列化和反序列化对于现代的程序员来说是一个既熟悉又陌生的概念。说熟悉是因为几乎每个程序员在工作中都直接或间接的使用过它，说陌生是因为大多数程序员对序列化和反序列化的认识仅仅停留在比较一下各种不同实现的序列化的性能上面，而很少有程序员对序列化和反序列化的设计和实现有深入的研究。</p><p>本文将从序列化和反序列化的设计和实现的入手，来简单讲解一下序列化和反序列化。其中包括以下几个方面：</p><ol><li>序列化和反序列化的作用</li><li>什么样的数据是可序列化的</li><li>序列化和反序列化的分类</li><li>序列化和反序列化的类型映射</li></ol><p>本文不会涉及到某几种语言的某几种序列化实现的性能对比之类的内容。</p><a id="more"></a><h1 id="序列化和反序列化的作用"><a href="#序列化和反序列化的作用" class="headerlink" title="序列化和反序列化的作用"></a>序列化和反序列化的作用</h1><p>我们在编写程序代码时，通常会定义一些常量和变量，然后再写一堆操作它们的指令。不管是变量还是常量，它们表示的都是数据。所以简单的说，一个程序就是一堆指令操作一堆数据。</p><p>但是为了更有效的管理这堆数据，现代的程序设计语言都会引入一个类型系统来对这些数据进行分类管理，而不是让程序员把所有数据都一股脑的当做二进制串来进行操作。</p><p>比如一个常量可能是一个数字，一个布尔值，一个字符串，或者是一个由它们构成的数组。而变量通常具有更丰富的类型可以使用。甚至你还可以自定义类型。对于面向对象的语言来说，一个类型表示的不仅仅是数据本身，还包括了对这种类型数据的一组操作。</p><p>一个程序可以以源码或者可执行的二进制形式保存在磁盘（或者其它存储介质）上。当你需要执行它时，它会以某种形式被载入内存，然后执行。</p><p>一个程序在执行过程中通常会生成新的数据，这些新的数据一部分是临时的，在内存中，它们转瞬即逝。还有一部分数据可能需要被保存下来，或者被传递到其它的地方去。在这种情况下，可能就会涉及到数据的形式转换的问题。这个把程序运行时的内存数据转换为一种可保存或可传递的数据的过程，我们就称它为序列化。</p><p>这些保存和传递的数据，可能会在某个时间被重新载入内存，但可能会是不同的进程，或者不同的程序，甚至不同的机器上被载入，还原为内存中的具体类型的数据变量，这个从保存的数据还原为具体语言具体类型的数据变量的过程，我们称它为反序列化。</p><h1 id="什么样的数据是可序列化的"><a href="#什么样的数据是可序列化的" class="headerlink" title="什么样的数据是可序列化的"></a>什么样的数据是可序列化的</h1><p>什么样的数据可序列化这是一个相对的问题，而不是一个绝对的问题。因为它会受到各种不同因素的影响。</p><h2 id="数据的可还原性"><a href="#数据的可还原性" class="headerlink" title="数据的可还原性"></a>数据的可还原性</h2><p>被序列化的数据应该是可还原的。可还原的意思是，一个被序列化的数据在被反序列化后仍然是有意义的。注意，这里说的是有意义，而不是说被反序列化的数据应该跟序列化之前的源数据相同。为了便于理解，我们来举例说明一下。</p><h3 id="指针数据是否可序列化"><a href="#指针数据是否可序列化" class="headerlink" title="指针数据是否可序列化"></a>指针数据是否可序列化</h3><p>首先我们来讨论一下指针类型的数据是否可序列化。</p><p>通常我们认为指针数据是不可序列化的，因为它表示的是一个内存地址，而如果我们把这个内存地址保存下来，下一次我们将这个内存地址还原到一个指针变量中时，这个内存地址所指向的位置的数据可能早就不是我们所需要的数据了，甚至指向的是一个完全没有意义的数据。所以，在这种情况下，虽然前后两个指针变量的值相同，但是还原之后的指针变量指向的数据已经没有意义了，我们就称它不具有可还原性。</p><p>那么指针数据真的不可序列化吗？如果我们从需要反序列化的数据有意义这个角度考虑，那么我们也可以做到对指针数据的序列化。</p><p>指针通常分为指向具体类型数据的指针（例如：<code>int *</code>, <code>string *</code>）和指向不明类型数据的指针（例如 <code>void *</code>）。</p><p>对于前者，如果我们希望序列化的数据包含指向的具体类型的数据，并且在反序列化之后，能够还原为一个指向该具体类型数据的指针，且指向的数据值跟源值相同的话，那么我们其实是可以做到的。虽然，还原之后的指针所指向的内存地址，跟源指针指向的内存地址可能完全不一样，但是它指向的数据是有意义的，且是我们期望的，那么这种情况下，我们也可以称这个指针数据是可还原的。</p><p>对于后者，如果我们没有所指向数据的具体信息，那就没有办法对指向的数据进行保存。所以这种类型的指针也就没办法进行序列化了。</p><p>另外，还有一种特殊的指针类型，它保存的并不是一个具体的内存地址，而是一个相对的偏移量，比如 <code>uintptr_t</code> 类型就常作此用，这种时候，对它的值序列化和反序列化之后，得到的值仍然是同样的相对偏移量值，在这种情况下，反序列化后的数据就是有意义的，所以，这种指针数据也具有可还原性。</p><p>从上面的分析，我们可以看出，指针类型是否可序列化，取决于我们想要什么意义的反序列化数据。</p><h3 id="资源类型是否可序列化"><a href="#资源类型是否可序列化" class="headerlink" title="资源类型是否可序列化"></a>资源类型是否可序列化</h3><p>对于资源类型，有些语言有明确的定义，比如 PHP，而有些语言则没有明确的定义。但大致上我们可以认为一个打开的文件对象，一个打开的数据库连接，一个打开的网络套接字，以及诸如此类跟外部资源相关的数据类型，都可以被称作资源类型。</p><p>对于资源类型我们通常认为它们都是不可序列化的，哪怕表示该类型的结构体中的所有字段都是可序列化的基本类型数据。原因是这些资源类型中保存的数据是跟当前打开的资源相关的，这些数据如果复制到其它的进程，或者其它的机器中去之后，这些资源类型中保存的数据就失去了意义。</p><p>对于资源类型的一部分属性数据，比如文件名，数据库地址，网络套接字地址，它们可以在不同的进程、不同的机器之间传递之后，仍然表示原有的意义。</p><p>但是通常的序列化程序是不会对资源类型做这样的序列化操作的，因为序列化程序对资源类型序列化时，并不能假定用户需要的仅仅是这些信息，而且如果用户需要的真的就仅仅是这些信息的话，那用户完全可以明确的只序列化这些数据，而不是对整个资源类型做序列化操作。</p><p>但是有些特殊的资源，比如内存流，文件流等。不同的序列化实现可能对待它们的方式也不同。有些序列化实现认为这些资源类型同样不可序列化。而有些序列化实现则认为可以将资源本身一起序列化，比如内存流中的数据会被作为序列化数据的主体进行序列化，在反序列化时，被反序列化为另外一个内存流对象，虽然是两个不同的资源，但是资源中的数据是相同的。</p><h2 id="序列化格式的限制"><a href="#序列化格式的限制" class="headerlink" title="序列化格式的限制"></a>序列化格式的限制</h2><p>一个数据能否被序列化，还要看所使用的序列化格式是否支持。</p><p>对于基本类型的数据来说，几乎所有的序列化格式都支持。但是对于有些采用代码生成器方式实现的序列化来说，它们可能只支持通过 IDL 生成的代码中所定义的类型的序列化，而不支持对语言内置的单个原生类型数据变量的序列化，也不支持通过普通方式定义的自定义类型数据的序列化。比如 Protocol Buffers 就是这样。</p><p>对于复杂类型，比如 map 这种类型，有些序列化格式只支持 Key 为字符串类型的 map 数据的序列化。而不支持其它 Key 类型的 map 数据的序列化。比如 JSON 就是这样。</p><p>还有一种复杂类型数据是带有循环引用结构的数据，比如下面这个 JavaScript 代码中定义的这个数组 <code>a</code>：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = [];</span><br><span class="line">a[<span class="number">0</span>] = a;</span><br></pre></td></tr></table></figure><p>它的第一个元素引用了自己，这就产生了循环引用。对于这种类型的数据，很多的序列化格式也是不支持的，比如 JSON，Msgpack 都不支持这种类型数据的序列化。</p><p>但是上面所说的情况，并不是所有的序列化格式都不支持，比如 Hprose 对上面所说的所有类型都支持。</p><p>以上这些限制都是序列化格式本身造成的。</p><h2 id="序列化实现的限制"><a href="#序列化实现的限制" class="headerlink" title="序列化实现的限制"></a>序列化实现的限制</h2><p>对于同一种序列化格式，即便是在同一种语言中，也可能存在着多种不同的实现，比如对于 JSON 序列化来说，它的 Java 版本的实现甚至有上百种。这些不同的实现各有特色，也各有各的限制，甚至互不兼容。有些实现可能仅仅支持几种特别定义的类型。有些则对语言内置的类型提供了很好的支持。</p><p>还有一些序列化格式跟特定语言有紧密的绑定关系，因此无法做到跨语言的序列化和反序列化，比如 Java 序列化，.NET 的 Binary 序列化，Go 语言的 Gob 序列化格式就只能支持特定的语言。</p><p>而且即便是这种针对特定语言的序列化也不是支持该语言的所有类型。比如：Java 序列化对于 class 类型只支持实现了 <code>java.io.Serializable</code> 接口的类型；.NET Binary 序列化则只支持标记了 <code>System.SerializableAttribute</code> 属性的类型。</p><p>所以，我们不能想当然的认为，一个数据支持某一种序列化，就一定支持其它类型的序列化。这种假设是不成立的。</p><h1 id="序列化和反序列化的分类"><a href="#序列化和反序列化的分类" class="headerlink" title="序列化和反序列化的分类"></a>序列化和反序列化的分类</h1><p>序列化和反序列化的格式多种多样，它们之间的主要区别可以大致分这样几类：</p><h2 id="按照可读性分类"><a href="#按照可读性分类" class="headerlink" title="按照可读性分类"></a>按照可读性分类</h2><p>首先从可读性角度，大致可分为文本序列化和二进制序列化两种，但是也有一些序列化格式介于两者之间，我们将它们暂称为半文本序列化。</p><h3 id="文本序列化"><a href="#文本序列化" class="headerlink" title="文本序列化"></a>文本序列化</h3><p>XML 和 JSON 是大家最常见的两种文本序列化格式。</p><p>文本序列化的数据都是使用人类可读的字符表示的，就像大部分编程语言一样。而且允许包含多余的空白，以增加可读性。当然也可以表示为紧凑编码形式，以利于减少存储空间和传输流量。</p><p>文本序列化除了可读性还具有可编辑性，因此，文本序列化格式也经常被用于作为配置文件的存储格式。这样，使用普通的文本编辑器就可以方便的编辑这种配置文件。</p><p>文本序列化在表示数字时，通常采用人类可读的十进制数（包括小数和科学计数法）的字符串形式，这除了具有可读性以外，还有另外一个好处，就是可以方便的表示大整数或者高精度小数。</p><h3 id="二进制序列化"><a href="#二进制序列化" class="headerlink" title="二进制序列化"></a>二进制序列化</h3><p>二进制序列化的的数据不具有可读性，但是通常比文本序列化格式更加紧凑，而且在解析速度上也更有优势，当然实际的解析速度还跟具体实现有很大的关系，所以这也不是绝对的。</p><p>因为它们本身不具有可读性，所以在实际使用时，如果要想查看这些数据，就需要借助一些工具将它们解析为可读信息之后才能使用。在这方面，它们相对于文本序列化具有明显的劣势。</p><p>二进制序列化表示数字时，通常会使用定长或者变长的二进制编码方式，这虽然有利于更快的编码和解析编程语言中的基本数字类型，但是却不能表示大整数和高精度小数。</p><p>Protocol Buffers，Msgpack，BSON，Hessian 等格式是二进制序列化格式的代表。</p><h3 id="半文本序列化"><a href="#半文本序列化" class="headerlink" title="半文本序列化"></a>半文本序列化</h3><p>半文本序列化格式通常兼具文本序列化的可读性和二进制序列化的性能。</p><p>半文本序列化的数据也使用人类可读的字符表示，具有一定的可读性，但是半文本序列化是空白敏感的，因此它们不能像文本序列化那样在序列化数据中添加空白。</p><p>半文本序列化格式采用紧凑编码形式，而且通常会采用跟二进制编码类似的TLV（Type-Length-Value）编码方式，因此具有比文本序列化更高效的解析速度，当然实际解析效率也跟具体实现有关。</p><p>半文本序列化格式中对原本的二进制字符串数据仍然按照二进制字符串的格式保存，而不会像文本序列化格式一样，需要将它们转换为 Base64 格式的文本。对于二进制字符串来说，不管是转为 Base64 格式的文本还是原本的样子，都不具有可读性，因此，直接以原格式保存，并不损失可读性，但是却可以增加解析效率。</p><p>半文本序列化格式在表示字符串时不会像文本序列化那样在字符串中间增加转义字符，或者将原本的字符用转义符号表示，因此，半本文序列化格式中的字符串反而比文本序列化的字符串具有更好的可读性。</p><p>半文本序列化格式在数字编码上具有跟文本序列化格式一样的特点。</p><p>Hprose，PHP 序列化格式是半文本序列化的代表。</p><h2 id="按照自描述性分类"><a href="#按照自描述性分类" class="headerlink" title="按照自描述性分类"></a>按照自描述性分类</h2><h3 id="自描述序列化"><a href="#自描述序列化" class="headerlink" title="自描述序列化"></a>自描述序列化</h3><p>如果序列化数据中包含有数据类型的元信息，或者数据的表示形式同时可以反映出它的类型，那么这种序列化格式就是自描述的。自描述的序列化格式，可以在不借助外部描述的情况下，进行解析。</p><p>文本序列化和半文本序列化基本上都是自描述的。二进制序列化格式中，大部分也是自描述的。</p><p>自描述序列化格式不依赖外描述文件是它的优势，在一些应用场景下，这具有不可替代的优越性。但也因为包含了元信息，导致它的数据大小通常要比非自描述序列化的数据大一些。</p><p>像 XML，JSON，Hprose，Hessian，Msgpack 都是自描述类型的序列化格式。</p><h3 id="非自描述序列化"><a href="#非自描述序列化" class="headerlink" title="非自描述序列化"></a>非自描述序列化</h3><p>非自描述序列化的数据在体积上更小，但是因为舍弃了自描述性，使得这种序列化数据在离开外部描述之后，就无法再被使用。</p><p>Protocol Buffers 是典型的非自描述类型的序列化格式的代表。</p><h2 id="按照实现方式分类"><a href="#按照实现方式分类" class="headerlink" title="按照实现方式分类"></a>按照实现方式分类</h2><p>序列化和反序列化的很大一部分特征是由它们的实现决定的。关于序列化通常是使用代码生成或者反射的方式来实现，而对于反序列化除了这两种方式之外，还有将序列化数据解析为语法树的方式，这种方式实际上并不算反序列化，但通常可以更快的查找和获取文本序列化数据中某个节点的值。</p><h3 id="基于代码生成器实现的序列化"><a href="#基于代码生成器实现的序列化" class="headerlink" title="基于代码生成器实现的序列化"></a>基于代码生成器实现的序列化</h3><p>采用代码生成方式实现序列化的好处是可以不依赖编程语言本身运行时中的元数据信息，这样即使某个语言（比如 C/C++）的运行时中本身没有包含足够的元数据时，也可以方便的进行序列化和反序列化。</p><p>采用代码生成方式实现序列化的另一个好处是，因为不使用反射，序列化和反序列化的速度通常会比基于反射实现的序列化反序列化更快一些。</p><p>但是采用代码生成方式实现的序列化的缺点也很明显，比如对支持的数据类型限制比较严格，使用起来比较麻烦，需要编写 IDL 文件，在类型映射上比较死板，通常只能实现 1-1 的映射（这个我们后面再谈），类型升级时，会产生兼容性问题等等。</p><h3 id="基于反射实现的序列化"><a href="#基于反射实现的序列化" class="headerlink" title="基于反射实现的序列化"></a>基于反射实现的序列化</h3><p>基于动态反射来实现序列化和反序列化可以做到更好的类型支持，比如语言的内置类型和普通方式编写的自定义类型的数据都可以被序列化和反序列化，而且无需编写 IDL 文件就可以实现动态序列化，类型映射也更加灵活，可以实现 n-m 的映射，类型升级时，可以避免产生兼容性问题。</p><p>但通常基于反射实现的序列化和反序列化的速度要比采用代码生成方式的序列化和反序列化要慢一些，但是这也不是绝对的，因为在实现中，可通过一些其它的手段来提升性能。</p><p>例如采用缓存的方式，对于那些需要反射才能获得的元信息进行缓存，这样在获取元信息时可以避免反射而直接使用缓存的元信息来加快序列化速度。还可以使用动态的字节码生成方式，比如在 Java 中使用 ASM 技术来动态生成序列化和反序列化的代码，在 .NET 中使用 Emit 技术也可以实现同样的功能。而对于 C、C++、Rust 等语言可以采用宏和模板的方式在编译期生成具体类型的序列化和反序列化的代码，对于 D、Nim 等语言则可以采用编译期反射和编译期代码执行功能在编译期动态生成具体类型的序列化和反序列化代码，通过这些手段，既可以获得传统的代码生成器方式的序列化和反序列化的性能，又可以避免代码生成器的缺陷。</p><p>例如 Hprose for .NET 就采用上面提到的元数据缓存 + Emit 动态代码生成的优化手段，使得它的序列化和反序列化速度远远超过 Protocol Buffers 的速度。</p><h2 id="按照跨语言能力分类"><a href="#按照跨语言能力分类" class="headerlink" title="按照跨语言能力分类"></a>按照跨语言能力分类</h2><p>并不是所有的序列化格式都是跨语言的。即使是跨语言的序列化格式，在跨语言的能力上也有所不同。</p><h3 id="特定语言专有的序列化"><a href="#特定语言专有的序列化" class="headerlink" title="特定语言专有的序列化"></a>特定语言专有的序列化</h3><p>大部分语言内置的序列化格式都属于特定语言专有的序列化。例如 Java 的序列化，.NET 的 Binary 序列化，Go 的 Gob 序列化都属于这一种。</p><p>但也有特例，比如 PHP 序列化，原本是 PHP 语言专有的序列化格式，但因为它的格式比较简单，因此也有一些其它语言上的 PHP 序列化的第三方实现。但终究 PHP 序列化格式跟 PHP 语言的关系更加紧密，所以在其他语言中使用 PHP 序列化时相对于其它跨语言的序列化格式或多或少的会有一些不方便的地方。</p><h3 id="跨语言的序列化"><a href="#跨语言的序列化" class="headerlink" title="跨语言的序列化"></a>跨语言的序列化</h3><p>文本序列化格式往往具有更好的跨语言特征。比如 XML，JSON 等序列化格式，对于不同的语言都有很多的实现来支持。</p><p>还有一些半文本或二进制序列化格式也是为跨语言而设计的，比如 Hprose，Protocol Buffers，MsgPack 等，它们也具有很好的跨语言能力。</p><p>但多数二进制序列化格式在跨语言方面有很多限制。</p><h1 id="序列化和反序列化的类型映射"><a href="#序列化和反序列化的类型映射" class="headerlink" title="序列化和反序列化的类型映射"></a>序列化和反序列化的类型映射</h1><p>如果编程语言中的数据类型跟序列化格式中的数据类型有且只有唯一的映射关系，我们就把这种类型映射关系称为 1-1 映射。</p><p>如果在序列化时，编程语言中的多种数据类型被映射为一种序列化格式的类型，并且在反序列化时，一种序列化类型可以被反序列化为编程语言中的多种类型，那么这种类型映射关系称为 n-m 映射。</p><p>当然还存在其它的情况，比如多种序列化类型被反序列化为编程语言中的同一种类型，再比如编程语言中的所有类型跟序列化类型中的某个类型都不存在映射关系，等等。这些其它情况，我们也把它们归到 1-1 映射中。</p><p>1-1 映射还是 n-m 映射，除了跟序列化格式有关以外，还跟具体的语言实现有很大的关系。</p><h2 id="1-1-映射"><a href="#1-1-映射" class="headerlink" title="1-1 映射"></a>1-1 映射</h2><p>语言内置的序列化和反序列化实现一般都是 1-1 映射。这可以保证序列化之前的数据跟反序列化之后的数据在类型上的完全一致性。但也由于语言内置类型的丰富性和 1-1 映射的一致性，导致这些语言内置的序列化格式几乎无法做到跨语言实现。</p><p>我们前面也谈到过一个特例，那就是 PHP 序列化，PHP 序列化之所以能够做到跨语言实现，是因为它本身的内置类型非常有限，以至于即使在 PHP 中是 1-1 映射的数据类型还不如其它一些跨语言的序列化支持的数据类型更丰富。</p><p>而 JSON 格式，如果把它放到 JavaScript 中，它也是 1-1 映射的。而 JSON 序列化在其它语言中的实现则是多种多样，有的仅支持 1-1 映射，有的则支持 n-m 映射，即便是同一种语言的不同实现也是如此。</p><p>1-1 映射最麻烦的问题是，要么支持的类型不够丰富，要么跨语言方面难以实现。</p><p>第一个问题对于本来类型就不是很多的脚本语言来说通常不是问题，但对于 Java，C# 之类的语言来说，这就是个问题了。</p><h2 id="n-m-映射"><a href="#n-m-映射" class="headerlink" title="n-m 映射"></a>n-m 映射</h2><p>n-m 映射可以很好的解决这个问题。</p><p>比如序列化格式中不需要为 Array，List，Tuple，Set 定义不同的类型，而只需要一种通用的列表类型，之后就可以将某种具体语言的 Array，List，Tuple，Set 等具有列表特征的数据都映射为这一种列表类型，在反序列化的时候，则直接反序列化为某种指定的类型。</p><p>这样做还有一个额外的好处：当你希望类型一致的时候，你就可以实现类型一致，而当你不希望使用一致的类型时，可以直接在序列化和反序列化的过程中进行类型的转换。而不需要得到了一致的类型之后，再去自己手动转换为另一种类型。</p><p>通过反射方式来实现的序列化和反序列化可以更方便的实现 n-m 映射。而通过代码生成器方式实现的序列化和反序列化则通常只能实现 1-1 映射。因此，通过反射方式来实现的序列化和反序列化具有更好的灵活性。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;序列化和反序列化对于现代的程序员来说是一个既熟悉又陌生的概念。说熟悉是因为几乎每个程序员在工作中都直接或间接的使用过它，说陌生是因为大多数程序员对序列化和反序列化的认识仅仅停留在比较一下各种不同实现的序列化的性能上面，而很少有程序员对序列化和反序列化的设计和实现有深入的研究。&lt;/p&gt;
&lt;p&gt;本文将从序列化和反序列化的设计和实现的入手，来简单讲解一下序列化和反序列化。其中包括以下几个方面：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;序列化和反序列化的作用&lt;/li&gt;
&lt;li&gt;什么样的数据是可序列化的&lt;/li&gt;
&lt;li&gt;序列化和反序列化的分类&lt;/li&gt;
&lt;li&gt;序列化和反序列化的类型映射&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本文不会涉及到某几种语言的某几种序列化实现的性能对比之类的内容。&lt;/p&gt;
    
    </summary>
    
      <category term="编程" scheme="https://coolcode.org/categories/%E7%BC%96%E7%A8%8B/"/>
    
    
      <category term="序列化" scheme="https://coolcode.org/tags/%E5%BA%8F%E5%88%97%E5%8C%96/"/>
    
      <category term="反序列化" scheme="https://coolcode.org/tags/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/"/>
    
  </entry>
  
</feed>
