標籤: 潭子電動車

  • 基於領域驅動設計(DDD)超輕量級快速開發架構(二)動態linq查詢的實現方式

    基於領域驅動設計(DDD)超輕量級快速開發架構(二)動態linq查詢的實現方式

    -之動態查詢,查詢邏輯封裝復用

    基於領域驅動設計(DDD)超輕量級快速開發架構詳細介紹請看

    https://www.cnblogs.com/neozhu/p/13174234.html

    需求

    1. 配合EasyUI datagird filter實現多字段(任意字段)的篩選
    2. 根據業務需求篩選特定的狀態或條件,如:查看結案的訂單,最近30天的訂單,查看屬於我的訂單.等等,這些邏輯是固定也是可以被重用,但又不想每次寫相同的條件,那麼下面我會給我的解決方案.

    需求1隻是一個偷懶的實現方式,因為datagrid自帶這個功能,但又不想根據具體的需求來畫查詢條件,如果需求必須要再datagrid上面做一塊查詢條件的輸入那目前只能在前端自己手工添加,在組織後傳入後台,暫時不在這裏討論

    需求2可能不太好解釋,看完代碼就自然理解為什麼要這麼做了,這麼做的好處有哪些

    具體實現的方式

     

     默認情況下 datagrid 有幾列就可以對這幾列進行篩選,對於日期型的字段會採用between,選擇2個時間之間進行篩選,数字類型會提供大於小於等符號選擇,可以自行嘗試,其原理是datagrid 會根據datagrid 頭部輸入的值生成一個Json字符串發送後台請求數據

    JSON:格式

    filterRules: [
    {field:field,op:op,value:value},
    {field:field,op:op,value:value},
    ] 
    • 通常的做法是一個一個判斷加條件
      1 var filters = JsonConvert.DeserializeObject<IEnumerable<filterRule>>(filterRules); 
      2 foreach (var rule in filters)
      3         {
      4           if (rule.field == "Id" && !string.IsNullOrEmpty(rule.value) && rule.value.IsInt())
      5           {
      6             var val = Convert.ToInt32(rule.value);
      7             switch (rule.op)
      8             {
      9               case "equal":
     10                 this.And(x => x.Id == val);
     11                 break;
     12               case "notequal":
     13                 this.And(x => x.Id != val);
     14                 break;
     15               case "less":
     16                 this.And(x => x.Id < val);
     17                 break;
     18               case "lessorequal":
     19                 this.And(x => x.Id <= val);
     20                 break;
     21               case "greater":
     22                 this.And(x => x.Id > val);
     23                 break;
     24               case "greaterorequal":
     25                 this.And(x => x.Id >= val);
     26                 break;
     27               default:
     28                 this.And(x => x.Id == val);
     29                 break;
     30             }
     31           }
     32           if (rule.field == "Name" && !string.IsNullOrEmpty(rule.value))
     33           {
     34             this.And(x => x.Name.Contains(rule.value));
     35           }
     36           if (rule.field == "Code" && !string.IsNullOrEmpty(rule.value))
     37           {
     38             this.And(x => x.Code.Contains(rule.value));
     39           }
     40 
     41           if (rule.field == "Address" && !string.IsNullOrEmpty(rule.value))
     42           {
     43             this.And(x => x.Address.Contains(rule.value));
     44           }
     45 
     46           if (rule.field == "Contect" && !string.IsNullOrEmpty(rule.value))
     47           {
     48             this.And(x => x.Contect.Contains(rule.value));
     49           }
     50 
     51           if (rule.field == "PhoneNumber" && !string.IsNullOrEmpty(rule.value))
     52           {
     53             this.And(x => x.PhoneNumber.Contains(rule.value));
     54           }
     55 
     56           if (rule.field == "RegisterDate" && !string.IsNullOrEmpty(rule.value))
     57           {
     58             if (rule.op == "between")
     59             {
     60               var datearray = rule.value.Split(new char[] { '-' });
     61               var start = Convert.ToDateTime(datearray[0]);
     62               var end = Convert.ToDateTime(datearray[1]);
     63 
     64               this.And(x => SqlFunctions.DateDiff("d", start, x.RegisterDate) >= 0);
     65               this.And(x => SqlFunctions.DateDiff("d", end, x.RegisterDate) <= 0);
     66             }
     67           }
     68           if (rule.field == "CreatedDate" && !string.IsNullOrEmpty(rule.value))
     69           {
     70             if (rule.op == "between")
     71             {
     72               var datearray = rule.value.Split(new char[] { '-' });
     73               var start = Convert.ToDateTime(datearray[0]);
     74               var end = Convert.ToDateTime(datearray[1]);
     75 
     76               this.And(x => SqlFunctions.DateDiff("d", start, x.CreatedDate) >= 0);
     77               this.And(x => SqlFunctions.DateDiff("d", end, x.CreatedDate) <= 0);
     78             }
     79           }
     80 
     81 
     82           if (rule.field == "CreatedBy" && !string.IsNullOrEmpty(rule.value))
     83           {
     84             this.And(x => x.CreatedBy.Contains(rule.value));
     85           }
     86 
     87          if (rule.field == "LastModifiedDate" && !string.IsNullOrEmpty(rule.value))
     88           {
     89             if (rule.op == "between")
     90             {
     91               var datearray = rule.value.Split(new char[] { '-' });
     92               var start = Convert.ToDateTime(datearray[0]);
     93               var end = Convert.ToDateTime(datearray[1]);
     94 
     95               this.And(x => SqlFunctions.DateDiff("d", start, x.LastModifiedDate) >= 0);
     96               this.And(x => SqlFunctions.DateDiff("d", end, x.LastModifiedDate) <= 0);
     97             }
     98           }
     99 
    100           if (rule.field == "LastModifiedBy" && !string.IsNullOrEmpty(rule.value))
    101           {
    102             this.And(x => x.LastModifiedBy.Contains(rule.value));
    103           }
    104 
    105         }

    View Code

    • 新的做法是動態根據field,op,value生成一個linq 表達式,不用再做繁瑣的判斷,這塊代碼也可以被其它項目使用,非常好用
    namespace SmartAdmin
    {
     
      public static class PredicateBuilder
      {
    
        public static Expression<Func<T, bool>> FromFilter<T>(string filtergroup) {
          Expression<Func<T, bool>> any = x => true;
          if (!string.IsNullOrEmpty(filtergroup))
          {
            var filters = JsonSerializer.Deserialize<filter[]>(filtergroup);
    
              foreach (var filter in filters)
              {
                if (Enum.TryParse(filter.op, out OperationExpression op) && !string.IsNullOrEmpty(filter.value))
                {
                  var expression = GetCriteriaWhere<T>(filter.field, op, filter.value);
                  any = any.And(expression);
                }
              }
          }
    
          return any;
        }
    
        #region -- Public methods --
        public static Expression<Func<T, bool>> GetCriteriaWhere<T>(Expression<Func<T, object>> e, OperationExpression selectedOperator, object fieldValue)
        {
          var name = GetOperand<T>(e);
          return GetCriteriaWhere<T>(name, selectedOperator, fieldValue);
        }
    
        public static Expression<Func<T, bool>> GetCriteriaWhere<T, T2>(Expression<Func<T, object>> e, OperationExpression selectedOperator, object fieldValue)
        {
          var name = GetOperand<T>(e);
          return GetCriteriaWhere<T, T2>(name, selectedOperator, fieldValue);
        }
    
        public static Expression<Func<T, bool>> GetCriteriaWhere<T>(string fieldName, OperationExpression selectedOperator, object fieldValue)
        {
          var props = TypeDescriptor.GetProperties(typeof(T));
          var prop = GetProperty(props, fieldName, true);
          var parameter = Expression.Parameter(typeof(T));
          var expressionParameter = GetMemberExpression<T>(parameter, fieldName);
          if (prop != null && fieldValue != null)
          {
           
            BinaryExpression body = null;
            switch (selectedOperator)
            {
              case OperationExpression.equal:
                body = Expression.Equal(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType)?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.notequal:
                body = Expression.NotEqual(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.less:
                body = Expression.LessThan(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.lessorequal:
                body = Expression.LessThanOrEqual(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.greater:
                body = Expression.GreaterThan(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.greaterorequal:
                body = Expression.GreaterThanOrEqual(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.contains:
                var contains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
                var bodyLike = Expression.Call(expressionParameter, contains, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(bodyLike, parameter);
              case OperationExpression.endwith:
                var endswith = typeof(string).GetMethod("EndsWith",new[] { typeof(string) });
                var bodyendwith = Expression.Call(expressionParameter, endswith, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(bodyendwith, parameter);
              case OperationExpression.beginwith:
                var startswith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
                var bodystartswith = Expression.Call(expressionParameter, startswith, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(bodystartswith, parameter);
              case OperationExpression.includes:
                return Includes<T>(fieldValue, parameter, expressionParameter, prop.PropertyType);
              case OperationExpression.between:
                return Between<T>(fieldValue, parameter, expressionParameter, prop.PropertyType);
              default:
                throw new Exception("Not implement Operation");
            }
          }
          else
          {
            Expression<Func<T, bool>> filter = x => true;
            return filter;
          }
        }
    
        public static Expression<Func<T, bool>> GetCriteriaWhere<T, T2>(string fieldName, OperationExpression selectedOperator, object fieldValue)
        {
    
    
          var props = TypeDescriptor.GetProperties(typeof(T));
          var prop = GetProperty(props, fieldName, true);
    
          var parameter = Expression.Parameter(typeof(T));
          var expressionParameter = GetMemberExpression<T>(parameter, fieldName);
    
          if (prop != null && fieldValue != null)
          {
            switch (selectedOperator)
            {
              case OperationExpression.any:
                return Any<T, T2>(fieldValue, parameter, expressionParameter);
    
              default:
                throw new Exception("Not implement Operation");
            }
          }
          else
          {
            Expression<Func<T, bool>> filter = x => true;
            return filter;
          }
        }
    
    
    
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr, Expression<Func<T, bool>> or)
        {
          if (expr == null)
          {
            return or;
          }
    
          return Expression.Lambda<Func<T, bool>>(Expression.OrElse(new SwapVisitor(expr.Parameters[0], or.Parameters[0]).Visit(expr.Body), or.Body), or.Parameters);
        }
    
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr, Expression<Func<T, bool>> and)
        {
          if (expr == null)
          {
            return and;
          }
    
          return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(new SwapVisitor(expr.Parameters[0], and.Parameters[0]).Visit(expr.Body), and.Body), and.Parameters);
        }
    
        #endregion
        #region -- Private methods --
    
        private static string GetOperand<T>(Expression<Func<T, object>> exp)
        {
          if (!( exp.Body is MemberExpression body ))
          {
            var ubody = (UnaryExpression)exp.Body;
            body = ubody.Operand as MemberExpression;
          }
    
          var operand = body.ToString();
    
          return operand.Substring(2);
    
        }
    
        private static MemberExpression GetMemberExpression<T>(ParameterExpression parameter, string propName)
        {
          if (string.IsNullOrEmpty(propName))
          {
            return null;
          }
    
          var propertiesName = propName.Split('.');
          if (propertiesName.Count() == 2)
          {
            return Expression.Property(Expression.Property(parameter, propertiesName[0]), propertiesName[1]);
          }
    
          return Expression.Property(parameter, propName);
        }
    
        private static Expression<Func<T, bool>> Includes<T>(object fieldValue, ParameterExpression parameterExpression, MemberExpression memberExpression ,Type type)
        {
          var safetype= Nullable.GetUnderlyingType(type) ?? type;
    
          switch (safetype.Name.ToLower())
          {
            case  "string":
              var strlist = (IEnumerable<string>)fieldValue;
              if (strlist == null || strlist.Count() == 0)
              {
                return x => true;
              }
              var strmethod = typeof(List<string>).GetMethod("Contains", new Type[] { typeof(string) });
              var strcallexp = Expression.Call(Expression.Constant(strlist.ToList()), strmethod, memberExpression);
              return Expression.Lambda<Func<T, bool>>(strcallexp, parameterExpression);
            case "int32":
              var intlist = (IEnumerable<int>)fieldValue;
              if (intlist == null || intlist.Count() == 0)
              {
                return x => true;
              }
              var intmethod = typeof(List<int>).GetMethod("Contains", new Type[] { typeof(int) });
              var intcallexp = Expression.Call(Expression.Constant(intlist.ToList()), intmethod, memberExpression);
              return Expression.Lambda<Func<T, bool>>(intcallexp, parameterExpression);
            case "float":
              var floatlist = (IEnumerable<float>)fieldValue;
              if (floatlist == null || floatlist.Count() == 0)
              {
                return x => true;
              }
              var floatmethod = typeof(List<int>).GetMethod("Contains", new Type[] { typeof(int) });
              var floatcallexp = Expression.Call(Expression.Constant(floatlist.ToList()), floatmethod, memberExpression);
              return Expression.Lambda<Func<T, bool>>(floatcallexp, parameterExpression);
            default:
              return x => true;
          }
          
        }
        private static Expression<Func<T, bool>> Between<T>(object fieldValue, ParameterExpression parameterExpression, MemberExpression memberExpression, Type type)
        {
          
          var safetype = Nullable.GetUnderlyingType(type) ?? type;
          switch (safetype.Name.ToLower())
          {
            case "datetime":
              var datearray = ( (string)fieldValue ).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
              var start = Convert.ToDateTime(datearray[0] + " 00:00:00", CultureInfo.CurrentCulture);
              var end = Convert.ToDateTime(datearray[1] + " 23:59:59", CultureInfo.CurrentCulture);
              var greater = Expression.GreaterThan(memberExpression, Expression.Constant(start, type));
              var less = Expression.LessThan(memberExpression, Expression.Constant(end, type));
              return Expression.Lambda<Func<T, bool>>(greater, parameterExpression)
                .And(Expression.Lambda<Func<T, bool>>(less, parameterExpression));
            case "int":
            case "int32":
              var intarray = ( (string)fieldValue ).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
              var min = Convert.ToInt32(intarray[0] , CultureInfo.CurrentCulture);
              var max = Convert.ToInt32(intarray[1], CultureInfo.CurrentCulture);
              var maxthen = Expression.GreaterThan(memberExpression, Expression.Constant(min, type));
              var minthen = Expression.LessThan(memberExpression, Expression.Constant(max, type));
              return Expression.Lambda<Func<T, bool>>(maxthen, parameterExpression)
                .And(Expression.Lambda<Func<T, bool>>(minthen, parameterExpression));
            case "decimal":
              var decarray = ( (string)fieldValue ).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
              var dmin = Convert.ToDecimal(decarray[0], CultureInfo.CurrentCulture);
              var dmax = Convert.ToDecimal(decarray[1], CultureInfo.CurrentCulture);
              var dmaxthen = Expression.GreaterThan(memberExpression, Expression.Constant(dmin, type));
              var dminthen = Expression.LessThan(memberExpression, Expression.Constant(dmax, type));
              return Expression.Lambda<Func<T, bool>>(dmaxthen, parameterExpression)
                .And(Expression.Lambda<Func<T, bool>>(dminthen, parameterExpression));
            case "float":
              var farray = ((string)fieldValue).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
              var fmin = Convert.ToDecimal(farray[0], CultureInfo.CurrentCulture);
              var fmax = Convert.ToDecimal(farray[1], CultureInfo.CurrentCulture);
              var fmaxthen = Expression.GreaterThan(memberExpression, Expression.Constant(fmin, type));
              var fminthen = Expression.LessThan(memberExpression, Expression.Constant(fmax, type));
              return Expression.Lambda<Func<T, bool>>(fmaxthen, parameterExpression)
                .And(Expression.Lambda<Func<T, bool>>(fminthen, parameterExpression));
            case "string":
              var strarray = ( (string)fieldValue ).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
              var smin = strarray[0];
              var smax = strarray[1];
            
              var strmethod = typeof(string).GetMethod("Contains");
              var mm = Expression.Call(memberExpression, strmethod, Expression.Constant(smin, type));
              var nn = Expression.Call(memberExpression, strmethod, Expression.Constant(smax, type));
    
    
              return Expression.Lambda<Func<T, bool>>(mm, parameterExpression)
                .Or(Expression.Lambda<Func<T, bool>>(nn, parameterExpression));
            default:
              return x => true;
          }
    
        }
    
    
    
        private static Expression<Func<T, bool>> Any<T, T2>(object fieldValue, ParameterExpression parameterExpression, MemberExpression memberExpression)
        {
          var lambda = (Expression<Func<T2, bool>>)fieldValue;
          var anyMethod = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
          .First(m => m.Name == "Any" && m.GetParameters().Count() == 2).MakeGenericMethod(typeof(T2));
    
          var body = Expression.Call(anyMethod, memberExpression, lambda);
    
          return Expression.Lambda<Func<T, bool>>(body, parameterExpression);
        }
    
        private static PropertyDescriptor GetProperty(PropertyDescriptorCollection props, string fieldName, bool ignoreCase)
        {
          if (!fieldName.Contains('.'))
          {
            return props.Find(fieldName, ignoreCase);
          }
    
          var fieldNameProperty = fieldName.Split('.');
          return props.Find(fieldNameProperty[0], ignoreCase).GetChildProperties().Find(fieldNameProperty[1], ignoreCase);
    
        }
        #endregion
      }
    
      internal class SwapVisitor : ExpressionVisitor
      {
        private readonly Expression from, to;
        public SwapVisitor(Expression from, Expression to)
        {
          this.from = from;
          this.to = to;
        }
        public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
      }
      public enum OperationExpression
      {
        equal,
        notequal,
        less,
        lessorequal,
        greater,
        greaterorequal,
        contains,
        beginwith,
        endwith,
        includes,
        between,
        any
      }
    }

    View Code

     1 public async Task<JsonResult> GetData(int page = 1, int rows = 10, string sort = "Id", string order = "asc", string filterRules = "")
     2     {
     3       try
     4       {
     5         var filters = PredicateBuilder.FromFilter<Company>(filterRules);
     6         var total = await this.companyService
     7                              .Query(filters)
     8                              .AsNoTracking()
     9                              .CountAsync()
    10                               ;
    11         var pagerows = (await this.companyService
    12                              .Query(filters)
    13                               .AsNoTracking()
    14                            .OrderBy(n => n.OrderBy(sort, order))
    15                            .Skip(page - 1).Take(rows)
    16                            .SelectAsync())
    17                            .Select(n => new
    18                            {
    19                              Id = n.Id,
    20                              Name = n.Name,
    21                              Code = n.Code,
    22                              Address = n.Address,
    23                              Contect = n.Contect,
    24                              PhoneNumber = n.PhoneNumber,
    25                              RegisterDate = n.RegisterDate.ToString("yyyy-MM-dd HH:mm:ss")
    26                            }).ToList();
    27         var pagelist = new { total = total, rows = pagerows };
    28         return Json(pagelist);
    29       }
    30       catch(Exception e) {
    31         throw e;
    32         }
    33 
    34     }

    配合使用的代碼

    • 對於固定查詢邏輯的封裝和復用,當然除了復用還可以明顯的提高代碼的可讀性.
    public class OrderSalesQuery : QueryObject<Order>
    {
        public decimal Amount { get; set; }
        public string Country { get; set; }
        public DateTime FromDate { get; set; }
        public DateTime ToDate { get; set; }
    
        public override Expression<Func<Order, bool>> Query()
        {
            return (x => 
                x.OrderDetails.Sum(y => y.UnitPrice) > Amount &&
                x.OrderDate >= FromDate &&
                x.OrderDate <= ToDate &&
                x.ShipCountry == Country);
        }
    }

    查看訂單的銷售情況,條件 金額,國家,日期

    var orderRepository = new Repository<Order>(this);
        
    var orders = orderRepository
        .Query(new OrderSalesQuery(){ 
            Amount = 100, 
            Country = "USA",
            FromDate = DateTime.Parse("01/01/1996"), 
            ToDate = DateTime.Parse("12/31/1996" )
        })
        .Select();

    調用查詢方法

    public class CustomerLogisticsQuery : QueryObject<Customer>
    {
        public CustomerLogisticsQuery FromCountry(string country)
        {
            Add(x => x.Country == country);
            return this;
        }
    
        public CustomerLogisticsQuery LivesInCity(string city)
        {   
            Add(x => x.City == city);
            return this;
        }
    }

    客戶查詢 根據國家和城市查詢

    public class CustomerSalesQuery : QueryObject<Customer>
    {
        public CustomerSalesQuery WithPurchasesMoreThan(decimal amount)
        {
            Add(x => x.Orders
                .SelectMany(y => y.OrderDetails)
                .Sum(z => z.UnitPrice * z.Quantity) > amount);
    
            return this;
        }
    
        public CustomerSalesQuery WithQuantitiesMoreThan(decimal quantity)
        {
            Add(x => x.Orders
                .SelectMany(y => y.OrderDetails)
                .Sum(z => z.Quantity) > quantity);
    
            return this;
        }
    }

    客戶的銷售情況,金額和數量

    var customerRepository = new Repository<Customer>(this);
    
    var query1 = new CustomerLogisticsQuery()
        .LivesInCity("London");
    
    var query2 = new CustomerSalesQuery()
        .WithPurchasesMoreThan(100)
        .WithQuantitiesMoreThan(10);
    
    customerRepository
        .Query(query1.And(query2))
        .Select()
        .Dump();

    復用上面的定義的查詢方法

    以上這些都是改項目提供的方法,非常的好用

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • JVM 總結

    JVM 總結

    JVM GC 總結。

    周志明大大的《深入理解Java虛擬機》出第三版了,早早的買了這本書,卻一直沒有花時間看。近來抽空溫習了一下,感覺又有了新的收穫。這裏簡單總結下。

    GC的由來

    由於的動態性,操作系統將堆交由給了開發者自己管理,手動申請,手動釋放。對於C++,則是將這個權限繼續交給了開發者,而對於Java,則是將這個過程自動化了。為什麼要釋放內存呢?最簡單的原因就是操作系統一共給你了4G的內存空間,你需要的時候,就去借用。有借有還,再借不難,只借不還,最後4G內存空間被用完了,你就無法再申請新的內存了。內存泄漏,就是只借不還。

    JVM在操作系統與開發者之間又封裝了一層,間接的接管了內存的劃分。同時也將堆統一管理起來,使得開發者只管借用內存,由JVM負責回收,了解JVM的回收機制,明白它的原理,能讓開發者在不同的場景下,定製不同的回收規則,提高回收效率。

    關於GC的思考

    如果讓我設計一個能自動回收垃圾的虛擬機,我會怎麼設計呢?

    • 什麼時候開始回收?
    • 怎麼判斷這部分內存可以回收?
    • 怎麼回收這部分的垃圾?

    這3個問題,也是JVM開發者一直在思考的問題。之前簡單了解過JVM,就知道JVM會有Stop The World的問題,這對於用戶體驗來說非常不好,其根本原因便是因為在回收垃圾的時候,用戶線程可能會修改這部分內存,如果不暫停用戶線程,則可能會導致嚴重的問題,而如何減少Stop The World的時候,甚至讓其消失,是各個垃圾回收器一直追求的目標。

    哪些內存可以回收?

    對於一個對象來說,當不存在任何一個引用能夠訪問到這個對象的時候,則說明這個對象可以進行回收。因為沒有任何引用指向這個對象,那麼這個對象就不能被讀或寫。

    • 引用計數法

      前面說判斷一個對象可以被回收的標準就是是否還有引用指向這個對象,所以最容易想到的便是引用計數法,通過判斷一個對象的引用數量即可,可是這樣無法判斷兩個循環引用的對象。

    • 可達性分析

      可達性分析指的是從目前程序中正在使用的所有引用的對象出發,循環遍歷所有能找到的對象。

      作為出發的點的這些對象,被稱為GC Roots

      GC Roots主要包括以下幾種:

      • 在虛擬機棧(比如棧幀中的本地遍歷表)中引用的對象
      • 靜態屬性引用的對象
      • 常量池引用對象(比如
        String Table
      • 本地方法棧引用的對象
      • Java虛擬機內部的引用對應的對象
      • 所有被同步鎖持有的對象

      總體來說,就是當前程序中正在被使用的引用所指向的對象會被作為GC Roots

    GC Roots出發,依次查找,就能標記出當前存活的對象。但是標記這個過程,細節上依然存在問題:

    • STW : 標記是通過引用查找對象的,如果在標記過程中,用戶修改了引用的對象,那麼會導致不可預估的後果,因此一般標記過程中,是會STW

    • 跨代標記 : 現在的垃圾回收器,大多數都是分代,或者分區域回收的,也就是說,可能進行垃圾回收的時候,不是標記所有的垃圾,而是標記一部分,比如老年代或者新生代。此時就存在一個問題,跨代引用。比如一個新生代的對象,僅僅被一個老年代對象引用的話,對於Yong GC來說,是不會掃描老年代對象的,這個時候就會造成誤判。解決這個誤判的方法便是記憶集(Remembered Set),記憶集通過AOP技術生成寫屏障來維護。

      前面說了從GC Roots開始掃面,那分代收集的,怎麼知道哪些對象是新生代的,哪些對象是老年代的呢?因為GC Roots是包含了所有引用的。後面想想,其實對象的分代信息是存放在對象頭裡面的。在掃描GC Roots的時候,只保留新生代的對象即可。這樣基本能保證掃描到的是新生代對象,然後老年代對新生代引用交給記憶集實現就行(自己的猜測,沒有證據)

      JVM書中說道通過AOP生成的寫屏障會使得只要有更新操作,無論更新的是不是老年代對新生代對象的引用,都會使卡表變髒,不過這樣的代價相對來說是能接受的。

    • GC Roots 需要掃描的引用過多 :隨着現在Java應用越做越大,Java堆也越來越大,GC Roots的掃描是需要STW的,如果每次GC都逐個掃描,會非常的浪費時間。解決這個問題的辦法就是OopMap,使用OopMap記錄應用程序所存放的引用,每次需要GC的時候掃描這個OopMap即可生成對應的GC RootsOopMap通過安全點和安全區域來維護,只有在安全點或安全區域的時候,才更新OopMap和進行垃圾回收。

    • 併發標記過程可能丟失存活的對象 :從CMSG1,都將從GC Roots出發標記存活對象的過程修改成併發的,這樣會需要解決的問題就是標記過程中如果用戶修改了對象的引用,可能會導致本應該存活的對象”丟失“(可以通過三色標記分析),相應的解決方案便是破壞存活對象消失的必要條件,分別是增量更新(Incremental Upate)和原始快照(Snapshot At The Begin,SATB),增量更新破壞的是第一個條件,每插入一個引用,就都記錄下來,而原始快照破壞的是第二個條件,每刪除一個,都將其記錄下來。

      增量更新和併發快照也是通過前面所說的AOP技術生成寫屏障來維護

    通過以上分析以及解決方案,基本明白了怎麼標記那些內存可以回收,接下來需要分析的就是什麼時候開始回收

    什麼時候開始內存回收?

    對於內存回收來說,開始也需要有一定的講究,理論上來說,隨時隨地都可以開始內存回收,但是如果回收時使用的內存過多,會導致GC時間過程,進而STW時間也會很長,如果回收過於頻繁,又會導致吞吐量下降,畢竟每次掃描GC Roots都回STW的。

    同時,前面還說過,對於用戶線程來說,需要將用戶線程運行到安全點,更新對應的OopMap,才能開始垃圾回收。

    因此,對應何時GC,有以下幾點分析:

    • 對於新生代來說,一般新生代滿了(Eden + Survivor1)就會開始進行(Yong/Minor GC

    • 對於老年代來說,一般是老年代滿了了會開始Full/Major GC

      注意:這裏的滿了,需要根據具體的回收器不同,來衡量真正的滿,對於沒有併發過程的GC,老年代滿一般指的是真正到達100%,已經無法分配內存了,對於有併發過程的GC,則需要預留出來空間給用戶線程在併發過程中同時申請內存,如果預留內存過小,則會使用非併發垃圾回收器進行Full GC

      CMS: -XX:CMSInitiatingOccupancyFraction 設置,默認92% (JDK 8),表示當老年代垃圾佔用到92%就開始老年代回收, JDK 9后便無法使用CMS

      G1: -XX:G1ReservePercent設置,默認為10,表示當整個Java堆使用到達90%,就開始回收。同時配合的參數還有-XX:InitiatingHeapOccupancyPercent=n,默認值為45,表示使用率到達45%就啟動標記周期。這裏的GCMixed GC

      一般來說,只有CMS才有Major GC,其他老年代GC都會回收整個Java堆,也稱為Full GC

    • 統計得到的Minor GC晉陞到老年代的平均大小大於老年代剩餘的空間。(JDK 6 之後已經刪除了擔保規則)

    • GC併發失敗(concurrent mode failure): 情況如前面說的,併發標記過程中,又出現了新生代晉陞的情況,但是此時老年代剩下的內存不足夠放下晉陞的對象的時候,會生導致Full GC

      這裏的Full GC和情況1中說到達預留空間的GC不一樣,情況1是正常進行的GC,而這個併發失敗卻是GC過程中出現了異常,一般需要切換到非併發GC,此時性能會大大下降

    • 方法區區域被使用完畢:JDK 8之後將方法區從Perm Gen替換成了元空間,一般來說元空間大小理論上等於本地內存大小,不過元空間有一個默認初始值,到達默認初始值后,會通過Full GC擴大

      注意:G1只有Yong GCMixed GC。沒有Full GC的概念,也就是說如果需要回收方法區的話,只能退化為Serial GC進行Full GC

      CMS可以通過-XX:+CMSClassUnloadingEnabled設置併發回收方法區

    • 最大連續空間裝不下大對象:對於CMS,基於標記-清除算法來說,即使空間足夠,但是由於內存碎片,裝不下分配的大對象時,會進行一次Full GC,對於G1來說,當分配巨型對象的時候,如果在老年代無法找到連續的Humongous的時候,會進行Full GC

    • 用戶執行System.gc(),可以通過-XX:+DisableExplicitGC屏蔽

    怎麼回收這些內存

    最後一步便是怎麼回收這些內存。怎麼回收,書中介紹不多,總體來說有以下三種:

    • 標記-清除(
      Mark-Sweep):最原始的方法,實現簡單,不用移動對象,很容易做到不用
      Stop The Word,但是缺點也很致命,容易產生內存碎片。標記清除的速度一般,
      Mark階段與活對象的數量成正比,
      Sweep階段與整堆大小成正比。目前只有
      CMS使用這種回收方案
    • 標記-複製(
      Mark-Copying):基於標記-清除修改的垃圾回收算法,需要移動對象。 前期標記,然後複製活下來的對象到另一個區域,再總體回收整塊區域。標記複製算法對於新生代這種專門放朝生夕死的對象效率非常高,因為存活下來的對象少,所以
      Mark階段和
      Copying階段花費的時間都會比較少,幾乎所有的分代
      GC新生代都是使用的這種算法
    • 標記-整理(
      Mark-Compact): 基於標記-清除算法修改的垃圾回收器,需要移動對象。前期標記,然後將所有對象移動到一起,再對剩餘的區域進行回收,速度最慢,但是不會產生內存碎片。

    對於新生代使用標記-複製算法,是毋庸置疑的。但是對於老年代,使用標記清除還是標記整理,需要有一定的考量。因為使用標記-清除,不用移動對象,速度會相對來說比較快,但是由於存在內存碎片,無法使用指針碰撞的方式分配內存,而不得不使用“分區空閑分配鏈表”來解決內存分配的問題,這樣會對在內存分配帶來一定的效率影響,而標記-整理算法需要移動對象,特別是對於老年代這種大對象來說,移動這些對象將是一種極為負重的操作,但是標記-整理不會產生內存碎片。

    因此,基於以上考慮,對於CMS這種側重響應速度,致力於減少STW時間的回收器來說,選擇了標記-清除算法,但是由於內存分配是一個非常頻繁的操作,使用”分區空閑分配鏈表”會降低整個垃圾回收器的吞吐量,因此,對於Parllel Scavenge這種注重回收吞吐的垃圾回收器來說,選擇了標記-整理算法。當然,對於G1則是吞吐和響應速度都比較注重,權衡之下,選擇了標記-整理(全局)算法。

    GC的概念,到這裏基本總結完畢,但是,如果僅僅是理論,只是讓我們記着一些概念性的東西,接下來,我會結合CMSG1GC日誌以及《深入理解JVM》第四章的內容,聊一聊如何分析以及查看GC過程,簡單介紹如果進行GC調優。

    個人公眾號:

    不定期更新一些經典Java書籍總結。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • 保護復活島生態文化 智利限遊客停留最多30日

    摘錄自2018年7月30日自由時報報導

    智利國會今年3月立法,強化旅客登復活島規範,並限制外來人口數量,限令於8月1日正式生效。

    法新社報導,根據新法令,外籍人士或非當地原住民的智利人,在島上停留的期限從原本的90天降至30天,僅原住民拉帕努伊人(Rapa Nui)的直系血親,才可在島上長期居住。此外,公務員、政府相關組織人士,以及在當地從事獨立經濟活動者和家人,也獲准在島上居住。觀光客則須在抵達時,出示飯店預約證明或當地人的邀請函,日後也將訂定島上最高人數限制。

    去年的人口普查資料顯示,島上居民已增至7750人,比數十年前多出一倍以上。

    此外,人潮除了破壞文化樣貌和生態平衡,也使當地的基礎服務瀕臨極限。當地政府的環境顧問古特雷茲(Ana Maria Gutierrez)舉例說明,復活島居民10年前每年約產生1.5噸廢棄物,但如今一天就高達2.5噸,因為居民的環保意識相當薄弱。

    市長艾德蒙茲一方面認同新法是「好的開始」,但另一方面卻直指新法「規模太小」,不足以保護島上文化、遺產和特色,甚至指稱許多拉帕努伊人企盼的是,全面禁止新住民到來。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • 豐田CES展將推燃料電池概念車 加氫只需3分鐘

    據悉,日本豐田汽車(Toyota)將在2014年的美國消費電子展(CES)上,主打燃料電池概念車(FCV),展示該公司研發汽車清淨能源的成果。豐田表示,這款概念車可跑310英哩(499公里),加氫時間只需3分鐘,新車預定2015年開賣。

    豐田的燃料電池車原本預計要價5萬美元,實際定價可能接近10萬美元。新車改良油電混合車,用燃料電池取代汽油引擎,電動馬達和其他零件則和現行的油電混合相去不遠。

    本站,福特汽車(Ford Motor)2日宣佈,C-Max Solar Energi太陽能概念車將在CES亮相。

    C-Max純靠電力可行駛大約21英里(34公里),最遠行程620英里左右。C-Max的車頂配備SunPower公司300至350瓦的太陽能電池,預告未來可能生產不需插電的量產充電車。福特表示,一整天的日光照射約等於4小時的充電量,大約有8瓩(KW)。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • 思考:如何保證服務穩定性?

    最近一直在忙618大促的全鏈路壓測&穩定性保障相關工作,結果618還未開始,生產環境就出了幾次生產故障,且大多都是和系統穩定性、性能相關的bad case。

    生產全鏈路壓測終於告一段落,抽出時間將個人收集的穩定性相關資料整理review了一遍,順帶從不同的維度,談談穩定性相關的“務虛”認知和思考。。。

     

    一、SLA!

    在開始談穩定性保障之前,我們先來聊聊業內經常提及的一個Topic:SLA!

    業內喜歡用SLA (服務等級協議,全稱:service level agreement)來衡量系統的穩定性,對互聯網公司來說就是網站服務可用性的一個保證。

    9越多代表全年服務可用時間越長服務越可靠,停機時間越短。就以一個標準99.99%為例,停機時間52.6分鐘,平均到每周也就是只能有差不多1分鐘的停機時間,

    也就是說網絡抖動這個時間可能就沒了。保證一個系統四個9或者更高的五個9,需要一套全體共識嚴格標準的規章制度,沒有規矩不成方圓。創建的規範有如下幾種:

    1、研發規範、自身穩定;

    2、事務中不能包含遠程調用;

    3、超時時間和重試次數要合理;

    4、表數據操作必須double check,合理利用索引,避免出現慢查詢、分庫分表不走分表鍵;

    5、沒有有效的資源隔離, 避免不同業務共用一個線程池或連接池;

    6、合理的系統拓撲,禁止不合理的服務依賴,能去依賴就去依賴,否則同步依賴盡量改成異步弱依賴;

    7、精簡的代碼邏輯;

    8、核心路徑流程必須進行資源隔離,確保任何突發情況主流程不能受影響。

     

    二、單服務穩定性

    關鍵字:開關可控、單一職責、服務隔離、異常兜底、監控發現!

    對於穩定性來說,拋開整體系統架構設計,單就每個業務域服務的穩定性也是非常的重要。

    只有每個業務環節都穩如泰山,才可以保障整個穩定性。單服務的穩定可以從以下幾個方面來進行:

    1、禁用設計:應該提供控制具體功能是否開啟可用的配置,在相應的功能服務出現故障時,快速下線局部功能,以保證整體服務的可用性;

    2、必要的緩存:緩存是解決併發的利器,可以有效的提高系統的吞吐量。按照業務以及技術的緯度必要時可以增加多級緩存來保證其命中率;

    3、接口無狀態性:服務接口應該是無狀態的,當前接口訪問不應該依賴上層接口的狀態邏輯;

    4、接口單一職責性:對於核心功能的接口,不應該過多的耦合不屬於它的功能。如果一個接口做的事情太多應做拆分,保證單接口的穩定性和快速響應;

    5、第三方服務隔離性:任何依賴於第三方的服務(不論接口還是中間件等),都應該做到熔斷和降級,不能有強耦合的依賴;

    6、業務場景兜底方案:核心業務場景需要做到完整的兜底方法,從前端到後端都應該有兜底措施;

    7、服務監控與及時響應:每個服務應該做好對應的監控工作,如有異常應及時響應,不應累積。

     

    三、集群穩定性

    關鍵字:系統架構、部署發布、限流熔斷、監控體系、壓測機制!

    對於集群維度的穩定性來說,穩定性保障會更加複雜。單服務是局部,集群是全局。一個見微知著,一個高瞻遠矚。

    1、合理的系統架構:合理的系統架構是穩定的基石;

    2、小心的代碼邏輯:代碼時刻都要小心,多擔心一點這裡會不會有性能問題,那裡會不會出現併發,代碼就不會有多少問題;

    3、優秀的集群部署:一台機器永遠會有性能瓶頸,優秀的集群部署,可以將一台機器的穩定放大無限倍,是高併發與大流量的保障;

    4、科學的限流熔斷:高併發來臨時,科學的限流和熔斷是系統穩定的必要條件;

    5、精細的監控體系:沒有監控體系,你永遠不會知道你的系統到底有多少隱藏的問題和坑,也很難知道瓶頸在哪裡;

    6、強悍的壓測機制:壓測是高併發穩定性的試金石,能提前預知高併發來臨時,系統應該出現的模樣;

    7、膽小的開發人員:永遠需要一群膽小的程序員,他們討厭bug,害怕error,不放過每一個波動,不信任所有的依賴。

     

    四、穩定性專項

    專項指的是針對某些特定場景下的特定問題而梳理出對應的方案。下面是針對一些常見的穩定性專項的概述:

    1、預案:分為定時預案和緊急預案,定時預案是大促常規操作對於一系列開關的編排,緊急預案是應對突發情況的特殊處理,都依賴於事前梳理;

    2、預熱:分為JIT代碼預熱和數據預熱,阿里內部有專門的一個產品負責這塊,通過存儲線上的常態化流量或者熱點流量進行回放來提前預熱,

      起源於某年雙十一零點的毛刺問題,原因是訪問了數據庫的冷數據rt增高導致的一系列上層限流,現在預熱已經成了大促之前的一個必要流程。

    3、強弱依賴:梳理強弱依賴是一個偏人肉的過程,但是非常重要,這是一個系統自查識別潛在風險點併為後續整理開關限流預案和根因分析的一個重要參考,

      阿里內部有一個強弱依賴檢測的平台,通過對測試用例注入RPC調用的延遲或異常來觀察鏈路的依賴變化,自動梳理出強弱依賴關係。

    4、限流降級熔斷:應對突發流量防止請求超出自身處理能力系統被擊垮的必要手段;

    5、監控告警&鏈路追蹤:監控分為業務監控、系統監控和中間件監控和基礎監控,作為線上問題發現和排查工具,重要性不言而喻。

     

    五、穩定性建設

    穩定性建設,就和基礎技術建設一樣,是一個長期迭代和不斷調整的過程,業內常見的穩定性建設類型,主要有如下幾種:

    1、容量規劃:個人感覺容量規劃在大廠里也並沒有做的很好,更多依賴的是業務方自己拍腦袋,然後全鏈路壓測期間驗證,不夠就再加機器。

    2、混沌工程:混沌工程是近幾年比較火的名詞,通過不斷給系統找麻煩來驗證並完善系統能力,阿里在這塊花了很大的精力建設紅藍軍對抗攻防,進行定期和不定期的演練,

      最後以打分的形式來給各個部門系統做排名,除了系統層面的故障演練外還有資金演練,篡改線上sql語句製造資損來測試業務監控糾錯的能力,通過製造小錯來避免大錯。

      跳轉門:混沌工程-初識

    3、流量調度:通過metric秒級監控和聚類算法實時找出異常單機來降低RPC流量權重,提升集群整體吞吐能力減少異常請求。

    4、容災&異地多活:起源於15年某施工隊將光纖挖斷帶來的支付寶故障,由此出來的三地五中心和單元化架構,異地多活本身的成本比較高,

      然後又存在數據同步的延時問題和切流帶來的臟數據問題,對於業務和技術都有比較高的要求。常見的容災有如下幾種:

      1)緩存掛掉,集群重啟緩存預熱如何處理?本地緩存,多級緩存是否可以替代?

      2)分佈式鎖,是否有開關一鍵切換?比如:ZK/ETCD編寫的分佈式鎖;

      3)大促峰值流量,如何防止外部ddos攻擊?如何識別流量類型?

      4)資源隔離:資源隔離,服務分組,流量隔離;

      5)高可用思想:避免單點設計!

      6)容錯:容錯上游,防禦下游。容錯主要需要注意如下幾點:

         6-1:外部依賴的地方都要做熔斷,避免雪崩;

         6-2:對於依賴我們的上游要限流,防止上游突發流量超過自己系統能夠扛住的最大QPS;

         6-3:對於下游既要評估好接口超時時間,防止下游接口超時異常導致自己系統被拖累;

         6-4:下游的接口要考慮各種異常情況,需要考慮中間狀態,通過引入柔性事務,確保數據最終一致。

    5、異地多活

    異地多活的本質,是數據中心架構的演進

    1)演進:單機房——雙機房——異地災備——異地多活;

    2)定義:分多個地域、多個數據中心運行線上的業務,並且每個IDC均提供在線服務;

    3)優點:彈性擴展能力、流量就近接入、靈活調度、提升可用性與用戶體驗、容災;

    4)步驟

      4-1:基礎設施:機房之間專線互聯,保證網絡質量穩定;

      4-2:持久存儲:一主三從,主IDC同步複製,異地IDC異步複製;

      4-3:中間件:DB、MQ、分佈式存儲;

      4-4:應用部署:根據應用域劃分,不同應用部署在不同地域,保持親緣性;

      4-5:流量接入與調度:網絡協議兼容,DNS,動態調度用戶就近訪問;

      4-6:監控與運維保障:專線實時監控,確保發生故障時可以觸發Failover(失效備援)和流量調度。

     

    六、穩定性思考

    關鍵字:階段工作、角色轉變!

    穩定性建設是一個演進的階段性過程,主要分為三個階段:

    1、發現問題解決問題:當問題較多時候就很被動,很多時候我們通過不斷完善監控來確保我們來快速定位問題,但仍處於被動的一方;

    2、主動尋找問題:混沌工程、破壞性測試、極限壓測、紅藍對抗等手段,一方作為創造問題方不斷挑戰系統極限,另一方見招拆招快速修復。

    3、角色轉變:這個過程中會積累很多處理問題的經驗,不斷完善系統健壯性,爭取在用戶發現問題前消滅於萌芽中。角色轉變,變被動為主動。

     

    七、推薦閱讀

    聊聊服務災備

    大促穩定性建設

    運維監控體系建設

    這樣的高可用,我不要

    高併發限流,到底限的什麼鬼

    新浪微博平台穩定性體系介紹

    StabilityGuide—穩定大於一切

    沒有預熱,不叫高併發,叫併發高

    信號量限流,高併發限流不得不說的秘密

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • 特斯拉Model S在港正式銷售 入門級57.9萬港幣

    昨(13)日有消息稱,特斯拉Model S車型已正式在香港銷售,其中入門級配置僅售57.9萬港幣。此外有報導稱,比亞迪將與ABB集團及德國戴姆勒共建全球最大電動汽車快速充電網,ABB將在未來6年內為兩家公司提供直流快速充電樁。

    上述消息為A股正紅火的新能源汽車相關概念再添一把柴。截至昨天收盤,充電樁概念逆市上漲0.67%,漲幅居前,奧特迅領漲,漲幅為7.60%。特斯拉概念表現較為抗跌僅下挫0.51%,金瑞科技漲停,比亞迪也大漲8.26%。鋰電池概念跌0.93%,其中南洋科技漲9.48%。

    證券方面人士認為,在政府大力建設新型城鎮化、發展城市公共交通的背景下,對新能源汽車的政策扶持將會給電池業、電池電機上游的鋰和稀土資源行業、充電站行業、乘用車企業帶來機遇。

    但需要注意的是,未來的上漲空間將取決於政策的落實情況和上市公司的基本面,並且投資者不可對其期望過高,尤其是目前市場處於震盪的情形下,一旦后續資金追高乏力,相關個股很難擺脫大勢的影響。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • Blazor帶我重玩前端(一)

    Blazor帶我重玩前端(一)

    寫在前面

    曾經我和前端朋友聊天的時候,我說我希望有一天可以用C#寫前端,不過當時更多的是美好的想象,而現在這一切正變得真實……

     

    什麼是Blazor

    我們知道瀏覽器可以正確解釋並執行JavaScript代碼,那麼瀏覽器是如何執行C#代碼的呢?答案是通過WebAssembly。通過WebAssembly,我們可以讓瀏覽器運行很多的高級語言,如 C#、C、C++、GO等,並使他們運行在基於內存安全的沙箱環境中。如下圖所示:

    作為一個已經五六年沒有寫過前端的.NET程序員,遇到Blazor實在是幸運中的幸運。它又讓我可以很愉快的寫前端了,而且還是用C#去寫,我也就不用再分出精力去學習其他的JS框架了。

    我們可以認為Blazor是.NET對WebAssembly的實現。通過使用Blazor,我們可以使用C#語言來取代JS去開發交互式Web UI。

    值得一提的是,Blazor是由Browser和Razor這兩個單詞合併而成的,意思就是Blazor可以基於客戶端執行Razor視圖后將HTML呈現給瀏覽器。所以想要更好的理解Blazor,就要首先更好的了解瀏覽器和Razor。

    Blazor有以下幾個優點:
    • 使用C#來取代JavaScript創建豐富的交互式UI
    • 基於.NET及其生態編寫服務器端和客戶端應用程序邏輯
    • 糅合現有HTML和CSS技術,提供了廣泛的瀏覽器支持,包括移動瀏覽器(注意:Blazor取代的是基於JavaScript的UI交互,而其他部分如HTML、CSS,這些是我們的技術基礎)
    • 與現代託管平台(例如Docker)集成。
    • Blazor是開源的,其源碼位置在GitHub上

    另外需要注意的,Blazor和Silverlight不可混為一談,Blazor是基於開放標準而構建的,本身不需要任何額外插件。而Silverlight帶有太多自有特性,所以不得不在瀏覽器上安裝插件以更好的支持其運行。

    什麼是WebAssembly

    概覽

    WebAssembly是一種二進制格式的指令集,其設計目標是能夠在解釋或者將其編譯為本地機器代碼並執行他們的機器上運行,這類似於我們.NET編譯后的IL。

    WebAssembly可以作為編譯高級編程語言的可移植目標,通過節省大小和加載時間,充分利用各種平台(移動平台和IOT平台)上的通用應用功能,使得WebAssembly可以以接近於本機(接近於本機的英語單詞是:near-native,在語言學里意思是精通語言的人,所說的話和說母語的人沒有什麼區別)的運行速度運行。

    支持

    WebAssembly已經獲得了大部分瀏覽器的支持。詳細內容可以移步至Can I Use

    手寫一個例子

    接下來我們看一個例子,方便起見,我們直接使用在線的WebAssembly編譯工具,地址是:https://mbebenita.github.io/WasmExplorer/。目前,這個工具只支持C和C++。不過也沒有什麼關係,我們寫一個簡單的方法用於測試即可。

    • 首先我們定義了一個計算兩個數和的方法:
    1 int Addition(int a, int b)
    2 {
    3   return a + b; 
    4 }
    • 然後點擊COMPILE

    在中間的框里會生成WAT(即WebAssembly文本格式)的代碼,最右邊的是二進制了。中間的代碼部分可以幫助我們查看在編譯的過程中發生了什麼,會看到生成了一個名為_Z8Additionii的function,其中8表示這個方法名的長度,後面的i表示有多個參數,接下來我們會去調用它。

     1 (module
     2  (table 0 anyfunc)
     3  (memory $0 1)
     4  (export "memory" (memory $0))
     5  (export "_Z8Additionii" (func $_Z8Additionii))
     6  (func $_Z8Additionii (; 0 ;) (param $0 i32) (param $1 i32) (result i32)
     7   (i32.add
     8    (get_local $1)
     9    (get_local $0)
    10   )
    11  )
    12 )
    • 點擊Download,下載.WAT文件

    • 接下來我們再寫一個HTML網頁出來,就用那種最簡單的HTML代碼,代碼如下:
     1 <HTML>
     2 <HEAD>
     3     <TITLE>WebAssembly Sample: Call C++ Code</TITLE>
     4     <script type="text/javascript">
     5         let addition = fetch('test.wasm')
     6             .then(response => response.arrayBuffer())
     7             .then(buffer => WebAssembly.compile(buffer))
     8             .then(module => { return new WebAssembly.Instance(module) })
     9             .then(instance => { addition = instance.exports._Z8Additionii }); 
    10     </script>
    11 </HEAD>
    12 <BODY BGCOLOR="FFFFFF">
    13     <h1>WebAssembly Sample: Call C++ Code</h1>
    14 </BODY>
    15 </HTML>
    • 最終的效果圖
    
    

    通過以上示例,我們基本上對Blazor和WebAssembly的部分運行機制有了一個比較清晰的認識了,接下來,我們繼續討論有關Blazor的內容。

    參考鏈接:

    https://webassembly.org/

    https://webassembly.github.io/spec/js-api/index.html

    https://caniuse.com/#search=wasm

    https://webassembly.github.io/spec/js-api/index.html

    
    

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • 特斯拉向企業客戶推出電動車租賃計劃 以提振需求

    特斯拉(Tesla)於8日推出面向企業客戶的S型豪華跑車租賃計劃,以爭取更多的美國國內客戶。消費者可分期支付車款,在租期截止後可選擇支付餘款並擁有汽車,也可選擇將汽車歸還。有意願者在網上僅需5分鐘就可提交一份租賃申請。

    此舉正逢S型豪華跑車在美國的銷量開始放慢增長。今年第一季度,S型車在美國市場售出4700輛,比去年同期僅增長1%。特斯拉公司表示,相比去年向個人客戶推出的一種功能相當於租賃計劃的推銷模式,面向中小企業的租賃計劃更為直接透明,租賃費可在企業納稅時扣抵。

    S型全電動跑車是特斯拉的主打產品,去年登上美國最暢銷豪華車榜單。但市場分析人士認為,美國消費者對該系列汽車的興趣開始消退,需求進入穩定階段,因此開拓國際市場對特斯拉來說尤為重要。他們預計,中國消費者早期的強勁需求可能是提升S型車銷量的最大因素。

    特斯拉電池工廠計畫面臨危機

    另外,特斯拉早前宣佈將耗資數十億美元建造全球最大的電池工廠,以求大幅降低電動車成本,幫助特斯拉進入中端市場。不過,特斯拉新聞發言人西恩曾表示,特斯拉電動車電池的兩項重要原料資源,現在均面臨缺乏危機。

    石墨是特斯拉電動車電池最重要的原料之一,特斯拉目前使用的石墨原料絕大部分來自日本和歐洲市場,且都是化學合成石墨,而並非天然開採的石墨。美國稀有金屬諮詢分析公司Technology Metal預計,未來需要新增6個石墨礦才能夠滿足不斷增長的電池生產對原材料的需求,石墨的價格則會繼續上漲。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

  • 特斯拉與阿里及租車公司合作 方便中國民眾購買電動車

    電動汽車大廠特斯拉(Tesla)日前分別與阿里巴巴旗下支付寶及一嗨租車合作,中國大陸客戶不僅可用支付寶訂車付款,還可以租用特斯拉的電動車。首批兩輛Model S電動車已經正式交付,下一批將會在5月份到達。

    除了阿里巴巴與一嗨租車搶著與特斯拉合作,電信巨頭之一的中國聯通日前也傳出與特斯拉中國合作的消息,將由聯通為特斯拉的電動車提供車載資訊服務。

    其實電動車在大陸已經銷售多年,長城、比亞迪等大陸本土車廠都有製造電動汽車,但整體銷量始終未見起色。特斯拉卻在大陸獲得了超高人氣。

    根據中國汽車工業協會的統計,2013年全國的純電動車銷量只有14,604輛,僅占全年汽車銷量的不到1%。此外,在大陸的電動車銷售業績中,大多屬於商用載客或公務車輛,民眾主動購買的純電動車數量相當稀少。

    特斯拉去年全年銷售了23,500輛電動汽車。特斯拉創辦人Elon Musk日前表示,預估今年公司在全球的銷量將超過30,000輛,其中有5,000輛會來自大陸市場。該公司更計畫在大陸設立超級充電站與電動車產線。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

  • 面試官:換人!他連哈希扣的都不懂

    面試官:換人!他連哈希扣的都不懂

    前言

    相信你面試的時候,肯定被問過 hashCode 和 equals 相關的問題 。如:

    • hashCode 是什麼?它是怎麼得來的?有什麼用?
    • 經典題,equals 和 == 有什麼區別?
    • 為什麼要重寫 equals 和 hashCode ?
    • 重寫了 equals ,就必須要重寫 hashCode 嗎?為什麼?
    • hashCode 相等時,equals 一定相等嗎?反過來呢?

    好的,上面就是靈魂拷問環節。其實,這些問題仔細想一下也不難,主要是平時我們很少去思考它。

    正文

    下面就按照上邊的問題順序,一個一個剖析它。扒開 hashCode 的神秘面紗。

    什麼是 hashCode?

    我們通常說的 hashCode 其實就是一個經過哈希運算之後的整型值。而這個哈希運算的算法,在 Object 類中就是通過一個本地方法 hashCode() 來實現的(HashMap 中還會有一些其它的運算)。

    public native int hashCode();
    

    可以看到它是一個本地方法。那麼,想要了解這個方法到底是用來幹嘛的,最直接有效的方法就是,去看它的源碼註釋。

    下邊我就用我蹩腳的英文翻譯一下它的意思。。。

    返回當前對象的一個哈希值。這個方法用於支持一些哈希表,例如 HashMap 。

    通常來講,它有如下一些約定:

    • 若對象的信息沒有被修改,那麼,在一個程序的執行期間,對於相同的對象,不管調用多少次 hashCode 方法,都應該返回相同的值。當然,在相同程序的不同執行期間,不需要保持結果一致。
    • 若兩個對象的 equals 方法返回值相同,那麼,調用它們各自的 hashCode 方法時,也必須返回相同的結果。(ps: 這句話解答了上邊的一些問題,後面會用例子來證明這一點)
    • 當兩個對象的 equals 方法返回值不同時,那麼它們的 hashCode 方法不用保證必須返回不同的值。但是,我們應該知道,在這種情況下,我們最好也設計成 hashCode 返回不同的值。因為,這樣做有助於提高哈希表的性能。

    在實際情況下,Object 類的 hashCode 方法在不同的對象中確實返回了不同的哈希值。這通常是通過把對象的內部地址轉換為一個整數來實現的。

    ps: 這裏說的內部地址就是指物理地址,也就是內存地址。需要注意的是,雖然 hashCode 值是依據它的內存地址而得來的。但是,不能說 hashCode 就代表對象的內存地址,實際上,hashCode 地址是存放在哈希表中的。

    上邊的源碼註釋真可謂是句句珠璣,把 hashCode 方法解釋的淋漓盡致。一會兒我通過一個案例說明,就能明白我為什麼這樣說了。

    什麼是哈希表?

    上文中提到了哈希表。什麼是哈希表呢?我們直接看百度百科的解釋。

    用一張圖來表示它們的關係。

    左邊一列就是一些關鍵碼(key),通過哈希函數,它們都會得到一個固定的值,分別對應右邊一列的某個值。右邊的這一列就可以認為是一張哈希表。

    而且,我們會發現,有可能有些 key 不同,但是它們對應的哈希值卻是一樣的,例如 aa,bb 都指向 1001 。但是,一定不會出現同一個 key 指向不同的值。

    這也非常好理解,因為哈希表就是用來查找 key 的哈希地址的。在 key 確定的情況下,通過哈希函數計算出來的 哈希地址,一定也是確定的。如圖中的 dd 已經確定在 1002 位置了,那麼就不可能再佔據 1003 位置。

    思考一下,如果有另外一個元素 ee 來了,它的哈希地址也落在 1002 位置,怎麼辦呢?

    hashCode 有什麼用?

    其實,上圖就已經可以說明一些問題了。我們通過一個 key 計算出它的 hashCode 值,就可以唯一確定它在哈希表中的位置。這樣,在查詢時,就可以直接定位到當前元素,提高查詢效率。

    現在我們假設有這樣一個場景。我們需要在內存中的一塊兒區域存放 10000 個不同的元素(以aa,bb,cc,dd 等為例)。那怎麼實現不同的元素插入,相同的元素覆蓋呢?

    我們最容易想到的方法就是,每當存一個新元素時,就遍歷一遍已經存在的元素,看有沒有相同的。這樣雖然也是可以實現的,但是,如果已經存在了 9000 個元素,你就需要去遍歷一下這 9000 個元素。很明顯,這樣的效率是非常低下的。

    我們轉換一種思路,還是以上圖為例。若來了一個新元素 ff,首先去計算它的 hashCode 值,得出為 1003 。發現此處還沒有元素,則直接把這個新元素 ff 放到此位置。

    然後,ee 來了,通過計算哈希值得到 1002 。此時,發現 1002 位置已經存在一個元素了。那麼,通過 equals 方法比較它們是否相等,發現只有一個 dd 元素,很明顯和 ee 不相等。那麼,就把 ee 元素放到 dd 元素的後邊(可以用鏈表形式存放)。

    我們會發現,當有新元素來的時候,先去計算它們的哈希值,再去確定存放的位置,這樣就可以減少比較的次數。如 ff 不需要比較, ee 只需要和 dd 比較一次。

    當元素越來越多的時候,新元素也只需要和當前哈希值相同的位置上,已經存在的元素進行比較。而不需要和其他哈希值不同的位置上的元素進行比較。這樣就大大減少了元素的比較次數。

    圖中為了方便,畫的哈希表比較小。現在假設,這個哈希表非常的大,例如有這麼非常多個位置,從 1001 ~ 9999。那麼,新元素插入的時候,有很大概率會插入到一個還沒有元素存在的位置上,這樣就不需要比較了,效率非常高。但是,我們會發現這樣也有一個弊端,就是哈希表所佔的內存空間就會變大。因此,這是一個權衡的過程。

    有心的同學可能已經發現了。我去,上邊的這個做法好熟悉啊。沒錯,它就是大名鼎鼎的 HashMap 底層實現的思想。對 HashMap 還不了解的,趕緊看這篇文章理一下思路。HashMap 底層實現原理及源碼分析

    所以,hashCode 有什麼用。很明顯,提高了查詢,插入元素的效率呀。

    equals 和 == 有什麼區別?

    這是萬年不變,經久不衰的經典面試題了。讓我油然想起,當初為了面試,背誦過的面經了,簡直是一把心酸一把淚。現在還能記得這道題的標準答案:equals 比較的是內容, == 比較的是地址。

    當時,真的就只是背答案,知其然而不知其所以然。再往下問,為什麼要重寫 equals ,就懵逼了。

    首先,我們應該知道 equals 是定義在所有類的父類 Object 中的。

     public boolean equals(Object obj) {
         return (this == obj);
     }
    

    可以看到,它的默認實現,就是 == ,這是用來比較內存地址的。所以,如果一個對象的 equals 不重寫的話,和 == 的效果是一樣的。

    我們知道,當創建兩個普通對象時,一般情況下,它們所對應的內存地址是不一樣的。例如,我定義一個 User 類。

    public class User {
        private String name;
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public User() {
    
        }
    }
    
    public class TestHashCode {
        public static void main(String[] args) {
            User user1 = new User("zhangsan", 20);
            User user2 = new User("lisi", 18); 
    
            System.out.println(user1 == user2);
            System.out.println(user1.equals(user2));
        }
    }
    // 結果: false	false
    

    很明顯,zhangsan 和 lisi 是兩個人,兩個不同的對象。因此,它們所對應的內存地址不同,而且內容也不相等。

    注意,這裏我還沒有對 User 重寫 equals,實際此時 equals 使用的是父類 Object 的方法,返回的肯定是不相等的。因此,為了更好地說明問題,我僅把第二行代碼修改如下:

    //User user2 = new User("lisi", 18);
    User user2 = new User("zhangsan", 20);
    

    讓 user1 和 user2 的內容相同,都是 zhangsan,20歲。按我們的理解,這雖然是兩個對象,但是應該是指的同一個人,都是張三。但是,打印結果,如下:

    這有悖於我們的認知,明明是同一個人,為什麼 equals 返回的卻不相等呢。因此,此時我們就需要把 User 類中的 equals 方法重寫,以達到我們的目的。在 User 中添加如下代碼(使用 idea 自動生成代碼):

    public class User {
        ... //省略已知代碼
            
        @Override
        public boolean equals(Object o) {
            //若兩個對象的內存地址相同,則說明指向的是同一個對象,故內容一定相同。
            if (this == o) return true;
            //類都不是同一個,更別談相等了
            if (o == null || getClass() != o.getClass()) return false;
            User user = (User) o;
            //比較兩個對象中的所有屬性,即name和age都必須相同,才可認為兩個對象相等
            return age == user.age &&
                    Objects.equals(name, user.name);
        }
       
    }
    //打印結果:  false 	true
    

    再次執行程序,我們會發現此時 equals 返回 true ,這才是我們想要的。

    因此,當我們使用自定義對象時。如果需要讓兩個對象的內容相同時,equals 返回 true,則需要重寫 equals 方法。

    為什麼要重寫 equals 和 hashCode ?

    在上邊的案例中,其實我們已經說明了為什麼要去重寫 equals 。因為,在對象內容相同的情況下,我們需要讓對象相等。因此,不能用 Object 類的默認實現,只去比較內存地址,這樣是不合理的。

    那 hashCode 為什麼要重寫呢? 這就涉及到集合,如 Map 和 Set (底層其實也是 Map)了。

    我們以 HashMap JDK1.8的源碼來看,如 put 方法。

    我們會發現,代碼中會多次進行 hash 值的比較,只有當哈希值相等時,才會去比較 equals 方法。當 hashCode 和 equals 都相同時,才會覆蓋元素。get 方法也是如此(先比較哈希值,再比較equals),

    只有 hashCode 和 equals 都相等時,才認為是同一個元素,找到並返回此元素,否則返回 null。

    這也對應 “hashCode 有什麼用?”這一小節。 重寫 equals 和 hashCode 的目的,就是為了方便哈希表這樣的結構快速的查詢和插入。如果不重寫,則無法比較元素,甚至造成元素位置錯亂。

    重寫了 equals ,就必須要重寫 hashCode 嗎?

    答案是肯定的。首先,在上邊的 JDK 源碼註釋中第第二點,我們就會發現這句說明。其次,我們嘗試重寫 equals ,而不重寫 hashCode 看會發生什麼現象。

    public class TestHashCode {
        public static void main(String[] args) {
            User user1 = new User("zhangsan", 20);
            User user2 = new User("zhangsan", 20);
    
            HashMap<User, Integer> map = new HashMap<>();
            map.put(user1,90);
            System.out.println(map.get(user2));
        }
    }
    // 打印結果: null
    

    對於代碼中的 user1 和 user2 兩個對象來說,我們認為他是同一個人張三。定義一個 map ,key 存儲 User 對象, value 存儲他的學習成績。

    當把 user1 對象作為 key ,成績 90 作為 value 存儲到 map 中時,我們肯定希望,用 key 為 user2 來取值時,得到的結果是 90 。但是,結果卻大失所望,得到了 null 。

    這是因為,我們自定義的 User 類,雖然重寫了 equals ,但是沒有重寫 hashCode 。當 user1 放到 map 中時,計算出來的哈希值和用 user2 去取值時計算的哈希值不相等。因此,equals 方法都沒有比較的機會。認為他們是不同的元素。然而,其實,我們應該認為 user1 和 user2 是相同的元素的。

    用圖來說明就是,user1 和 user2 存放在了 HashMap 中不同的桶裡邊,導致查詢不到目標元素。

    因此,當我們用自定義類來作為 HashMap 的 key 時,必須要重寫 hashCode 和 equals 。否則,會得到我們不想要的結果。

    這也是為什麼,我們平時都喜歡用 String 字符串來作為 key 的原因。 因為, String 類默認就幫我們實現了 equals 和 hashCode 方法的重寫。如下,

    // String.java
    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            //從前向後依次比較字符串中的每個字符
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
    
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;
    		//把字符串中的每個字符都取出來,參与運算
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            //把計算出來的最終值,存放在hash變量中。
            hash = h;
        }
        return h;
    }
    

    重寫 equals 時,可以使用 idea 提供的自動代碼,也可以自己手動實現。

    public class User {
        ... //省略已知代碼
            
        @Override
        public int hashCode() {
            return Objects.hash(name, age);
        }
       
    }
    //此時,map.get(user2) 可以得到 90 的正確值
    

    在重寫了 hashCode 后,使用自定義對象作為 key 時,還需要注意一點,不要在使用過程中,改變對象的內容,這樣會導致 hashCode 值發生改變,同樣得不到正確的結果。如下,

    public class TestHashCode {
        public static void main(String[] args) {
            User user = new User("zhangsan", 20);
    
            HashMap<User, Integer> map = new HashMap<>();
            map.put(user,90);
            System.out.println(map.get(user));
            user.setAge(18); //把對象的年齡修改為18
            System.out.println(map.get(user));
        }
    }
    // 打印結果:
    // 90
    // null
    

    會發現,修改后,拿到的值是 null 。這也是,hashCode 源碼註釋中的第一點說明的,hashCode 值不變的前提是,對象的信息沒有被修改。若被修改,則有可能導致 hashCode 值改變。

    此時,有沒有聯想到其他一些問題。比如,為什麼 String 類要設計成不可以變的呢?這裏用 String 作為 HashMap 的 key 時,可以算作一個原因。你肯定不希望,放進去的時候還好好的,取出來的時候,卻找不到元素了吧。

    String 類內部會有一個變量(hash)來緩存字符串的 hashCode 值。只有字符串不可變,才可以保證哈希值不變。

    hashCode 相等時,equals 一定相等嗎?

    很顯然不是的。在 HashMap 的源碼中,我們就能看到,當 hashCode 相等時(產生哈希碰撞),還需要比較它們的 equals ,才可以確定是否是同一個對象。因此,hashCode 相等時, equals 不一定相等 。

    反過來,equals 相等的話, hashCode 一定相等嗎? 那必須的。equals 都相等了,那說明在 HashMap 中認為它們是同一個元素,所以 hashCode 值必須也要保證相等。

    結論:

    • hashCode 相等,equals 不一定相等。
    • hashCode 不等,equals 一定不等。
    • equals 相等, hashCode 一定相等。
    • equals 不等, hashCode 不一定不等。

    關於最後這一點,就是 hashCode 源碼註釋中提到的第三點。當 equals 不等時,不用必須保證它們的 hashCode 也不相等。但是為了提高哈希表的效率,最好設計成不等。

    因為,我們既然知道它們不相等了,那麼當 hashCode 設計成不等時。只要比較 hashCode 不相等,我們就可以直接返回 null,而不必再去比較 equals 了。這樣,就減少了比較的次數,無疑提高了效率。

    結尾

    以上就是 hashCode 和 equals 相關的一些問題。相信已經可以解答你心中的疑惑了,也可以和面試官侃侃而談。再也不用擔心,面試官說換人了。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!