Vue 数据更新但页面没有更新的 7 种情况,你遇到过几种

thbcm阅读(254)

文章来源于公众号:像素摇摆 ,作者Leiy

1. Vue 无法检测实例被创建时不存在于 data 中的 property

原因:由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

场景:

var vm = new Vue({
  data:{},
  // 页面不会变化
  template: '<div>{{message}}</div>'
})
vm.message = 'Hello!' // `vm.message` 不是响应式的

解决办法:

var vm = new Vue({
  data: {
    // 声明 a、b 为一个空值字符串
    message: '',
  },
  template: '<div>{{ message }}</div>'
})
vm.message = 'Hello!'

2. Vue 无法检测对象 property 的添加或移除

原因:官方 – 由于 JavaScript(ES5) 的限制,Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。

场景:

var vm = new Vue({
  data:{
    obj: {
      id: 001
    }
  },
  // 页面不会变化
  template: '<div>{{ obj.message }}</div>'
})


vm.obj.message = 'hello' // 不是响应式的
delete vm.obj.id       // 不是响应式的

解决办法:

// 动态添加 - Vue.set
Vue.set(vm.obj, propertyName, newValue)


// 动态添加 - vm.$set
vm.$set(vm.obj, propertyName, newValue)


// 动态添加多个
// 代替 Object.assign(this.obj, { a: 1, b: 2 })
this.obj = Object.assign({}, this.obj, { a: 1, b: 2 })


// 动态移除 - Vue.delete
Vue.delete(vm.obj, propertyName)


// 动态移除 - vm.$delete
vm.$delete(vm.obj, propertyName)

3. Vue 不能检测通过数组索引直接修改一个数组项

原因:官方 – 由于 JavaScript 的限制,Vue 不能检测数组和对象的变化;尤雨溪 – 性能代价和获得用户体验不成正比。

场景:

var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // 不是响应性的

解决办法:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)


// vm.$set
vm.$set(vm.items, indexOfItem, newValue)


// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

拓展:Object.defineProperty() 可以监测数组的变化

Object.defineProperty() 可以监测数组的变化。但对数组新增一个属性(index)不会监测到数据变化,因为无法监测到新增数组的下标(index),删除一个属性(index)也是。

场景:

var arr = [1, 2, 3, 4]
arr.forEach(function(item, index) {
    Object.defineProperty(arr, index, {
    set: function(value) {
      console.log('触发 setter')
      item = value
    },
    get: function() {
      console.log('触发 getter')
      return item
    }
  })
})
arr[1] = '123'  // 触发 setter
arr[1]          // 触发 getter 返回值为 "123"
arr[5] = 5      // 不会触发 setter 和 getter

4. Vue 不能监测直接修改数组长度的变化

原因:官方 – 由于 JavaScript 的限制,Vue 不能检测数组和对象的变化;尤雨溪 – 性能代价和获得用户体验不成正比。

场景:

var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items.length = 2 // 不是响应性的

解决办法:

vm.items.splice(newLength)

5. 在异步更新执行之前操作 DOM 数据不会变化

原因:Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.thenMutationObserversetImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

场景:

<div id="example">{{message}}</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
vm.$el.style.color = 'red' // 页面没有变化

解决办法:

var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改数据
//使用 Vue.nextTick(callback) callback 将在 DOM 更新完成后被调用
Vue.nextTick(function () {
  vm.$el.textContent === 'new message' // true
  vm.$el.style.color = 'red' // 文字颜色变成红色
})

拓展:异步更新带来的数据响应的误解

<!-- 页面显示:我更新啦! -->
<div id="example">{{message.text}}</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: {},
  }
})
vm.$nextTick(function () {
  this.message = {}
  this.message.text = '我更新啦!'
})

上段代码中,我们在 data 对象中声明了一个 message 空对象,然后在下次 DOM 更新循环结束之后触发的异步回调中,执行了如下两段代码:

this.message = {};
this.message.text = '我更新啦!'

到这里,模版更新了,页面最后会显示 我更新啦!

模板更新了,应该具有响应式特性,如果这么想那么你就已经走入了误区。

一开始我们在 data 对象中只是声明了一个 message 空对象,并不具有 text 属性,所以该 text 属性是不具有响应式特性的。

但模板切切实实已经更新了,这又是怎么回事呢?

那是因为 Vue.js 的 DOM 更新是异步的,即当 setter 操作发生后,指令并不会立马更新,指令的更新操作会有一个延迟,当指令更新真正执行的时候,此时 text 属性已经赋值,所以指令更新模板时得到的是新值。

模板中每个指令/数据绑定都有一个对应的 watcher 对象,在计算过程中它把属性记录为依赖。之后当依赖的 setter 被调用时,会触发 watcher 重新计算 ,也就会导致它的关联指令更新 DOM。

具体流程如下所示:

  • 执行 this.message = {}; 时, setter 被调用。
  • Vue.js 追踪到 message 依赖的 setter 被调用后,会触发 watcher 重新计算。
  • this.message.text = '我更新啦!';text 属性进行赋值。
  • 异步回调逻辑执行结束之后,就会导致它的关联指令更新 DOM,指令更新开始执行。

所以真正的触发模版更新的操作是 this.message = {};这一句引起的,因为触发了 setter,所以单看上述例子,具有响应式特性的数据只有 message 这一层,它的动态添加的属性是不具备的。

对应上述第二点 – Vue 无法检测对象 property 的添加或移除

6. 循环嵌套层级太深,视图不更新?

看到网上有些人说数据更新的层级太深,导致数据不更新或者更新缓慢从而导致试图不更新?

由于我没有遇到过这种情况,在我试图重现这种场景的情况下,发现并没有上述情况的发生,所以对于这一点不进行过多描述(如果有人在真实场景下遇到这种情况留个言吧)。

针对上述情况有人给出的解决方案是使用强制更新:

如果你发现你自己需要在 Vue 中做一次强制更新,99.9% 的情况,是你在某个地方做错了事。

vm.$forceUpdate()

7. 拓展:路由参数变化时,页面不更新(数据不更新)

拓展一个因为路由参数变化,而导致页面不更新的问题,页面不更新本质上就是数据没有更新。

原因:路由视图组件引用了相同组件时,当路由参会变化时,会导致该组件无法更新,也就是我们常说中的页面无法更新的问题。

场景:

<div id="app">
  <ul>
    <li><router-link to="/home/foo">To Foo</router-link></li>    
    <li><router-link to="/home/baz">To Baz</router-link></li>    
    <li><router-link to="/home/bar">To Bar</router-link></li>    
  </ul>    
  <router-view></router-view>
</div>
const Home = {
  template: `<div>{{message}}</div>`,
  data() {
    return {
      message: this.$route.params.name
    }
  }
}


const router = new VueRouter({
  mode:'history',
    routes: [
    {path: '/home', component: Home },
    {path: '/home/:name', component: Home }
  ]
})


new Vue({
  el: '#app',
  router
})

上段代码中,我们在路由构建选项 routes 中配置了一个动态路由'/home/:name',它们共用一个路由组件 Home,这代表他们复用 RouterView

当进行路由切换时,页面只会渲染第一次路由匹配到的参数,之后再进行路由切换时,message 是没有变化的。

解决办法:

解决的办法有很多种,这里只列举我常用到几种方法。

  1. 通过 watch 监听 $route 的变化。
    const Home = {
      template: `<div>{{message}}</div>`,
      data() {
        return {
          message: this.$route.params.name
        }
      },
      watch: {
       '$route': function() {
       this.message = this.$route.params.name
        }
        }
    }
    ...
    new Vue({
      el: '#app',
      router
    })

  1. <router-view> 绑定 key 属性,这样 Vue 就会认为这是不同的 <router-view>

弊端:如果从 /home 跳转到 /user 等其他路由下,我们是不用担心组件更新问题的,所以这个时候 key 属性是多余的。

    <div id="app">
      ...
  <router-view :key="key"></router-view>
    </div>

以上就是W3Cschool编程狮关于Vue 数据更新但页面没有更新的 7 种情况,你遇到过几种的相关介绍了,希望对大家有所帮助。

2020年,选择Vue的公司越来越多了

thbcm阅读(256)

7月17日,Vue3.0进入了RC.1阶段,7月20日,Vue3.0进入了RC.2阶段。

RC阶段即是发行候选版本,如果未出现问题则可发布成为正式版本。多数开源软件会推出两个RC版本,最后的 RC2 就属于正式版本。

这代表了什么?其实很简单,代表的就是Vue3.0已经步入非常成熟的阶段。

在国内最受欢迎的两大前端框架中,虽然我们很难分清Vue与React孰优孰劣,但仅从招聘需求来看,显然选择招聘 Vue 技术栈的公司已经远超 React 技术栈。

Vue 的设计初衷便是容易入门和尽可能精简,现在 Vue3.0 便是实现了比 Vue2.0 更快、更小、更易于维护、更多的原生支持与更易于开发使用等改进。而这些特点,都构成了让 Vue 受到越来越多公司欢迎的关键因素。

岗位需求大,一方面让我们获得了更多面试机会,但另一方面也说明了使用 Vue 的人数同样众多,竞争无比激烈。

JavaScript ES2020-您应该知道的功能

thbcm阅读(177)

文章来源于公众号:前端人

ES2020 或 ECMAScript 2020 为 JavaScript 带来了令人兴奋的功能。在本文中,我想谈谈我最喜欢的 ES2020 功能。这意味着本文并未涵盖所有新增内容。因此,让我们看看我最喜欢的新功能:

  • 动态导入
  • 空位合并运算符
  • 可选链接运算符
  • 私有类变量Private
  • Promise.allSettled
  • 有关更多信息和所有其他内容,请查看官方的ECMAScript语言规范

动态导入

其中之一是我们可以使用async / await动态导入依赖项。这意味着我们不必先导入所有内容,并且仅在需要它们时才可以导入依赖项。结果,通过在运行时加载模块来提高应用程序的性能。

它如何提高性能?使用常规模块系统,我们在程序开始时静态导入模块。无论我们现在还是以后需要它们,我们都必须先导入它们。另外,在加载时会评估来自导入模块的所有代码。因此,它不必要地减慢了应用程序的速度。为什么?因为它会在执行代码之前下载导入的模块并评估每个模块的代码。

让我们来看一个例子。

if (calculations) {
    const calculator = await import('./calculator.js');
    const result = calculator.add(num1, num2);
    console.log(result);
}

在上面的代码片段中,您可以看到我们仅在要执行计算时才导入计算器模块。因此,我们不会通过在运行时之前加载所有代码来不必要地降低应用程序的速度。因此,动态导入是一个方便的补充。

空位合并运算符

“空值合并运算符(??)是一种逻辑运算符,当其左侧操作数为 null 或未定义时,将返回其右侧操作数,否则将返回其左侧操作数。”

基本上,Nullish合并运算符使我们可以检查值是否为 null 或未定义,并在这种情况下提供回退值。让我们看一个例子:

let score = 0;
let pass = score ?? 60;


console.log(pass);

在上面的代码段中,值为 pass0 。原因是 ?? 运算符不会将零强制为伪造的值。变量 pass 只得到 60 分配如果变量 score 是undefinednull

但是,双管道“ ||”之间有什么区别?和这个运算符?当使用双管道“ ||”时,它总是返回真实值,这可能会导致某些意外结果。当使用空值合并运算符时,我们可以更加严格,因此仅当该值为 null 或未定义时才允许使用默认值。

例如,假设我们有以下代码:

let score = 0;
let pass = score || 60;


console.log(pass);

在上面的示例中,使用时,值 0 被视为虚假值 || 。在上面的代码片段中,值 pass 是 60 ,这不是我们想要的。因此,双问号允许我们检查变量是否为空,这意味着变量是未定义还是为空。

Promise.allSettled

使用可选的链接运算符,我们可以从对象访问深度嵌套的属性。如果属性存在,则运算符返回其值。如果该属性不存在,则运算符返回 undefined 。

const person = {
 name: "Catalin Pit",
 employer: {
  name: "Catalins Tech",
 }
};


console.log(person?.employer?.name);

上面的代码段说明了访问深度嵌套的对象属性的示例。但是,我们可以在数组和函数调用上使用它。在下面的代码段中,您可以看到我们检查数组是否存在,如果存在,则访问第三个值。此外,您还可以查看检查 API 是否存在某个函数,如果存在,则将其调用。

const allowedValues = [1, 5, 10, 13, 90, 111];
console.log(allowedValues?.[2]);


// functional call
const response = myAPI.getData?.();

总之,可选的链接运算符非常方便,它还有助于我们使代码更具可读性和简短性。

私有类变量

从现在开始,我们也可以在 JavaScript 中的类中创建私有变量。制作私有变量所需要做的就是在变量前面添加哈希符号。例如,#firstName 是一个私有变量,不能在类外部访问。

尝试在类外部调用该变量会导致 SyntaxError 。

class Person {
  #firstName = "Catalin";
  getFirstName() 
  { 
      console.log(this.#firstName) 
   }
}


const person1 = new Person();
person1.getFirstName() // "Catalin"


// Returns "undefined"
console.log(person1.firstName); 
// Returns "Uncaught SyntaxError: Private field '#firstName' must be declared in an enclosing class"
console.log(person1.#firstName); 

在上面的代码中,您可以看到一个私有类变量在起作用。尝试 firstName 在类外访问变量,我们收到一个错误。因此,当我们不想在类外公开数据时,添加便很方便。

文章中的补充内容并非唯一。还有更多,我鼓励您查看官方的 ECMAScript 语言规范来查看所有这些内容。

以上就是W3Cschool编程狮关于JavaScript ES2020-您应该知道的功能的相关介绍了,希望对大家有所帮助。

Java字符串操作的12个小技巧!

thbcm阅读(240)

文章来源于公众号:沉默王二 ,作者沉默王二

字符串可以说是 Java 中最具有代表性的类了,似乎没有之一哈,这就好像直播界的李佳琪,脱口秀中的李诞,一等一的大哥地位。不得不承认,最近吐槽大会刷多了,脑子里全是那些段子,写文章都有点不由自主,真的是,手不由己啊。

字符串既然最常用,那就意味着面试官好这一口,就喜欢问一些字符串方面的编码技巧,来测试应聘者是否技术过硬,底子扎实,对吧?

那这次,我就来盘点 12 个精致的 Java 字符串操作小技巧,来帮助大家提高一下下。在查看我给出的答案之前,最好自己先动手尝试一遍,写不出来答案没关系,先思考一遍,看看自己的知识库里是不是已经有解决方案,有的话,就当是温故复习了,没有的话,也不要担心,刚好学一遍。

01、如何在字符串中获取不同的字符及其数量?

这道题可以拆解为两个步骤,第一步,找出不同的字符,第二步,统计出它们的数量。好像有点废话,是不是?那我先来一个答案吧。

public class DistinctCharsCount {
    public static void main(String[] args) {
        printDistinctCharsWithCount("itwanger");
        printDistinctCharsWithCount("chenmowanger");
    }


    private static void printDistinctCharsWithCount(String input) {
        Map<Character, Integer> charsWithCountMap = new LinkedHashMap<>();


        for (char c : input.toCharArray()) {
            Integer oldValue = charsWithCountMap.get(c);


            int newValue = (oldValue == null) ? 1 :
                    Integer.sum(oldValue, 1);


            charsWithCountMap.put(c, newValue);
        }
        System.out.println(charsWithCountMap);
    }
}

程序输出的结果是:

{i=1, t=1, w=1, a=1, n=1, g=1, e=1, r=1}
{c=1, h=1, e=2, n=2, m=1, o=1, w=1, a=1, g=1, r=1}

说一下我的思路:

1)声明一个 LinkedHashMap,也可以用 HashMap,不过前者可以保持字符串拆分后的顺序,结果看起来更一目了然。

为什么要用 Map 呢?因为 Map 的 key 是不允许重复的,刚好可以对重复的字符进行数量的累加。

2)把字符串拆分成字符,进行遍历。

3)如果 key 为 null 的话,就表明它的数量要 +1;否则的话,就在之前的值上 +1,然后重新 put 到 Map 中,这样就覆盖了之前的字符数量。

思路很清晰,对不对?忍不住给自己鼓个掌。

那,JDK 8 之后,Map 新增了一个很厉害的方法 merge(),一次性为多个键赋值:

private static void printDistinctCharsWithCountMerge(String input) {
    Map<Character, Integer> charsWithCountMap = new LinkedHashMap<>();


    for (char c : input.toCharArray()) {
        charsWithCountMap.merge(c, 1, Integer::sum);
    }
    System.out.println(charsWithCountMap);
}

有没有很厉害?一行代码就搞定。第一个参数为键,第二个参数为值,第三个参数是一个 BiFunction,意思是,如果键已经存在了,就重新根据 BiFunction 计算新的值。

如果字符是第一次出现,就赋值为 1;否则,就把之前的值 sum 1。

02、如何反转字符串?

如果同学们对 StringBuilder 和 StringBuffer 很熟悉的话,这道题就很简单,直接 reverse() 就完事,对不对?

public class ReverseAString {
    public static void main(String[] args) {
        reverseInputString("编程狮");
    }
    private static void reverseInputString(String input) {
        StringBuilder sb = new StringBuilder(input);
        String result = sb.reverse().toString();
        System.out.println(result);
    }
}

输出结果如下所示:

编程狮

多说一句,StringBuffer 和 StringBuilder 很相似,前者是同步的,所有 public 方法都加了 synchronized 关键字,可以在多线程中使用;后者是不同步的,没有 synchronized 关键字,所以性能更佳,没有并发要求的话,就用 StringBuilder。

03、如何判断一个字符串是前后对称的?

什么意思呢?就好像一个字符串,前后一折,是对称的。就像你站在镜子前,看到了一个玉树临风、闭月羞花的自己。

public class PalindromeString {
    public static void main(String[] args) {


        checkPalindromeString("编程狮");
        checkPalindromeString("编程狮 狮程编");
    }


    private static void checkPalindromeString(String input) {
        boolean result = true;
        int length = input.length();
        for (int i = 0; i < length / 2; i++) {
            if (input.charAt(i) != input.charAt(length - i - 1)) {
                result = false;
                break;
            }
        }
        System.out.println(input + " 对称吗? " + result);


    }
}

输出结果如下所示:

编程狮 对称吗? false
编程狮 狮程编 对称吗? true

说一下我的思路:要判断字符串对折后是否对称,很简单,从中间劈开,第一个字符对照最后一个字符,一旦找到不等的那个,就返回 false。

注意三点:

1)for 循环的下标从 0 开始,到 length/2 结束。

2)下标 i 和 length-i-1 是对称的。

3)一旦 false 就 break。

04、如何删除所有出现的指定字符?

字符串类没有提供 remove() 方法,但提供了 replaceAll() 方法,通过将指定的字符替换成空白字符就可以办得到,对吧?

public class RemoveCharFromString {
    public static void main(String[] args) {
        removeCharFromString("编程狮", '狮');
        removeCharFromString("bianchengshi", 'n');


    }


    private static void removeCharFromString(String input, char c) {
        String result = input.replaceAll(String.valueOf(c), "");
        System.out.println(result);
    }
}

输出结果如下所示:

编程
biachegshi

05、如何证明字符串是不可变的?

字符串不可变的这个事我曾写过两篇文章,写到最后我都要吐了。但是仍然会有一些同学弄不明白,隔段时间就有人私信我,我就不得不把之前的文章放到收藏夹,问的时候我就把链接发给他。

之所以造成这个混乱,有很多因素,比如说,Java 到底是值传递还是引用传递?字符串常量池是个什么玩意?

这次又不得不谈,虽然烦透了,但仍然要证明啊!

public class StringImmutabilityTest {
    public static void main(String[] args) {
        String s1 = "编程狮二";
        String s2 = s1;
        System.out.println(s1 == s2);


        s1 = "编程狮三";
        System.out.println(s1 == s2);


        System.out.println(s2);
    }
}

输出结果如下所示:

true
false
编程狮二

1)String s1 = "编程狮二",Java 在字符串常量池中创建“编程狮二”这串字符的对象,并且把地址引用赋值给 s1

2)String s2 = s1,s2 和 s1 指向了同一个地址引用——常量池中的那个“编程狮二”。

所以,此时 s1 == s2 为 true。

3)s1 = "编程狮三",Java 在字符串常量池中创建“编程狮三”这串字符的对象,并且把地址引用赋值给 s1,但 s2 仍然指向的是“编程狮二”那串字符对象的地址引用。

所以,此时 s1 == s2 为 false,s2 的输出结果为“编程狮二”就证明了字符串是不可变的。

06、如何统计字符串中的单词数?

这道题呢?主要针对的是英文字符串的情况。虽然中文字符串中也可以有空白字符,但不存在单词这一说。

public class CountNumberOfWordsInString {
    public static void main(String[] args) {
        countNumberOfWords("My name is Wanger");
        countNumberOfWords("I Love Java Programming");
        countNumberOfWords(" Java    is  very   important ");
    }


    private static void countNumberOfWords(String line) {
        String trimmedLine = line.trim();
        int count = trimmedLine.isEmpty() ? 0 : trimmedLine.split("\\s+").length;


        System.out.println(count);
    }
}

输出结果如下所示:

4
4
4

split() 方法可以对字符串进行拆分,参数不仅可以是空格,也可以使正则表达式代替的空白字符(多个空格、制表符);返回的是一个数组,通过 length 就可以获得单词的个数了。

07、如何检查两个字符串中的字符是相同的?

如何理解这道题呢?比如说,字符串“沉默王二”和“沉王二默”就用了同样的字符,对吧?比如说,字符串“沉默王二”和“沉默王三”用的字符就不同,理解了吧?

public class CheckSameCharsInString {
    public static void main(String[] args) {
        sameCharsStrings("编程狮", "狮编程");
        sameCharsStrings("编程狮王", "编程狮子");
    }


    private static void sameCharsStrings(String s1, String s2) {
        Set<Character> set1 = s1.chars().mapToObj(c -> (char) c).collect(Collectors.toSet());
        System.out.println(set1);
        Set<Character> set2 = s2.chars().mapToObj(c -> (char) c).collect(Collectors.toSet());
        System.out.println(set2);
        System.out.println(set1.equals(set2));
    }
}

输出结果如下所示:

[编, 程, 狮]
[编, 程, 狮]
true
[编, 程, 狮, 王]
[编, 程, 子, 狮]
false

上面的代码用到了 Stream 流,看起来很陌生,但很好理解,就是把字符串拆成字符,然后收集到 Set 中,Set 是一个不允许有重复元素的集合,所以就把字符串中的不同字符收集起来了。

08、如何判断一个字符串包含了另外一个字符串?

这道题有点简单,对吧?上一道还用 Stream 流,这道题就直接送分了?不用怀疑自己,就用字符串类的 contains() 方法。

public class StringContainsSubstring {
    public static void main(String[] args) {
        String s1 = "编程狮";
        String s2 = "编程";


        System.out.println(s1.contains(s2));
    }
}

输出结果如下所示:

true

contains() 方法内部其实调用的是 indexOf() 方法:

public boolean contains(CharSequence s) {
    return indexOf(s.toString()) >= 0;
}

09、如何在不用第三个变量的情况下交换两个字符串?

这道题就有点意思了,对吧?尤其是前提条件,不使用第三个变量。

public class SwapTwoStrings {
    public static void main(String[] args) {
        String s1 = "编程";
        String s2 = "狮";


        s1 = s1.concat(s2);
        s2 = s1.substring(0,s1.length()-s2.length());
        s1 = s1.substring(s2.length());


        System.out.println(s1);
        System.out.println(s2);
    }
}

输出结果如下所示:

狮
编程

说一下我的思路:

1)通过 concat() 方法把两个字符串拼接到一块。

2)然后通过 substring() 方法分别取出第二个字符串和第一个字符串。

10、如何从字符串中找出第一个不重复的字符?

来,上个例子来理解一下这道题。比如说字符串“编程狮编编程王”,第一个不重复的字符是“狮”,对吧?因为“编”重复了,“程”重复了。

public class FindNonRepeatingChar {
    public static void main(String[] args) {
        System.out.println(printFirstNonRepeatingChar("编程狮编编程王"));
        System.out.println(printFirstNonRepeatingChar("编程狮编"));
        System.out.println(printFirstNonRepeatingChar("狮狮狮"));
    }


    private static Character printFirstNonRepeatingChar(String string) {
        char[] chars = string.toCharArray();


        List<Character> discardedChars = new ArrayList<>();


        for (int i = 0; i < chars.length; i++) {
            char c = chars[i];


            if (discardedChars.contains(c))
                continue;


            for (int j = i + 1; j < chars.length; j++) {
                if (c == chars[j]) {
                    discardedChars.add(c);
                    break;
                } else if (j == chars.length - 1) {
                    return c;
                }
            }
        }
        return null;
    }
}

输出结果如下所示:

狮
程
null

说一下我的思路:

1)把字符串拆分成字符数组。

2)声明一个 List,把重复的字符放进去。

3)外层的 for 循环,从第一个字符开始,如果已经在 List 中,继续下一轮。

4)嵌套的 for 循环,从第一个字符的下一个字符(j = i + 1)开始遍历,如果找到和之前字符重复的,就加入到 List 中,跳出内层的循环;如果找到最后(j == chars.length - 1)也没有找到,就是第一个不重复的字符,对吧?

11、如何检查字符串中只包含数字?

有一种很傻的解法,就是用 Long.parseLong(string) 对字符串强转,如果转不成整形,那肯定不是只包含数字,对吧?

但这种方法也太不可取了,所以还得换一种巧妙的,就是使用正则表达式。

public class CheckIfStringContainsDigitsOnly {
    public static void main(String[] args) {
        digitsOnlyString("123 编程狮");
        digitsOnlyString("123");


    }


    private static void digitsOnlyString(String string) {
        if (string.matches("\\d+")) {
            System.out.println("只包含数字的字符串:" + string);
        }
    }
}

输出结果如下所示:

只包含数字:123

12、如何实现字符串的深度拷贝?

由于字符串是不可变的,所以可以直接使用“=”操作符将一个字符串拷贝到另外一个字符串,并且互不影响。

public class JavaStringCopy {
    public static void main(String args[]) {
        String str = "编程狮王";
        String strCopy = str;


        str = "编程狮子";
        System.out.println(strCopy);
    }
}

输出结果如下所示:

编程狮王

这个例子和之前证明字符串是不可变的例子几乎没什么差别,对吧?这的确是因为字符串是不可变的,如果是可变对象的话,深度拷贝就要注意了,最好使用 new 关键字返回新的对象。

public Book getBook() {
    Book clone = new Book();
    clone.setPrice(this.book.getPrice());
    clone.setName(this.book.getName());
    return clone;
}

最后

希望这 12 个精致的字符串操作小技巧可以帮助大家巩固一波基础,反正我自己已经重新巩固了一波,很有收获的样子,感觉就像是“一群小精灵在我脑子里跳舞一样”,学它就对了!

以上就是W3Cschool编程狮关于Java字符串操作的12个小技巧!的相关介绍了,希望对大家有所帮助。

用 Python 为老师送上节日的祝福

thbcm阅读(260)

文章来源于公众号:Python技术 作者:派森酱

提到老师,大家首先想到的可能就是在学校中教我们文化课的人,除此之外,在生活或工作中给予我们指导及帮助的人也可称之为老师,今天是教师节,本文我们就使用 Python 来为所有的老师送上节日的祝福。

向日葵

向日葵的花语包括:信念、光辉、忠诚等,很适合老师,我们先画一个简单的向日葵。

绘制效果如下:

实现代码如下:

turtle.setup(600, 600, 80, 80)
turtle.pencolor("yellow")
turtle.pensize(4)
turtle.penup()
turtle.fd(-150)
turtle.pendown()
for i in range(18):
    turtle.fd(300)
    turtle.left(100)
turtle.fd(150)
turtle.right(90)
turtle.pensize(8)
turtle.pencolor("green")
turtle.fd(400)
turtle.penup()
turtle.pensize(6)
turtle.pendown()
# 绘制叶子
turtle.fd(-250)
turtle.seth(45)
turtle.circle(-130, 60)
turtle.seth(-135)
turtle.circle(-130, 60)
turtle.seth(135)
turtle.circle(130, 60)
turtle.seth(-45)
turtle.circle(130, 60)
turtle.done()

贺卡

我们还可以利用 Python 在照片中添加一些文字来制作贺卡。

原图:

效果图:

主要实现代码:

img = cv2.imread('test.png')
mask = np.zeros(img.shape[:2], np.uint8)
size = (1, 65)
bgd = np.zeros(size, np.float64)
fgd = np.zeros(size, np.float64)
rect = (1, 1, img.shape[1], img.shape[0])
cv2.grabCut(img, mask, rect, bgd, fgd, 10, cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask == 2) | (mask == 0), 1, 255)
img = img.astype(np.int32)
img *= mask2[:, :, np.newaxis]
img[img>255] = 255
img =img.astype(np.uint8)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = Image.fromarray(img, 'RGB')
img.save('test1.jpg')
fp = open(r"word.txt", "r", encoding="utf-8")
text = fp.read()
mask_pic=np.array(Image.open(r"test1.jpg"))
wordcloud = WordCloud(font_path='hyr3gjm.ttf',mask=mask_pic,max_words=200).generate(text)
image=wordcloud.to_image()
image.save("wordcloud2.png")
cloud_data = np.array(image)
alpha = np.copy(cloud_data[:,:,0])
alpha[alpha>0] = 255
new_image = Image.fromarray(np.dstack((cloud_data, alpha)))
card = Image.open("test.png")
card = card.convert("RGBA")
card.paste(new_image, (0,0), mask=new_image)
card.save("card.png")

照片墙

我们也可以利用 Python 将一些照片制作成文字效果的照片墙。

实现效果:

主要实现代码:

# 将字转化为汉字库的点阵数据
def char2bit(textStr):
    KEYS = [0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01]
    target = []
    global count
    count = 0
    for x in range(len(textStr)):
        text = textStr[x]
        rect_list = [] * 16
        for i in range(16):
            rect_list.append([] * 16)
        gb2312 = text.encode('gb2312')
        hex_str = binascii.b2a_hex(gb2312)
        result = str(hex_str, encoding='utf-8')
        area = eval('0x' + result[:2]) - 0xA0
        index = eval('0x' + result[2:]) - 0xA0
        offset = (94 * (area-1) + (index-1)) * 32
        font_rect = None
        with open("HZK16", "rb") as f:
            f.seek(offset)
            font_rect = f.read(32)
        for k in range(len(font_rect) // 2):
            row_list = rect_list[k]
            for j in range(2):
                for i in range(8):
                    asc = font_rect[k * 2 + j]
                    flag = asc & KEYS[i]
                    row_list.append(flag)
        output = []
        for row in rect_list:
            for i in row:
                if i:
                    output.append('1')
                    count+=1
                else:
                    output.append('0')
        target.append(''.join(output))
    return target


# 生成图片文字
def head2char(workspace,folder,self,outlist):
    # 将工作路径转移至头像文件夹
    os.chdir(folder)
    # 获取文件夹内头像列表
    imgList = os.listdir(folder)
    # 获取头像图片个数
    numImages = len(imgList)
    # 设置头像裁剪后尺寸
    eachSize = 100
    # 变量 n 用于循环遍历头像图片
    n=0
    # 变量 count 用于为最终生成的单字图片编号
    count = 0
    # 初始化颜色列表,用于背景着色
    colorlist = ['#FFFACD','#F0FFFF','#BFEFFF','#b7facd','#ffe7cc','#fbccff','#d1ffb8','#febec0','#E0EEE0']
    # index 用来改变不同字的背景颜色
    index = 0
    # 每个 item 对应不同字的点阵信息
    for item in outlist:
        # 将工作路径转到头像所在文件夹
        os.chdir(folder)
        # 新建一个带有背景色的画布,16 * 16点阵,每个点处填充 2 * 2 张头像图片,故长为 16 * 2 * 100
        canvas = Image.new('RGB', (3200, 3200), colorlist[index])  # 新建一块画布
        # index 变换,用于变换背景颜色
        index = (index+1)%9
        count += 1
        # 每个 16 * 16 点阵中的点,用四张 100 * 100 的头像来填充
        for i in range(16*16):
            # 点阵信息为 1,即代表此处要显示头像来组字
            if item[i] == "1":
                # 循环读取连续的四张头像图片
                x1 = n % len(imgList)
                x2 = (n+1) % len(imgList)
                x3 = (n+2) % len(imgList)
                x4 = (n+3) % len(imgList)
                # 以下四组 try,将读取到的四张头像填充到画板上对应的一个点位置
                # 点阵处左上角图片 1/4
                try:
                    # 打开图片
                    img = Image.open(imgList[x1])
                except IOError:
                    print("有1张图片读取失败,已使用备用图像替代")
                    img = Image.open(self)
                finally:
                    # 缩小图片
                    img = img.resize((eachSize, eachSize), Image.ANTIALIAS)
                    # 拼接图片
                    canvas.paste(img, ((i % 16) * 2 * eachSize, (i // 16) * 2 * eachSize))
                # 点阵处右上角图片 2/4
                try:
                    img = Image.open(imgList[x2])
                except IOError:
                    print("有1张图片读取失败,已使用备用图像替代")
                    img = Image.open(self)
                finally:
                    img = img.resize((eachSize, eachSize), Image.ANTIALIAS)
                    canvas.paste(img, (((i % 16) * 2 + 1) * eachSize, (i // 16) * 2 * eachSize))
                # 点阵处左下角图片 3/4
                try:
                    img = Image.open(imgList[x3])
                except IOError:
                    print("有1张图片读取失败,已使用备用图像替代")
                    img = Image.open(self)
                finally:
                    img = img.resize((eachSize, eachSize), Image.ANTIALIAS)
                    canvas.paste(img, ((i % 16) * 2 * eachSize, ((i // 16) * 2 + 1 ) * eachSize))
                # 点阵处右下角图片 4/4
                try:
                    img = Image.open(imgList[x4])
                except IOError:
                    print("有1张图片读取失败,已使用备用图像替代")
                    img = Image.open(self)
                finally:
                    img = img.resize((eachSize, eachSize), Image.ANTIALIAS)
                    canvas.paste(img, (((i % 16) * 2 + 1) * eachSize, ((i // 16) * 2 + 1) * eachSize))
                #调整 n 以读取后续图片
                n= (n+4) % len(imgList)
        os.chdir(workspace)
        # 创建文件夹用于存储输出结果
        if not os.path.exists('output'):
            os.mkdir('output')
        os.chdir('output')
        # 存储将拼接后的图片,quality 为图片质量,1 - 100,100 最高
        canvas.save('result%d.jpg'% count, quality=100)

总结

本文,我们利用 Python 实现了 3 种送祝福的方式,你有哪些更好或有趣的实现方式吗?

以上就是W3Cschool编程狮关于用 Python 为老师送上节日的祝福的相关介绍了,希望对大家有所帮助。

十分钟搞懂机器学习中的余弦相似性

thbcm阅读(283)

余弦(余弦函数)是三角函数的一种。

在直角三角形中,某个锐角的余弦等于它的邻边与斜边的比值,比如下图中角A的余弦就可以这样计算:cosA=b/c 。




在非直角三角形中,我们可以使用余弦定理:任意三角形中任一边的平方等于其他两边的平方和减去这两边与夹角余弦乘积的两倍,用公式表达就是:




我们对这个公式做一下变换,余弦就可以表示为:




直角三角形中锐角的余弦计算其实是余弦定理的一个特例,计算过程如下:

(1)首先,我们了解直角三角形中的勾股定理:




(2)将上边公式中
替换为
,最后就能得出直角三角形中锐角的余弦计算方法:


SMOTE与SMOGN算法R语言代码

thbcm阅读(238)

  在之前的文章
SMOGN算法Python实现:解决回归分析中的数据不平衡中,我们介绍了基于
Python语言中的
smogn包,实现
SMOGN算法,对机器学习、深度学习回归中
训练数据集不平衡的情况加以解决的具体方法;而我们也在上述这一篇文章中提到了,
SMOGN算法的
Python实现实在是太慢了,且
Python还无法较为方便地实现回归数据的
SMOTE算法。因此,我们就在本文中介绍一下基于
R语言中的
UBL包,实现
SMOTE算法与
SMOGN算法的方法。对于这两种算法的具体介绍与对比,大家参考上述提到的这一篇文章即可,这里就不再赘述了。

归约证明在密码学中的应用

thbcm阅读(308)

归约证明是一种证明方法,通过将一个待证明的问题(目标问题)转换为另一个已知难解的问题(基准问题),来证明目标问题的难度不低于基准问题。简单来说,假设我们已经知道某个问题很难解决,如果能证明我们要研究的问题至少和这个已知的难题一样难解,那么就可以认为我们的问题也具有相应的安全性。

【SQL】Lag/Rank/Over窗口函数揭秘,数据分析之旅

thbcm阅读(241)

正当她全身心投入到新环境的学习与探索中时,微信工作群的一则消息如同夏日里的一阵清风,为她带来了新的机遇。逸尘,作为某项目微信群中的甲方代表,通过该群发布了紧急的数据分析任务,而他当时并未意识到是小悦将会被指定来负责处理这项任务。虽然两人上周在咖啡馆的偶遇只是匆匆一瞥,但那次偶遇似乎为这次合作埋下了一丝伏笔。

联系我们