關於vue的多頁面標籤功能,對於嵌套router-view緩存的最終無奈解決方法_網頁設計公司

1{icon} {views}

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

最近寫我自己的後台開發框架,要弄一個多頁面標籤功能,之前有試過vue-element-admin的多頁面,以為很完美,就按它的思路重新寫了一個,但發現還是有問題的。

vue-element-admin它用的是在keep-alive組件上使用include屬性,綁定$store.state.tagsView.cachedViews,當點擊菜單時,往$store.state.tagsView.cachedViews添加頁面的name值,在標籤卡上點擊關閉后就從$store.state.tagsView.cachedViews裏面把緩存的name值刪除掉,這樣聽似乎沒什麼問題。但它無法很好的支持無限級別的子菜單的緩存。

目前vue-element-admin官方預覽地址的菜單結構大多是一級菜單分類,下面是二級子菜單。如下圖所示,它只能緩存二級子菜單,三級子菜單它緩存不了。為什麼會出現這個情況呢。因為嵌套router-view的問題。

 

 

 

按vue-element-admin的路由結構,它的一級菜單,其實對應的是一個layout組件,layout裏面有個router-view(稱它為一級router-view)它有用keep-alive包裹着,用來放二級菜單對應的頁面,所以對於二級菜單來說,它都是用同一個router-view。如果我需要創建三級菜單的話,那就需要在二級菜單目錄里創建一個包含router-view(稱它為二級router-view)的index.vue文件,用來放三級菜單對應的頁面,那麼你就會發現這個三級菜單的頁面怎麼也緩存不了。

 

因為只有一級router-view被keep-alive包裹起着緩存作用,下面的router-view它不緩存。當然我們也可以在二級的router-view也包一個keep-alive,也用include屬性,但你會發現也用不了,因為還要匹配name值,就是說二級router-view的文件也得寫上name值,寫上name值后你發現還是用不了,因為include數組裡面沒有這個二級router-view的name值,所以你還得在tabsView里的addView裏面做手腳,把路由所匹配到的所有路由的name值都添加到cachedViews里,然後還要在關閉時再進行處理。天啊。我想想都頭痛,理論是應該是可以實現的,但會增加了很多前端代碼量。

 

請注意!下面的方法也是有Bug的,請重點看下面的BUT開始部分

還好keep-alive還有另一個屬性exclude,我馬上就有思路了,而且非常簡潔,默認全部頁面進行緩存,所有的router-view都包一層keep-alive,只有在點擊標籤卡上的關閉按鈕時,往$store.state.sys.excludeViews添加關閉頁面的name值,下次打開后再從excludeViews裏面把頁面的name值刪除掉就行了,非常地簡單易懂,不過最底層的頁面,仍然需要寫上跟路由定義時完全匹配的name值。這一步我仍然想不到有什麼辦法可以省略掉。

為方便代碼,我寫了一個組件aliveRouterView組件,併合局註冊,這個組件用來代替router-view組件,如下面代碼所示,$store.state.sys.config.PAGE_TABS這個值是是否開戶多頁面標籤功能參數

<template>
  <keep-alive :exclude="exclude">
    <router-view />
  </keep-alive>
</template>
<script>
export default {
  computed: {
    exclude() {
      if (this.$store.state.sys.config.PAGE_TABS) {
        return this.$store.state.sys.excludeViews;
      } else {
        return /.*/;
      }
    }
  }
};
</script>

 

多頁面標籤組件viewTabs.vue,如下面代碼所示

<template>
  <div class="__common-layout-tabView">
    <el-scrollbar>
      <div class="__tabs">
        <div
          class="__tab-item"
          :class="{ '__is-active':item.name==$route.name }"
          v-for="item in viewRouters"
          :key="item.path"
          @click="onClick(item)"
        >
          {{item.meta.title}}
          <span
            class="el-icon-close"
            @click.stop="onClose(item)"
            :style="viewRouters.length<=1?'width:0;':''"
          ></span>
        </div>
      </div>
    </el-scrollbar>
  </div>
</template>
<script>
export default {
  data() {
    return {
      viewRouters: []
    };
  },
  watch: {
    $route: {
      handler(v) {
        if (!this.viewRouters.some(item => item.name == v.name)) {
          this.viewRouters.push(v);
        }
      },
      immediate: true
    }
  },
  methods: {
    onClick(data) {
      if (this.$route.fullPath != data.fullPath) {
        this.$router.push(data.fullPath);
      }
    },
    onClose(data) {
      let index = this.viewRouters.indexOf(data);
      if (index >= 0) {
        this.viewRouters.splice(index, 1);
        if (data.name == this.$route.name) {
          this.$router.push(this.viewRouters[index < 1 ? 0 : index - 1].path);
        }
        this.$store.dispatch("excludeView", data.name);
      }
    }
  }
};
</script>
<style lang="scss">
.__common-layout-tabView {
  $c-tab-border-color: #dcdfe6;
  position: relative;
  &::before {
    content: "";
    border-bottom: 1px solid $c-tab-border-color;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 2px;
    height: 100%;
  }
  .__tabs {
    display: flex;
    .__tab-item {
      white-space: nowrap;
      padding: 8px 6px 8px 18px;
      font-size: 12px;
      border: 1px solid $c-tab-border-color;
      border-left: none;
      border-bottom: 0px;
      line-height: 14px;
      cursor: pointer;
      transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
        padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
      &:first-child {
        border-left: 1px solid $c-tab-border-color;
        border-top-left-radius: 2px;
        margin-left: 10px;
      }
      &:last-child {
        border-top-right-radius: 2px;
        margin-right: 10px;
      }
      &:not(.__is-active):hover {
        color: #409eff;
        .el-icon-close {
          width: 12px;
          margin-right: 0px;
        }
      }
      &.__is-active {
        padding-right: 12px;
        border-bottom: 1px solid #fff;
        color: #409eff;
        .el-icon-close {
          width: 12px;
          margin-right: 0px;
          margin-left: 2px;
        }
      }
      .el-icon-close {
        width: 0px;
        height: 12px;
        overflow: hidden;
        border-radius: 50%;
        font-size: 12px;
        margin-right: 12px;
        transform-origin: 100% 50%;
        transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
        vertical-align: text-top;
        &:hover {
          background-color: #c0c4cc;
          color: #fff;
        }
      }
    }
  }
}
</style>

 

貼上我的sys的store文件,後面我發現,我把頁面name添加到excludeViews后,在下一幀中再從excludeViews中把name刪除后,這樣也能有效果。如下面excludeView所示。這樣就更加簡潔。我只需在關閉標籤卡時處理一下就行了。

const sys = {
    state: {
        permissionRouters: [],//權限路由表
        permissionMenus: [],//權限菜單列表
        config: null, //系統配置        
        excludeViews: [] //用於多頁面選項卡
    },
    getters: {

    },
    mutations: {
        SET_PERMISSION_ROUTERS(state, routers) {
            state.permissionRouters = routers;
        },
        SET_PERMISSION_MENUS(state, menus) {
            state.permissionMenus = menus;
        },
        SET_CONFIG(state, config) {
            state.config = config;
        },
        ADD_EXCLUDE_VIEW(state, viewName) {
            state.excludeViews.push(viewName);
        },
        DEL_EXCLUDE_VIEW(state, viewName) {
            let index = state.excludeViews.indexOf(viewName);
            if (index >= 0) {
                state.excludeViews.splice(index, 1);
            }
        }
    },
    actions: {
        //排除頁面
        excludeView({ state, commit, dispatch }, viewName) {
            if (!state.excludeViews.includes(viewName)) {
                commit("ADD_EXCLUDE_VIEW", viewName);
                Promise.resolve().then(() => {
                    commit("DEL_EXCLUDE_VIEW", viewName);
                })
            }
        }
    }
}
export default sys

 

效果如下圖所示,記得一點,就是得在你的頁面上填寫name值,需要跟定義路由時完全一致

 

BUT!!當我截完上面的動圖后,我就發現了問題了,而且是一個無法解決的問題,按我上面的方法,如果我點一下首頁,再點回原來的用戶管理,再關閉用戶管理,再打開用戶管理,你會發現緩存一直都在。

這是為什麼呢?究根詰底還是這個嵌套router-view的問題,不同的router-view的緩存是獨立的,首頁頁面是緩存在一級router-view下面,而用戶管理頁面是緩存在二級router-view下面,當我關閉用戶管理頁面后,只是往excludeViews添加了用戶管理頁面的name(sys.anme),所以只會刪除二級router-view下面name值為sys.user的頁面,二級router-view的name值為sys,它還緩存在一級router-view,所以導致用戶管理一直緩存着。

當然我也想過在關閉頁面時,把頁面父級的所有router-view的name值都添加到excludeViews裏面,這樣的話,也會出現問題,就是當我關閉用戶管理頁面后,同樣在name值為sys的二級router-view下面的頁面緩存都刪除掉了。

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

當我測試了一晚上,我發現這真的是無解的,中間我也試過網上說的暴力刪除cache方法(方法介紹),也是因為這個嵌套router-view的問題導致失敗。

其實網上有人提出的解決方法是把框架改成只有一個一級router-view,一開始我覺得這是個下策,後面發現這也是唯一的方法了。

無奈,我確實不想扔棄這個多頁面標籤功能。那就改吧,其實改起來也不複雜,就是將菜單跟路由數組分為兩成數組,各自獨立。路由全部同級,均在layout布局組件的children裏面。

只使用一級router-view後面,這個多頁面標籤功能就非常好解決了,用include或exclude都可以,沒有什麼問題,但這兩種方法都得在頁面上寫name值,我是一個懶惰的程序員,總是寫這種跟業務無關係的name值顯得特別多餘。幸運的是,我之前在網上有找到一種暴力刪除緩存的方法,經過我的測試后,發現只有一個小問題(下面會提到),其它方面幾乎完美,而且跟include、exclude相比,還能完美支持同個頁面可以根據不同參數同時緩存的功能。(在vue-element-admin裏面也有說到include是沒法支持這種功能的,如下圖)

 

思想是這樣的,在store里創建一個openedPageRouters(已打開的頁面路由數組),我watch路由的變化,當打開一個新頁面時,往openedPageRouters裏面添加頁面路由,當我關閉頁面標籤時,到openedPageRouters裏面刪除對應的頁面路由,而上面提到的暴力刪除緩存,是在頁面的beforeRouterLeave事件中進行刪除中,所以我註冊一個全局mixin的beforeRouterLeave事件,檢測離開的頁面如果不存在於openedPageRouters數組裡面,那就進行緩存刪除。

思路很完美,當然裏面還有一個小問題,就是刪除不是當前激活的頁面,怎麼處理,因為beforeRouterLeave必須在要刪除頁面的生命周期才能觸發的,這個我用了點小手段,我先跳轉到要刪除的頁面,然後往openedPageRouters里刪除這個頁面路由,然後再跳回原來的頁面,這樣就能讓它觸發beforeRouterLeave了。哈哈,不過這個會導致一個小問題,就是地址欄的閃動一下,也就是上面提到的小問題。

下面是我的pageTabs.vue多頁面標籤組件的代碼

<template>
  <div class="__common-layout-pageTabs">
    <el-scrollbar>
      <div class="__tabs">
        <div
          class="__tab-item"
          v-for="item in $store.state.sys.openedPageRouters"
          :class="{ '__is-active': item.meta.canMultipleOpen?item.fullPath==$route.fullPath:item.path==$route.path }"
          :key="item.fullPath"
          @click="onClick(item)"
        >
          {{item.meta.title}}
          <span
            class="el-icon-close"
            @click.stop="onClose(item)"
            :style="$store.state.sys.openedPageRouters.length<=1?'width:0;':''"
          ></span>
        </div>
      </div>
    </el-scrollbar>
  </div>
</template>
<script>
export default {
  watch: {
    $route: {
      handler(v) {
        this.$store.dispatch("openPage", v);
      },
      immediate: true
    }
  },
  methods: {
    //點擊頁面標籤卡時
    onClick(data) {
      if (this.$route.fullPath != data.fullPath) {
        this.$router.push(data.fullPath);
      }
    },
    //關閉頁面標籤時
    onClose(route) {
      if (route.fullPath == this.$route.fullPath) {
        let index = this.$store.state.sys.openedPageRouters.indexOf(route);
        this.$store.dispatch("closePage", route);
        //刪除頁面后,跳轉到上一頁面
        this.$router.push(
          this.$store.state.sys.openedPageRouters[index < 1 ? 0 : index - 1]
            .path
        );
      } else {
        let lastPath = this.$route.fullPath;
        //先跳轉到要刪除的頁面,再刪除頁面路由,再跳轉回來原來的頁面
        this.$router.replace(route).then(() => {          
          this.$store.dispatch("closePage", route);
          this.$router.replace(lastPath);
        });
      }
    }
  }
};
</script>
<style lang="scss">
.__common-layout-pageTabs {
  $c-tab-border-color: #dcdfe6;
  position: relative;
  &::before {
    content: "";
    border-bottom: 1px solid $c-tab-border-color;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 2px;
    height: 100%;
  }
  .__tabs {
    display: flex;
    .__tab-item {
      white-space: nowrap;
      padding: 8px 6px 8px 18px;
      font-size: 12px;
      border: 1px solid $c-tab-border-color;
      border-left: none;
      border-bottom: 0px;
      line-height: 14px;
      cursor: pointer;
      transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
        padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
      &:first-child {
        border-left: 1px solid $c-tab-border-color;
        border-top-left-radius: 2px;
        margin-left: 10px;
      }
      &:last-child {
        border-top-right-radius: 2px;
        margin-right: 10px;
      }
      &:not(.__is-active):hover {
        color: #409eff;
        .el-icon-close {
          width: 12px;
          margin-right: 0px;
        }
      }
      &.__is-active {
        padding-right: 12px;
        border-bottom: 1px solid #fff;
        color: #409eff;
        .el-icon-close {
          width: 12px;
          margin-right: 0px;
          margin-left: 2px;
        }
      }
      .el-icon-close {
        width: 0px;
        height: 12px;
        overflow: hidden;
        border-radius: 50%;
        font-size: 12px;
        margin-right: 12px;
        transform-origin: 100% 50%;
        transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
        vertical-align: text-top;
        &:hover {
          background-color: #c0c4cc;
          color: #fff;
        }
      }
    }
  }
}
</style>

 

以下是store代碼

const sys = {
    state: {
        menus: [],//
        permissionRouters: [],//權限路由表
        permissionMenus: [],//權限菜單列表
        config: null, //系統配置        
        openedPageRouters: [] //已打開原頁面路由
    },
    getters: {

    },
    mutations: {
        SET_PERMISSION_ROUTERS(state, routers) {
            state.permissionRouters = routers;
        },
        SET_PERMISSION_MENUS(state, menus) {
            state.permissionMenus = menus;
        },
        SET_MENUS(state, menus) {
            state.menus = menus;
        },
        SET_CONFIG(state, config) {
            state.config = config;
        },
        //添加頁面路由        
        ADD_PAGE_ROUTER(state, route) {
            state.openedPageRouters.push(route);
        },
        //刪除頁面路由
        DEL_PAGE_ROUTER(state, route) {
            let index = state.openedPageRouters.indexOf(route);
            if (index >= 0) {
                state.openedPageRouters.splice(index, 1);
            }
        },
        //替換頁面路由
        REPLACE_PAGE_ROUTER(state, route) {
            for (let key in state.openedPageRouters) {
                if (state.openedPageRouters[key].path == route.path) {
                    state.openedPageRouters.splice(key, 1, route)
                    break;
                }
            }
        }
    },
    actions: {
        //打開頁面
        openPage({ state, commit }, route) {
            let isExist = state.openedPageRouters.some(
                item => item.fullPath == route.fullPath
            );
            if (!isExist) {
                //判斷頁面是否支持不同參數多開頁面功能,如果不支持且已存在path值一樣的頁面路由,那就替換它
                if (route.meta.canMultipleOpen || !state.openedPageRouters.some(
                    item => item.path == route.path
                )) {
                    commit("ADD_PAGE_ROUTER", route);
                } else {
                    commit("REPLACE_PAGE_ROUTER", route);
                }
            }
        },
        //關閉頁面
        closePage({ state, commit }, route) {
            commit("DEL_PAGE_ROUTER", route);
        }        
    }
}
export default sys

 

以下是暴力刪除頁面緩存的代碼,我寫成了一個全局的mixin

import Vue from 'vue'
Vue.mixin({
  beforeRouteLeave(to, from, next) {
    //限制只有在我寫的那個父類里才可能會用這個緩存刪除功能
    if (!this.$parent || this.$parent.$el.className != "el-main __common-layout-main" || !this.$store.state.sys.config.PAGE_TABS) {
      next();
      return;
    }
    let isExist = this.$store.state.sys.openedPageRouters.some(item => item.fullPath == from.fullPath)
    if (!isExist) {
      let tag = this.$vnode.tag;
      let cache = this.$vnode.parent.componentInstance.cache;
      let keys = this.$vnode.parent.componentInstance.keys;
      let key;
      for (let k in cache) {
        if (cache[k].tag == tag) {
          key = k;
          break;
        }
      }
      if (key) {
        if (cache[key] != null) {
          delete cache[key];
          let index = keys.indexOf(key);
          if (index > -1) {
            keys.splice(index, 1);
          }
        }
      }
    }
    next();
  }
})

 

 然後router-view這樣使用,根據我的配置$store.state.sys.config.PAGE_TABS(是否啟用多頁面標籤)進行判斷 ,對了,我相信有不少人肯定會想到,路由不嵌套了,沒有matched數組了,怎麼弄麵包屑,可以看我下面代碼的處理,$store.state.sys.permissionMenus這個數組是我從後台傳過來的,是一個根據當前用戶的權限獲取到的所有有權限訪問的菜單數組,都是一級數組,沒有嵌套關係,我的菜單數組跟路由都是根據這個permissionMenus進行構建的。而我的麵包屑數組就是從這個數組遞歸出來的。

<template>
  <el-main class="__common-layout-main">
    <page-tabs class="c-mg-t-10p" v-if="$store.state.sys.config.PAGE_TABS" />
    <div class="c-pd-20p">
      <el-breadcrumb separator="/">
        <el-breadcrumb-item v-for="m in breadcrumbItems" :key="m.id">{{m.name}}</el-breadcrumb-item>
      </el-breadcrumb>
      <div class="c-h-15p"></div>
      <keep-alive v-if="$store.state.sys.config.PAGE_TABS">
        <router-view :key="$route.fullPath" />
      </keep-alive>
      <router-view v-else />
    </div>
  </el-main>
</template>
<script>
import pageTabs from "./pageTabs";
export default {
  components: { pageTabs },
  data() {
    return {
      viewNames: ["role"]
    };
  },
  computed: {
    breadcrumbItems() {
      let items = [];
      let buildItems = id => {
        let b = this.$store.state.sys.permissionMenus.find(
          item => item.id == id
        );
        if (b) {
          items.unshift(b);
          if (b.parentId) {
            buildItems(b.parentId);
          }
        }
      };
      buildItems(this.$route.meta.id);
      return items;
    }
  }
};
</script>
<style lang="scss">
$c-tab-border-color: #dcdfe6;
.__common-layout-main.el-main {
  padding: 0px;
  overflow: unset;
  .el-breadcrumb {
    font-size: 12px;
  }
}
</style>

 

演示一個最終效果,哎,弄了我整整两天時間,不過我改成不嵌套路由后,發現代碼量也少了很多,也是因禍得福啊。這更符合我的Less框架的理念了。哈哈哈!

對了,我之前有說到個小問題,大家可以仔細看一下,下圖的地址欄,當我關閉非當前激活的頁面標籤時,你會發現地址欄會閃現一下。好吧,下面這個動圖還不太明顯。

大家可以到我的LessAdmin框架預覽地址測試下,不要亂改菜單數據哦,會導致打不開的

http://test.caijt.com:9001

用戶:superadmin

密碼:admin

 

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

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

自帶7座大空間+In-Joy智聯黑科技?傳祺GS8還讓對手活嗎?_網頁設計公司

2{icon} {views}

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

In-Joy系統主頁是“多任務卡片”式,首頁有三個最常用功能,可以同時查閱到幾項重要信息,並能快捷控制。In-Joy系統的另一個亮點,同時也是其設計理念——“一秒必達”,你可以更方便快捷的做很多事情,比如撥打電話、調整空調溫度、打開收音機、播放音樂、設置地圖導航等等。

前言

不少科幻電影會有這樣的鏡頭,主角通過手機輕鬆控制汽車,就像《007》中的詹世邦用手機遠程啟動了汽車逃過了反派的追殺;《復讎者聯盟》中鋼鐵俠擁有上天入地的隱形显示屏,快速掌握戰場信息及聯絡隊友。這些東西都讓我們垂涎三尺,非常地炫酷,但是有沒有想過這已經幾乎實現了呢?還是在一輛有着超大空間、霸氣的7座SUV上?沒錯,它就是10月26日將上市的“旗艦級豪華大7座SUV”傳祺GS8,我們今天討論的主角。

霸氣高顏值傳祺GS8將上市,請準備好聚光燈

鋼鐵俠托尼是一張性格乖張的人,有着一種吸引眾人目光的能力,而我們的主角——傳祺GS8兼具勇而不莽、貴而不俗,定位中大型7座SUV的它有着4810*1910*1770mm車身尺寸,在視覺上就給人一種寬奢的感覺。另外使用了傳祺光影雕塑2.0設計理念,整體線條硬朗霸氣,加上使用了科技感十足的LED大燈,給人感覺大氣穩重又不失霸氣感,瞬間拉高了中國品牌車型的顏值。

如此霸氣的車身尺寸加上2800mm的軸距,當然讓傳祺GS8有着優秀的空間表現,軸距比漢蘭達這個合資對手還多出10mm。除此以外,車內目光所及者皆體現豪華感,如黃花梨木紋與鑲嵌的金色飾條,搭配頂級麂皮質感的超纖維面料,考究的配色和做工,盡顯豪華品質;配備頂級品牌安橋車載音響系統、全景天窗、Easy Open電動掀背門等豪車級配置,直擊30萬級以上合資高端車。

In-Joy黑科技放大用戶價值,三屏互聯超高級享受

鋼鐵俠的戰衣有着一個超大的液晶屏幕,

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

這輛傳祺GS8也有10英寸的哈曼頂級液晶屏幕以及7英寸的儀錶盤液晶显示屏。光是尺寸大是沒有用的,還必須是智能,傳祺GS8擁有“黑科技”In-Joy系統,運用Carlife和Carplay系統輕鬆實現中控大屏、儀錶屏與智能手機三屏互聯,車載屏幕就相當於變成了一台智能手機,導航信息、影音娛樂信息均可在7寸液晶儀錶上體現。

In-Joy系統主頁是“多任務卡片”式,首頁有三個最常用功能,可以同時查閱到幾項重要信息,並能快捷控制。In-Joy系統的另一個亮點,同時也是其設計理念——“一秒必達”,你可以更方便快捷的做很多事情,比如撥打電話、調整空調溫度、打開收音機、播放音樂、設置地圖導航等等。更值得炫耀的是,In-Joy系統配備了同級獨有的無線充電功能,為手機自由充電,暢享愉悅空間。

車內有着一個貼心管家,名字是智慧傳祺3.0系統

賈維斯可以遠程啟動實現遠程救主,而傳祺GS8也有着這樣智能的表現。據了解,智慧傳祺3.0系統能幫助我們實現手機控制車輛,甚至無視距離,遠程遙控啟動車輛、遠程開啟空調、座椅加熱、燈光、車窗、後備箱等功能,試想一下寒冷的冬天早早地遠程遙控開啟空調以及座椅加熱,一坐進車內就感受到溫暖而不是面對一輛冷冰冰的車是多麼愜意的事情。

它的智能還體現在駕駛方面,駕駛中大型SUV總讓人覺得難以駕駛,但傳祺GS8有着前碰預警系統、併線輔助系統、車道偏離系統以及ACC自適應巡航系統,幫助駕駛減少時盲區,而駕駛強度也會大幅度降低。

傳祺GS8預售價:16.98萬-25.98萬。

總的來說,傳祺GS8不僅僅在空間方面以及內飾豪華感等方面給到我們驚喜,還在互聯以及自動安全方面有着超越同價位對手的表現,是一輛有着諸多“黑科技”的中大型SUV,非常適合想要選擇中大型SUV、注重用戶體驗以及主動安全配置的用戶購買。10月26日,傳祺GS8將在杭州國際博覽中心震撼上市,不如掃描下面的二維碼,先來一大波美圖洗洗眼吧。

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

生態環境部環評司有關負責人就《經濟、技術政策生態環境影響分析技術指南(試行)》有關問題答記者問_租車

1{icon} {views}

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

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

2020-11-10 來源:生態環境部

2020-11-10
來源:生態環境部 分享到:
[打印]
字號:[大] [中] [小]   生態環境部近日印發了《 經濟、技術政策生態環境影響分析技術指南》(試行)(以下簡稱《指南》)。針對《指南》的出台背景、主要內容、實施重點等問題,生態環境部環評司有關負責人回答了記者的提問。
  問:《指南》發布的背景和意義是什麼?   答:《指南》是貫徹落實《中華人民共和國環境保護法》的重要舉措。《環境保護法》第十四條規定“國務院有關部門和省、自治區、直轄市人民政府組織制定經濟、技術政策,應當充分考慮對環境的影響,聽取有關方面和專家的意見”。為貫徹落實上述規定,“十三五”以來,我部組織環境工程評估中心、環境與經濟政策研究中心、清華大學、北京師範大學等單位,針對區域、產業和城鎮化等領域的典型政策開展了環境評價研究,同時借鑒美國、加拿大、英國、歐盟、世界銀行和相關國際組織制定的政策(戰略)環境評價技術指南等,探索形成了適用於政策生態環境影響分析的技術方法、內容框架和成果要求,在此基礎上編製形成《指南》,可以為經濟、技術政策的制定者在分析政策的生態環境影響方面提供參考。
  問:《指南》的定位是什麼?   答:《指南》是推動政策制定過程中充分考慮生態環境影響的技術指引文件。《指南》列舉了適用的政策類型,提出了一般性分析程序和技術路線,提供了推薦性指標體系和技術方法,政策制定部門在開展環境影響分析工作過程中可根據實際情況增補或調整指標體系,選擇或創新技術方法。
  問:政策生態環境影響分析的作用是什麼?   答:經濟、技術政策關係到一個國家、地區或部門長期發展前景,影響涉及時間跨度大,空間範圍廣,影響類型多樣,政策執行過程中不確定性因素多,可能會對生態環境造成潛在的重大影響。《指南》從環境質量、生態保護、資源消耗、應對氣候變化等四個方面構建了較為全面的指標體系,政策制定部門可通過全面梳理重點識別,判斷政策是否存在重大不利生態環境影響。針對具有潛在重大不利生態環境影響的政策內容,分析其影響範圍和程度,預警可能引發的生態環境風險,通過優化政策內容、完善保障措施和制度,從源頭降低生態環境影響,防控生態環境風險。針對促進生態環境保護、改善環境質量的政策內容,通過提高保障措施和制度的有效性,推動政策落地實施。
  問:政策生態環境影響分析是否涉及新增行政程序?   答:根據《環境保護法》第十四條“國務院有關部門和省、自治區、直轄市人民政府組織制定經濟、技術政策,應當充分考慮對環境的影響”的規定,《重大行政決策程序暫行條例》縣級以上地方人民政府在制(修)定相關政策時應在決策啟動階段開展環境影響和環境效益等分析預測的要求,

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

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

政策制定主體應將生態環境影響分析工作融入政策制定過程。本《指南》僅提供技術指引,不增加行政程序。
  問:《指南》包括哪些主要內容?   答:《指南》包括七部分內容。一是確定經濟、技術政策生態環境影響分析的適用範圍;二是確定政策生態環境影響分析的工作流程;三是提出政策分析內容及步驟,即政策要素解析,相關政策回顧性分析,政策與黨中央、國務院相關決策部署的符合性分析等;四是提出“初步識別”的內容及推薦方法,建議從環境質量、生態保護、資源消耗、應對氣候變化四個方面梳理政策生態環境影響,推薦採用快速、定性分析方法識別政策是否存在重大生態環境影響;五是提出“影響分析”的內容及推薦方法。推薦採用列表法分析政策直接、間接及累積性生態環境影響的範圍和程度,結合不確定性分析預測存在的生態環境風險;六是提出保障措施及制度分析的內容和步驟;七是明確結論與建議。
  問:《指南》推薦採用哪些技術方法?   答:經濟、技術政策生態環境影響範圍廣,影響產生的邏輯鏈條長,執行過程中不確定性因素多,難以定量分析。同時,一些政策出台的周期短、時效性強,需儘快判斷其可能造成的生態環境影響或風險,為政策制定和實施提供支撐和保障,因此《指南》推薦採用快速、定性方法開展生態環境影響分析。在生態環境影響初步識別階段,結合推薦性指標體系採用矩陣分析、檢查表、專家分析等方法,判斷政策是否存在重大不利生態環境影響。在環境影響分析階段,結合政策產生生態環境影響的作用方式和受影響區域特點,採用列表法分析政策對各受影響區域的直接、間接、累積性影響及生態環境風險。鑒於政策生態環境影響分析尚無普遍實踐,開展工作過程中可根據政策類型、政策內容和管理需求合理選擇或創新政策生態環境影響分析技術方法。
  問:《指南》發布后將重點推動哪些後續工作?   答:一是組織開展試點研究,形成案例庫。目前政策生態環境影響分析處於初步推廣階段,下一步將選取典型省份、典型政策領域作為試點推動開展政策生態環境影響分析工作,在試點基礎上逐步完善技術方法體系,建立政策生態環境影響分析案例庫。二是持續開展專題研究,提供更為完善的技術支撐。根據政策類型及政策內容不同,產生生態環境影響的作用機制不同,影響類型和程度也存在很大差異。基於本《指南》提出的一般性程序、技術路線和推薦方法,後續將進一步組織開展專題研究,分析政策生態環境影響機制,研究制定針對不同領域和政策類型的技術路線,提出適用於各分析環節的技術方法,為推動決策機構在政策制定過程中更好考慮對生態環境影響提供技術保障。

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

微軟將推出對使用者更友善、更容易使用的檔案恢復工具_網頁設計公司

2{icon} {views}

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

人嘛!難免有錯手刪掉不該刪檔案的時候,在 2020 年初時,Microsoft 發表了一個供 Windows 系統恢復檔案的工具,允許用戶反會撤銷決定,恢復從各地永久刪除的檔案,包括資源回收桶。雖然這是個福音,但對於大多數用戶來說還是有不算低的門檻,不過接下來,Microsoft 將讓它變得更友善、更容易使用。

微軟將推出對使用者更友善、更容易使用的檔案恢復工具

在之前推出的 Windows 檔案恢復工具,其實是一個指令工具,它的工作方式與其他第三方應用不太一樣,只能輸入指令來使用它,雖說是免費使用,但因為對使用者友好程度低,使得這個工具立意良善卻普及度不如預期。根據消息,Microsoft 有計畫讓這工具的使用更加簡單,新的改良正在路上。

這項工具目前已經對測試人員釋出,根據 Windows Latest 報導,在最新測試版本中除了速度變得更快,還支援一般與廣泛兩種模式。使用新的「一般」模式,你可以掃描磁碟機 (NTFS 檔案系統) 以尋找被刪除檔案並嘗試還原。Microsoft 指出,一般模式是使用者的標準恢復選項,適用於恢復近期刪除的檔案。任何具備一點指令基本知識的人都能夠恢復他們的檔案,雖然一般模式對使用者更友好,但如果硬碟中的可用空間已經被其他檔案所覆蓋,它可能就失去作用了,尤其是如果你的電腦中是使用 SSD 的話。

Microsoft 表示,從 2020 年首度發表檔案恢復工具以來,陸續收到許多使用者的意見回饋,未來幾個月內,該工具將會推出更多改良,第一個大更新會在 2021 年初與 Windows 10 2004 更新或更新的版本推出給普羅大眾。

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

◎資料來源:Windows Latest

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

深入了解ConcurrentHashMap_網頁設計公司

1{icon} {views}

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

在上一篇文章【簡單了解系列】從基礎的使用來深挖HashMap里,我從最基礎的使用中介紹了HashMap,大致是JDK1.7和1.8中底層實現的變化,和介紹了為什麼在多線程下可能會造成死循環,擴容機制是什麼樣的。感興趣的可以先看看。

我們知道,HashMap是非線程安全的容器,那麼為什麼ConcurrentHashMap能夠做到線程安全呢?

底層結構

首先看一下ConcurrentHashMap的底層數據結構,在Java8中,其底層的實現方式與HashMap一樣的,同樣是數組、鏈表再加紅黑樹,具體的可以參考上面的HashMap的文章,下面所有的討論都是基於Java 1.8。

transient volatile Node<K,V>[] table;

volatile關鍵字

對比HashMap的底層結構可以發現,table的定義中多了一個volatile關鍵字。這個關鍵字是做什麼的呢?我們知道所有的共享變量都存在主內存中,就像table。

而線程對變量的所有操作都必須在線程自己的工作內存中完成,而不能直接讀取主存中的變量,這是JMM的規定。所以每個線程都會有自己的工作內存,工作內存中存放了共享變量的副本。而正是因為這樣,才造成了可見性的問題。

ABCD四個線程同時在操作一個共享變量X,此時如果A從主存中讀取了X,改變了值,並且寫回了內存。那麼BCD線程所得到的X副本就已經失效了。此時如果沒有被volatile修飾,那麼BCD線程是不知道自己的變量副本已經失效了。繼續使用這個變量就會造成數據不一致的問題。

內存可見性

而如果加上了volatile關鍵字,BCD線程就會立馬看到最新的值,這就是內存可見性。你可能想問,憑什麼加了volatile的關鍵字就可以保證共享變量的內存可見性?

那是因為如果變量被volatile修飾,在線程進行寫操作時,會直接將新的值寫入到主存中,而不是線程的工作內存中;而在讀操作時,會直接從主存中讀取,而不是線程的工作內存。

基礎使用

首先這個使用與HashMap沒有任何區別,只是實現改成了ConcurrentHashMap。

Map<String, String> map = new ConcurrentHashMap<>();
map.put("微信搜索", "SH的全棧筆記");
map.get("微信搜索"); // SH的全棧筆記

取值

首先我們來看一下get方法的使用,源碼如下。

public V get(Object key) {
  Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
  int h = spread(key.hashCode());
  if ((tab = table) != null && (n = tab.length) > 0 &&
      (e = tabAt(tab, (n - 1) & h)) != null) {
    if ((eh = e.hash) == h) {
      if ((ek = e.key) == key || (ek != null && key.equals(ek)))
        return e.val;
    }
    else if (eh < 0)
      return (p = e.find(h, key)) != null ? p.val : null;
    while ((e = e.next) != null) {
      if (e.hash == h &&
          ((ek = e.key) == key || (ek != null && key.equals(ek))))
        return e.val;
    }
  }
  return null;
}

大概解釋一下這個過程發生了什麼,首先根據key計算出哈希值,如果找到了就直接返回值。如果是紅黑樹的話,就在紅黑樹中查找值,否則就按照鏈表的查找方式查找。

這與HashMap也差不多的,元素會首先以鏈表的方式進行存儲,如果該桶中的元素數量大於TREEIFY_THRESHOLD的值,就會觸發樹化。將當前的鏈錶轉換為紅黑樹。因為如果數量太多的話,鏈表的查詢效率就會變得非常低,時間複雜度為O(n),而紅黑樹的查詢時間複雜度則為O(logn),這個閾值在Java 1.8中的默認值為8,定義如下。

static final int TREEIFY_THRESHOLD = 8;

賦值

put的源碼就不放出來了,放在這大家估計也不會一行一行的去看。所以我就簡單的解釋一下put的過程發生了什麼事,並貼上關鍵代碼就好了。

整個過程,除開併發的一些細節,大致的流程和1.8中的HashMap是差不多的。

  • 首先會根據傳入的key計算出hashcode,如果是第一次被賦值,那自然需要進行初始化table
  • 如果這個key沒有存在過,直接用CAS在當前槽位的頭節點創建一個Node,會用自旋來保證成功
  • 如果當前的Node的hashcode是否等於-1,如果是則證明有其它的線程正在執行擴容操作,當前線程就加入到擴容的操作中去
  • 且如果該槽位(也就是桶)上的數據結構如果是鏈表,則按照鏈表的插入方式,直接接在當前的鏈表的後面。如果數量大於了樹化的閾值就會轉為紅黑樹。
  • 如果這個key存在,就會直接覆蓋。
  • 判斷是否需要擴容

看到這你可能會有一堆的疑問。

例如在多線程的情況下,幾個線程同時來執行put操作時,怎麼保證只執行一次初始化,或者怎麼保證只執行一次擴容呢?萬一我已經寫入了數據,另一個線程又初始化了一遍,豈不是造成了數據不一致的問題。同樣是多線程的情況下, 怎麼保證put值的時候不會被其他線程覆蓋。CAS又是什麼?

接下來我們就來看一下在多線程的情況下,ConcurrentHashMap是如何保證線程安全的。

初始化的線程安全

首先我們來看初始化的源碼。

private final Node<K,V>[] initTable() {
  Node<K,V>[] tab; int sc;
  while ((tab = table) == null || tab.length == 0) {
    if ((sc = sizeCtl) < 0)
      Thread.yield(); // lost initialization race; just spin
    else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
      try {
        if ((tab = table) == null || tab.length == 0) {
          int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
          @SuppressWarnings("unchecked")
          Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
          table = tab = nt;
          sc = n - (n >>> 2);
        }
      } finally {
        sizeCtl = sc;
      }
      break;
    }
  }
  return tab;
}

可以看到有一個關鍵的變量,sizeCtl,其定義如下。

private transient volatile int sizeCtl;

sizeCtl使用了關鍵字volatile修飾,說明這是一個多線程的共享變量,可以看到如果是首次初始化,第一個判斷條件if ((sc = sizeCtl) < 0)是不會滿足的,正常初始化的話sizeCtl的值為0,初始化設定了size的話sizeCtl的值會等於傳入的size,而這兩個值始終是大於0的。

CAS

然後就會進入下面的U.compareAndSwapInt(this, SIZECTL, sc, -1)方法,這就是上面提到的CAS,Compare and Swap(Set),比較並交換,Unsafe是位於sun.misc下的一個類,在Java底層用的比較多,它讓Java擁有了類似C語言一樣直接操作內存空間的能力。

例如可以操作內存、CAS、內存屏障、線程調度等等,但是如果Unsafe類不能被正確使用,就會使程序變的不安全,所以不建議程序直接使用它。

compareAndSwapInt的四個參數分別是,實例、偏移地址、預期值、新值。偏移地址可以快速幫我們在實例中定位到我們要修改的字段,此例中便是sizeCtl。如果內存當中的sizeCtl是傳入的預期值,則將其更新為新的值。這個Unsafe類的方法可以保證這個操作的原子性。當你在使用parallelStream進行併發的foreach遍歷時,如果涉及到修改一個整型的共享變量時,你肯定不能直接用i++,因為在多線程下,i++每次操作不能保證原子性。所以你可能會用到如下的方式。

AtomicInteger num = new AtomicInteger();
arr.parallelStream().forEach(item -> num.getAndIncrement());

你可能會好奇,為什麼使用了AtomicInteger就可以保證原子性,跟Unsafe類和CAS又有什麼關係,讓我們接着往下,看getAndIncrement方法的底層實現。

public final int getAndIncrement() {
  return unsafe.getAndAddInt(this, valueOffset, 1);
}

可以看到,底層調用的是Unsafe類的方法,這不就聯繫上了嗎,而getAndIncrement的實現又長這樣。

public final int getAndAddInt(Object var1, long var2, int var4) {
  int var5;
  do {
    var5 = this.getIntVolatile(var1, var2);
  } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
  return var5;
}

沒錯,這裏底層調用了compareAndSwapInt方法。可以看到這裏加了while,如果該方法返回false就一直循環,直到成功為止。這個過程有個的名字,叫自旋。特別高端啊,說人話就是無限循環。

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

什麼情況會返回false呢?那就是var5變量存儲的值,和現在內存中實際var5的值不同,說明這個變量已經被其他線程修改過了,此時通過自旋來重新獲取,直到成功為止,然後自旋結束。

結論

聊的稍微有點多,這小節的問題是如何保證不重複初始化。那就是執行首次擴容時,會將變量sizeCtl設置為-1,因為其被volatile修飾,所以其值的修改對其他線程可見。

其它線程再調用初始化時,就會發現sizeCtl的值為-1,說明已經有線程正在執行初始化的操作了,就會執行Thread.yield(),然後退出。

yield相信大家都不陌生,和sleep不同,sleep可以讓線程進入阻塞狀態,且可以指定阻塞的時間,同時釋放CPU資源。而yield不會讓線程進入阻塞狀態,而且也不能指定時間,它讓線程重新進入可執行狀態,讓出CPU調度,讓CPU資源被同優先級或者高優先級的線程使用,稍後再進行嘗試,這個時間依賴於當前CPU的時間片劃分。

如何保證值不被覆蓋

我們在上一節舉了在併發下i++的例子,說在併發下i++並不是一個具有原子性的操作,假設此時i=1,線程A和線程B同時取了i的值,同時+1,然後此時又同時的寫回。那麼此時i++的值會是2而不是3,在併發下1+1+1=2是可能出現的。

讓我們來看一下ConcurrentHashMap在目標key已經存在時的賦值操作,因為如果不存在會直接調用Unsafe的方法創建一個Node,所以後續的線程就會進入到下面的邏輯中來,由於太長,我省略了一些代碼。

......
V oldVal = null;
synchronized (f) {
  if (tabAt(tab, i) == f) {
    if (fh >= 0) {
      binCount = 1;
      for (Node<K,V> e = f;; ++binCount) {
        ......
      }
    }
    else if (f instanceof TreeBin) {
      Node<K,V> p;
      binCount = 2;
      if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
        oldVal = p.val;
        if (!onlyIfAbsent)
          p.val = value;
      }
    }
  }
}
if (binCount != 0) {
  if (binCount >= TREEIFY_THRESHOLD)
    treeifyBin(tab, i);
  if (oldVal != null)
    return oldVal;
  break;
}

上述代碼在賦值的邏輯外層包了一個synchronized,這個有什麼用呢?

synchronized關鍵字

這個地方也可以換一個方式來理解,那就是synchronized如何保證線程安全的。線程安全,我認為更多的是描述一種風險。在堆內存中的數據由於可以被任何線程訪問到,在沒有任何限制的情況下存在被意外修改的風險。

synchronized是通過對共享資源加鎖的方式,使同一時間只能有一個線程能夠訪問到臨界區(也就是共享資源),共享資源包括了方法、鎖代碼塊和對象。

那是不是使用了synchronized就一定能保證線程安全呢?不是的,如果不能正確的使用,很可能就會引發死鎖,所以,保證線程安全的前提是正確的使用synchronized

自動擴容的線程安全

除了初始化、併發的寫入值,還有一個問題值得關注,那就是在多線程下,ConcurrentHashMap是如何保證自動擴容是線程安全的。

擴容的關鍵方案是transfer,但是由於代碼太多了,貼在這個地方可能會影響大家的理解,感興趣的可以自己的看一下。

還是大概說一下自動擴容的過程,我們以一個線程來舉例子。在putVal的最後一步,會調用addCount方法,然後在方法里判讀是否需要擴容,如果容量超過了實際容量 * 負載因子(也就是sizeCtl的值)就會調用transfer方法。

計算分區的範圍

因為ConcurrentHashMap是支持多線程同時擴容的,所以為了避免每個線程處理的數量不均勻,也為了提高效率,其對當前的所有桶按數量(也就是上面提到的槽位)進行分區,每個線程只處理自己分到的區域內的桶的數據即可。

當前線程計算當前stride的代碼如下。

stride = (NCPU > 1) ? (n >>> 3) / NCPU : n);

如果計算出來的值小於設定的最小範圍,也就是private static final int MIN_TRANSFER_STRIDE = 16;,就把當前分區範圍設置為16。

初始化nextTable

nextTable也是一個共享變量,定義如下,用於存放在正在擴容之後的ConcurrentHashMap的數據,當且僅當正在擴容時才不為空。

private transient volatile Node<K,V>[] nextTable;

如果當前transfer方法傳入的nextTab(這是個局部變量,比上面提到的nextTable少了幾個字母,不要搞混了)是null,說明是當前線程是第一個調用擴容操作的線程,就需要初始化一個size為原來容量2被的nextTable,核心代碼如下。

Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; // 可以看到傳入的初始化容量是n << 1。

初始化成功之後就更新共享變量nextTable的值,並設置transferIndex的值為擴容前的length,這也是一個共享的變量,表示擴容使還未處理的桶的下標。

設置分區邊界

一個新的線程加入擴容操作,在完成上述步驟后,就會開始從現在正在擴容的Map中找到自己的分區。例如,如果是第一個線程,那麼其取到的分區就會如下。

start = nextIndex - 1;
end = nextIndex > stride ? nextIndex - stride : 0;
// 實際上就是當還有足夠的桶可以分的時候,線程分到的分區為 [n-stride, n - 1]

可以看到,分區是從尾到首進行的。而如果是首次進入的線程,nextIndex 的值會被初始化為共享變量transferIndex 的值。

Copy分區內的值

當前線程在自己劃分到的分區內開始遍歷,如果當前桶是null,那麼就生成一個 ForwardingNode,代碼如下。

ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);

並把當前槽位賦值為fwd,你可以把ForwardingNode理解為一個標誌位,如果有線程遍歷到了這個桶, 發現已經是ForwardingNode了,就代表這個桶已經被處理過了,就會跳過這個桶。

如果這個桶沒有被處理過,就會開始給當前的桶加鎖,我們知道ConcurrentHashMap會在多線程的場景下使用,所以當有線程正在擴容的時候,可能還會有線程正在執行put操作,所以如果當前Map正在執行擴容操作,如果此時再寫入數據,很可能會造成的數據丟失,所以要對桶進行加鎖。

總結

對比在1.7中採用的Segment分段鎖的臃腫設計,1.8中直接使用了CASSynchronized來保證併發下的線程安全。總的來說,在1.8中,ConcurrentHashMap和HashMap的底層實現都差不多,都是數組、鏈表和紅黑樹的方式。其主要區別就在於應用場景,非併發的情況可以使用HashMap,而如果要處理併發的情況,就需要使用ConcurrentHashMap。關於ConcurrentHashMap就先聊到這裏。

本文使用 mdnice 排版

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

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

吹皺東地中海一池春水後 土耳其將在黑海鑽探石油_網頁設計公司

1{icon} {views}

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

摘錄自2020年5月15日中央社報導

土耳其能源部長唐梅茲今(14日)表示,征服者號鑽井船自7月開始將首度前往黑海進行鑽探。他並宣示會繼續依照計畫進行東地中海能源鑽探的決心。唐梅茲(Fatih Donmez)今天接受安納杜魯新聞社(Anadolu Agency)專訪時作以上表示。

東地中海發現離岸能源後,已經成為能源爭奪的引爆點。1974年賽島希臘裔曾試圖發動政變與希臘合併,土耳其以保護土裔為由出兵占領賽島北部1/3土地,並於1983年扶植土裔成立北賽,但國際僅承認希裔控制的南賽。

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

土耳其是唯一承認北賽的國家。南賽當局認為土耳其的鑽探作業違反國際法,並且強調對於相關油氣資源的決定屬於南賽主權的範疇。土耳其則不接受南賽政府與周邊國家達成的專屬經濟區協議。歐洲聯盟去年曾對土耳其「展開非法鑽探活動」進行制裁,展現對歐盟成員國南賽的堅定支持。

生活環境
能源議題
能源轉型
國際新聞
土耳其
石油探鑽
黑海
石油

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

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

全球首款配備 HDMI 2.1 的 32 吋 4K 144Hz 電競螢幕 ROG Swift PG32UQ 發表_網頁設計公司

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

對遊戲玩家來說,除了系統、主機之外,螢幕也是重中之重,除了觀乎視覺享受,更能左右你的遊戲成績。ROG 今日(1/13)發表了首款支援 HDMI 2.1 的 32 吋 4K 144Hz 電競螢幕 ROG Swift PG32UQ ,擁有足夠的頻寬,無論與電腦或家用主機連皆使用,都能擁有比其他電競螢幕更好的視覺體驗。

全球首款配備 HDMI 2.1 的 32 吋 4K 144Hz 電競螢幕 ROG Swift PG32UQ 發表

ROG Swift PG32UQ 配備 4K 144Hz 4K IPS 面板,反應時間僅有 1ms;支援 HDR 600,亮度峰值可達到 600cd/m2,色彩方面經過原廠調校,可呈現 100% sRGB 與 98% DCI-P3 色域範圍,另外支援 NVIDIA G-Sync、Overdrive 與 ELMB Sync。

在螢幕後方提供了 DisplayPort 1.4 與 2 個 HDMI 2.1 訊號輸入連接埠。只要你的訊號輸入設備能夠支援,透過 DisplayPort 1.4 連接可以使該螢幕在 PC 上以 144Hz 更新率顯示 4K 影像,Asus 說明,該功能主要運用一種稱為 DSC 的訊號壓縮技術,可對 UHD 的訊號進行壓縮並且不會降低影像品質。 HDMI 2.1 部分則提供了 VRR 更新率同步、自動低延遲模式與 4K@120Hz 等,因為頻寬夠大,可以與 PC、Xbox Series X 和 PS5 相容,並且能夠確實以 120fps 的速度來呈現 4K 高解析度。

雖然上市日期與售價不明,但以已經上市的 Acer Nitro XV28 這款擁有 4K 144Hz IPS 顯示器的價格來評估,售價不太可能會低於 899 美元,如果想要入手,還要多存點錢。不過在會中也提到接著正在規劃 ROG STRIX XG43UQ 與 TUF GAMING VG28UQ 兩款不同尺寸的 4K 電競螢幕,預期應該都會支援 HDMI 2.1,倒是可以期待一下。

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

Flutter 動畫鼻祖之CustomPaint_包裝設計

1{icon} {views}

※產品缺大量曝光嗎?你需要的是一流包裝設計!

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

老孟導讀:CustomPaint可以稱之為動畫鼻祖,它可以實現任何酷炫的動畫和效果。CustomPaint本身沒有動畫屬性,僅僅是繪製屬性,一般情況下,CustomPaint會和動畫控制配合使用,達到理想的效果。

基本用法

CustomPaint的用法非常簡單,如下:

CustomPaint(
  painter: MyCustomPainter(),
)

MyCustomPainter定義如下:

class MyCustomPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {}

  @override
  bool shouldRepaint(MyCustomPainter oldDelegate) {
    return this != oldDelegate;
  }
}

上面的MyCustomPainter為了看起來清晰,什麼也沒有做,通常情況下,在paint方法內繪製自定義的效果。shouldRepaint方法通常在當前實例和舊實例屬性不一致時返回true。

paint通過canvas繪製,size為當前控件的大小,下面看看canvas的方法。

繪製點

Paint _paint = Paint()
    ..color = Colors.red
    ..strokeWidth = 3;

  @override
  void paint(Canvas canvas, Size size) {
    var points = [
      Offset(0, 0),
      Offset(size.width / 2, size.height / 2),
      Offset(size.width, size.height),
    ];
    canvas.drawPoints(PointMode.points, points, _paint);
  }

PointMode有3種模式:

  • points:點
  • lines:將2個點繪製為線段,如果點的個數為奇數,最後一個點將會被忽略
  • polygon:將整個點繪製為一條線

繪製線

canvas.drawLine(Offset(0, 0),Offset(size.width, size.height), _paint);

繪製路徑

Paint _paint = Paint()
  ..color = Colors.red
  ..style = PaintingStyle.stroke
  ..strokeWidth = 3;

@override
void paint(Canvas canvas, Size size) {
  print('size:$size');
  var _path = Path()
    ..moveTo(0, 0)
    ..lineTo(size.width, 0)
    ..lineTo(size.width, size.height)
  ..close();
  canvas.drawPath(_path, _paint);
}

這裏注意Paint.style,還可以設置為PaintingStyle.fill,效果如下:

此時Path的路徑不要在一條直線上,否則會看不到效果。

繪製各種形狀

繪製圓形

canvas.drawCircle(Offset(size.width/2, size.height/2), 20, _paint);

繪製橢圓

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

canvas.drawOval(Rect.fromLTRB(0, 0, size.width, size.height/2), _paint);

如果給定的Rect為正方形,那麼橢圓將會變為圓形。

繪製弧

canvas.drawArc(
    Rect.fromLTRB(0, 0, size.width, size.height), 0, pi/2, true, _paint);

繪製圓角矩形

canvas.drawRRect(
    RRect.fromLTRBR(0, 0, size.width, size.height, Radius.circular(10)), _paint)

canvas還有很多繪製函數,比如貝塞爾曲線、三次貝塞爾曲線、畫布的反轉等操作,這裏不在一一介紹。

這些函數和Android的Canvas基本一樣,如果你有Android基礎,直接套用即可。

最後奉上一個繪製玫瑰的動畫效果:

這個效果是不是很酷炫,我們看下繪製花骨朵代碼:

///
/// 繪製花骨朵
///
_drawFlower(Canvas canvas, Size size) {
  //將花變為紅色
  if (flowerPaths.length >= RoseData.flowerPoints.length) {
    var path = Path();
    for (int i = 0; i < flowerPaths.length; i++) {
      if (i == 0) {
        path.moveTo(flowerPaths[i].dx, flowerPaths[i].dy);
      } else {
        path.lineTo(flowerPaths[i].dx, flowerPaths[i].dy);
      }
    }
    _paint.style = PaintingStyle.fill;
    _paint.color = _flowerColor;
    canvas.drawPath(path, _paint);
  }
  //繪製線
  _paint.style = PaintingStyle.stroke;
  _paint.color = _strokeColor;
  //去掉最後2個點,最後2個點為了繪製紅色
  var points = flowerPaths.sublist(0, max(0, flowerPaths.length - 2));
  canvas.drawPoints(PointMode.polygon, points, _paint);
}

花骨朵的繪製只通過canvas.drawPath就實現了,其實整個玫瑰花的繪製都是通過canvas.drawPath加上動畫控制實現的。

CustomPaint可以實現任何你想要的動畫的效果,比如繪畫版就可以通過此控件實現。

獲取完整代碼方式掃碼下方二維碼回復:rose

交流

老孟Flutter博客地址(近200個控件用法):http://laomengit.com

歡迎加入Flutter交流群(微信:laomengit)、關注公眾號【老孟Flutter】:

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

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

和付費網盤說再見,跟着本文自己起個網盤(Java 開源項目)_租車

9{icon} {views}

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

本文適合有 Java 基礎知識的人群,跟着本文可學習和運行 Java 網盤項目。

本文作者:HelloGitHub-秦人

HelloGitHub 推出的《講解開源項目》系列。

今天給大家帶來一款開源 Java 版網盤項目—— kiftd-source,本文將用 3 分鐘帶大家搭建一個個人網盤,技術便利生活,你值得擁有~

項目地址:https://github.com/KOHGYLW/kiftd-source

一、項目介紹

kiftd 是一款開源、使用簡單、功能完整的 Java 網盤/雲盤系統。支持在線視頻播放、文檔在線預覽、音樂播放、圖片查看等功能的文件雲存儲平台。

技術棧

  • JDK 版本:1.8.0_131
  • 項目管理框架:Maven(m2e 1.8.0 for Eclipse)
  • Archetype:mavem-archetype-quickstart 1.1
  • Spring Boot:SpringBoot 基於 Spring 開發,旨在提高微服務的開發效率。
  • MyBatis:一款優秀的持久層框架,它支持自定義 SQL、存儲過程以及高級映射。
  • H2 DB:一款開源的嵌入式數據庫引擎,採用 Java 語言編寫,不受平台的限制。

二、網盤搭建

2.1 Windows 環境運行

2.1.1 下載安裝包

直接從官網下載最新的安裝包,安裝地址:https://kohgylw.gitee.io/

項目比較溫馨,支持三種下載方式:Github、阿里雲、Gitee 下載。如下圖:

2.1.2 檢查配置

這裏主要檢查一下本地 JDK 是否已安裝,在命令行窗口執行 java -version 查看 Java 版本。如下所示表示已安裝,就可以進行下一步操作。

java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

2.1.3 運行 jar

雙擊 kiftd-1.0.29-RELEASE.jar,或者在命令行執行 java -jar kiftd-1.0.29-RELEASE.jar 命令都運行可以jar 文件,會彈出安裝的界面,如下圖:

這個界面的這幾個按鈕說明一下:

  • 開啟(Start):運行網盤服務,初次啟動的端口默認是 8080
  • 文件(Files):這個按鈕菜單中主要有網盤文件導入,導出,刪除,刷新功能。
  • 設置(Setting):設置功能主要可以設置網盤的服務端口,網盤的物理存儲路徑等信息。
  • 退出(Exit):關閉網盤系統。

點擊 開啟(Start) 按鈕即可運行網盤,這裏我設置的端口是 8090,在瀏覽器訪問: localhost:8090,運行效果如下圖:

項目是運行了,發現一個問題無法上傳文件?因為我們忘了登錄這個操作。點擊系統 登錄按鈕,填入賬號和密碼即可登錄。那麼登錄密碼在哪裡呢?這裏我直接告訴大家,用戶信息在 conf/account.properties,文件內容如下:

#<This is the default kiftd account setting file. >
#Sun May 10 21:56:28 CST 2020
admin.pwd=000000  #用戶名.密碼=000000
authOverall=l
admin.auth=cudrm
  • 用戶名:admin
  • 密碼:000000

這樣登錄之後就可以使用網盤的所有功能了。

2.2 Linux 環境運行

2.2.1 安裝 Screen 工具

Screen 工具能夠虛擬出一個終端並執行相應的操作。因為本篇所講的網盤需要一個終端。執行如下命令安裝 Screen

yum install screen

2.2.2 Screen 常用命令

screen -S myScreen #創建虛擬終端
java -jar kiftd-1.0.29-RELEASE.jar -console #在虛擬終端中以命令模式啟動 kiftd
screen -r myScreen #返回之前的虛擬終端並繼續操作 kiftd。

2.2.3 命令行操作

Linux 環境上使用 console 模式啟動的效果是這樣的:

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

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

命令行輸入 -start 即可運行項目。例如輸入 -files 控制台显示是這樣的:

其實和 Windows 上一樣,包括文件導入,導出,刪除功能,多了幾個命令是查看文件,切換目錄等功能。

三、開發環境運行

3.1 下載項目

兩種方式下載項目,使用 GitBash 下載項目:

git clone https://github.com/KOHGYLW/kiftd-source.git

另外一種方式直接下載 zip 壓縮包,如下圖:

3.2 運行

打開 kohgylw.kiftd.mc.MC 類,進行測試運行。注意:本文使用 Eclipse 工具打開。

3.3 閱讀代碼

3.3.1 前台請求

就以創建目錄這個功能為例。我們先看前端功能。點擊“操作”->“新建文件夾”,填寫文件夾名稱,點擊保存如下圖:

我們知道前台 新建文件夾 功能調用的後台接口是 newFolder.ajax

3.3.2 後端接口
通過前台請求可知調用的後台接口為 homeController/newFolder.ajax。打開代碼實現,我們會看到下面這個方法。

public String newFolder(final HttpServletRequest request) {
		
        ...
        //參數校驗的部分代碼已省略
		Folder f = new Folder();
		f.setFolderId(UUID.randomUUID().toString());
		f.setFolderName(folderName);
		f.setFolderCreationDate(ServerTimeUtil.accurateToDay());
		if (account != null) {
			f.setFolderCreator(account);
		} else {
			f.setFolderCreator("匿名用戶");
		}
		f.setFolderParent(parentId);
		int i = 0;
		while (true) {
			try {
                // 數據庫插入新建文件夾的數據
				final int r = this.fm.insertNewFolder(f);
				if (r > 0) {
					if (fu.isValidFolder(f)) {
						this.lu.writeCreateFolderEvent(request, f);
						return "createFolderSuccess";
					} else {
						return "cannotCreateFolder";
					}
				}
				break;
			} catch (Exception e) {
				f.setFolderId(UUID.randomUUID().toString());
				i++;
			}
			if (i >= 10) {
				break;
			}
		}
		return "cannotCreateFolder";
	}

四、功能說明

4.1 上傳

  1. 點擊 操作,可以上傳文件和上傳文件夾,如下圖:

  2. 將本地需要上傳的文件,拖拽網盤頁面也可以上傳此文件。

4.2 視頻/音頻播放

  1. 上傳視頻到網盤,網盤也支持在線視頻播放,效果如下圖:

  2. 上傳音頻,例如我最喜歡 周杰倫 的歌曲,可以在線播放了。

4.3 快捷鍵使用

網盤還對一些常用功能添加了快捷鍵。功能和快捷鍵參照如下:

功能 快捷鍵
上傳文件夾 Shift +U
上傳文件 Shift +F
新建文件 Shift +N
複製 Shift +C
剪切 Shift +X
刪除 Shift +D

4.4 配置文件修改

配置文件在項目 conf 目錄,包括兩個配置文件:

  • account.properties:配置賬號信息,權限信息
  • server.properties:服務器的配置文件,可配置服務器端口,緩衝文件大小等

4.5 在線預覽

網盤支持文檔 txtpdfdocxppt 在線預覽功能,支持圖片的在線預覽。圖片預覽效果如下:

pdf 文件預覽效果如下:

4.6 分享下載鏈接

網盤也考慮文件的分享,它可以生成下載鏈接,瀏覽器訪問下載鏈接就可以直接下載文件。選擇需要下載的文件,點擊 下載 按鈕,選擇 下載鏈接+,既可以生成文件下載鏈接。如下圖:

五、最後

教程至此已經結束,你自己的網盤跑起來了嗎?網盤是不是還不錯?而且搭建也特別簡單。一些重要的東西就可以存放到自己的網盤啦!說到底,編程語言只是工具,我們只要很好的使用工具,再加上自己天馬行空的思想,我想會創造出更多不可思議的項目。

Java 語言為什麼經久不衰,因為它能做的事情太多了,而且生態也特別豐富。如果你也有興趣那就加入 Javaer 開發者的大家庭吧!開源分享讓我們彼此認識,有了開源項目讓我們看到編程語言的絢麗多彩。

教程至此,你應該也能快速運行個人網盤了。編程是不是也特別有意思呢?先下載安裝包給自己部署一套網盤系統吧。對源碼感興趣的朋友可以開始學習項目源碼了~

關注公眾號加入交流群

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

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

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

日本科學博物館免費開放線上數位恐龍展覽室,用 360 影像觀賞恐龍化石_貨運

1{icon} {views}

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

恐龍,這些曾經存活在地球的巨大生物一直受到很多人的喜愛,不管大朋友還是小朋友都能由恐龍的生態變化中窺知地球環境變遷等頗富教育意義的課題。在疫情問題全球化的現在,爸媽也不太敢帶孩子前往人潮擁擠的密閉環境中,不過現在你可以跟孩子一起在家透過網路觀賞各種恐龍化石。

日本科學博物館免費開放線上數位恐龍展覽室,用 360 影像觀賞恐龍化石

從 2013 年以來,日本國家科學博物館就開始與凸版印刷株式會社合作,持續開發和利用 VR 技術來將恐龍化石的 3D 形狀測量數據紀錄下來,並且將內容 VR 化。近日更於網路上免費開放了「數位恐龍展覽室」(ディノ・ネット デジタル恐竜展示室),將日本各博物館館藏的 7 種、9 款恐龍化石以 VR 技術做成可供網友進行 360 度觀賞的模型影像,讓大家能夠藉由網路從各種不同角度來觀賞化時的樣貌。
【免費觀賞恐龍化石 3D 影像,點這裡】

在這次開放的素材中,包含異特龍(Allosaurus)與厚頭龍(Pachycephalosaurus)的測量數據,另外還有群馬縣自然歷史博物館、北海道大學綜合博物館、鵡川穗別博物館等珍貴的館藏恐龍化石的實際測量 3D 影像 和 VR 影像。當你點選後網站會引導以內嵌的 Sketchfub 來開啟 3D 與 VR 影像,你可以用滑鼠來旋轉、點擊縮放影像,部分恐龍影像上還有標註點可供點擊查看資訊(不過是日文)。

這些被上網公開的恐龍資料都是實際測量後的結果,轉換成數位 3D 與 VR 檔案的工程也非常浩大,所以內容實在相當珍貴,接下來寒假要到了,爸媽可以和孩子一起觀看恐龍化石影響,來一個了解恐龍的親子共讀寓教於樂時光。

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念