GVKun编程网logo

Rust Concept Clarification: Deref vs AsRef vs Borrow vs Cow

25

在本文中,我们将带你了解RustConceptClarification:DerefvsAsRefvsBorrowvsCow在这篇文章中,同时我们还将给您一些技巧,以帮助您实现更有效的-集合遍历for

在本文中,我们将带你了解Rust Concept Clarification: Deref vs AsRef vs Borrow vs Cow在这篇文章中,同时我们还将给您一些技巧,以帮助您实现更有效的- 集合 遍历 foreach Iterator 并发修改 ConcurrentModificationException MD、AI - TensorFlow - 分类与回归(Classification vs Regression)、android – java.util.ConcurrentModificationException和SharedPreference、Appium问题解决方案(8)- selenium.common.exceptions.WebDriverException: Message: An unknown server-side error occurred while processing the command. Original error: Could not sign with default certificate.

本文目录一览:

Rust Concept Clarification: Deref vs AsRef vs Borrow vs Cow

Rust Concept Clarification: Deref vs AsRef vs Borrow vs Cow

本篇是《Rust 概念解惑 | Deref vs AsRef vs Borrow vs Cow》的英文版,得益于现在翻译高科技,我终于可以用英文完成长篇大论了。

此文也发布于  https://dev.to/zhanghandong/rust-concept-clarification-deref-vs-asref-vs-borrow-vs-cow-13g6

Understanding by Module

In fact, the classification by standard library will first give you a small idea of what they do.

  1. std [1]:: ops [2]:: Deref [3] . As you can see, Deref is categorized as an ops module. If you look at the documentation, you will see that this module defines the   trait [4]  fr all the overloadable operators. For example, Add trait corresponds to +, while Deref trait  corresponds to a shared(immutable) borrowing dereference operation, such as *v . Correspondingly, there is also the DerefMut trait, which corresponds to the dereferencing operation of exclusive(mutable) borrowing. Since the Rust ownership semantics is a language feature throughout , the semantics of Owner / immutable borrowing(&T)/ mutable borrowing(&mut T) all appear together.
  2. std [5]:: convert [6]:: AsRef [7] . As you can see, AsRef is grouped under the convert module. if you look at the documentation, you will see that   traits [8] related to type conversions are defined in this module. For example, the familiar "From/To", "TryFrom/TryTo" and "AsRef/AsMut" also appear in pairs here, indicating that the feature is releated to type conversions. Based on the naming rules in the   Rust API Guidelines [9] , wen can infer that methods starting with as_ represent conversions from borrow -> borrow, i.e, reference -> reference , and are overhead-free. And such conversions do not fail.
  3. std [10]:: borrow [11]:: Borrow [12]. As you can see, Borrow is categorized in the borrow module. The documentation for this module is very minimal, with a single sentence saying that this is for using borrowed data. So the trait is more or less related to expressing borrwo semantics. Three   traits [13]  are provided:   Borrow [14] / BorrowMut [15]/ ToOwned [16] , which corresponds exactly to the ownership semantics.
  4. std [17]:: borrow [18]:: Cow [19]. It can be seen that Cow is also classified as a borrow module.  According to the description, Cow is a clone-on-write smart pointer. The main reason for putting it in the borrow module is to use borrowing as much as possible and avoid copying, as an optimization.

std[20]::ops[21]::Deref[22]

First, let''s look at the definition of the trait.

pub trait Deref {
    type Target: ?Sized;
    #[must_use]
    pub fn deref(&self) -> &Self::Target;
}

The definition is not complicated, Deref contains only a deref method signature. The beauty of this trait is that it is called "implicitly" by the compiler, officially called "deref coercion[23] ".

Here is an example from the standard library.

use std::ops::Deref;

struct DerefExample<T> {
    value: T
}

impl<T> Deref for DerefExample<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

let x = DerefExample { value: ''a'' };
assert_eq!(''a'', *x);

In the code, the DerefExample structure implements the Deref trait, so it can be executed using the dereference operator *. In the example, the value of the field value is returned directly.

As you can see, DerefExample has a pointer-like behavior , because it implements Deref, because it can be dereferenced. DerefExample also becomes a kind of smart pointer. This is one way to identify if a type is a smart pointer, by seeing if it implements Deref. But not all smart pointers implement Deref, some implent Drop, or both.

Now let''s summarize Deref.

If T implements Deref<Target=U>, and x is an instance of type T, then.

  1. In an immutable context, the operation of *x (when T is neither a reference nor a primitive pointer) is equivalent to *Deref::deref(&x).
  2. The value of &T is forced to be converted to the value of &U. (deref coercion).
  3. T implements all the (immutable) methods of U.

The beauty of Deref is that it enhances the Rust development experience. A typical example from the standard library is that Vec<T> shares all the methods of slice by implemented  Deref.

impl<T, A: Allocator> ops::Deref for Vec<T, A> {
    type Target = [T];

    fn deref(&self) -> &[T] {
        unsafe { slice::from_raw_parts(self.as_ptr(), self.len) }
    }
}

For example, the simplest method, len(), is actually defined in the `slice` [24] module. In Rust, when executing . call, or at the function argument position, the compiler automatically performs the implicit act of deref coercion. so it is equivalent to Vec<T> having the slice method as well.

fn main() {
 let a = vec![123];
 assert_eq!(a.len(), 3); // 当 a 调用 len() 的时候,发生 deref 强转
}

Implicit behavior in Rust is not common,  but Deref is one of them, and its implicit coercion make smart pointers easy to use.

fn main() {
    let h = Box::new("hello");
    assert_eq!(h.to_uppercase(), "HELLO");
}

For example, if we manipulate Box<T> , instead of manually dereferencing T inside to manipulate it,  as if the outer layer of Box<T> is transparent, we can manipulate T directly.

Another example.

fn uppercase(s: &str) -> String {
    s.to_uppercase()
}

fn main() {
    let s = String::from("hello");
    assert_eq!(uppercase(&s), "HELLO");
}

The argument type of the uppercase method above is obviously &str, but the actual type passed in the main function is &String, so why does it compile successfully? It is because String implementsDeref.

impl ops::Deref for String {
    type Target = str;

    #[inline]
    fn deref(&self) -> &str {
        unsafe { str::from_utf8_unchecked(&self.vec) }
    }
}

That''s the beauty of Deref. But some people may mistake it for inheritance. Big mistake.

This behavior seems a bit like inheritance, but please don''t just use Deref to simulate inheritance.

std[25]::convert[26]::AsRef[27]

Let''s look at the definition of AsRef.

pub trait AsRef<T: ?Sized> {
    fn as_ref(&self) -> &T;
}

We already know that AsRef can be used for conversions. Compared to Deref, which has an implicit behavior, AsRef is an explicit conversion.

fn is_hello<T: AsRef<str>>(s: T) {
   assert_eq!("hello", s.as_ref());
}

fn main() {
    let s = "hello";
    is_hello(s);
    
    let s = "hello".to_string();
    is_hello(s);
}

In the above example, the function of is_hello is a generic function. The conversion is achieved by qualifying T: AsRef<str> and using an explicit call like s.as_ref() inside the function. Either String or str actually implements the AsRef trait.

So now the question is, when do you use AsRef? Why not just use &T?

Consider an example like this.

pub struct Thing {
    name: String,
}

impl Thing {
    pub fn new(name: WhatTypeHere) -> Self {
        Thing { name: name.some_conversion() }
}

In the above example, the new function name has the following options for the type parameter.

  1. &str. In this case, the caller needs to pass in a reference. But in order to convert to String, the called party (callee) needs to control its own memory allocation, and will have a copy.
  2. String. In this case, the caller is fine passing String, but if it is passing a reference, it is similar to case 1.
  3. T: Into<String>. In this case, the caller can pass &str and String, but there will be memory allocation and copying during the type conversion as well.
  4. T: AsRef<str>. Same as case 3.
  5. T: Into<Cow<''a, str>>, where some allocations can be avoided. Cow will be described later.

There is no one-size-fits-all answer to the question of when to use which type. Some people just like &str and will use it no matter what. There are trade-offs here.

  1. On occasions when assignment and copying are less important,  there is no need to make type signatures too complicated, just use &str.
  2. Some need to look at method definitions and whether they need to consume ownership, or return ownership or borrowing.
  3. Some need to minimize assignment and copy, so it is necessary to use more complex type signatures, as in case 5.

Application of Deref and AsRef in API design

The wasm-bindgen[28] library contains a component called **web-sys**[29].

This component is the binding of Rust to the browser Web API. As such, web-sys makes it possible to manipulate the browser DOM with Rust code, fetch server data, draw graphics, handle audio and video, handle client-side storage, and more.

However, binding Web APIs with Rust is not that simple. For example, manipulating the DOM relies on JavaScript class inheritance, so web-sys must provide access to this inheritance hierarchy. In web-sys, access to this inheritance structure is provided using Deref and AsRef.

Using Deref

let element: &Element = ...;

element.append_child(..); // call a method on `Node`

method_expecting_a_node(&element); // coerce to `&Node` implicitly

let node: &Node = &element; // explicitly coerce to `&Node`

If you have web_sys::Element, then you can get web_sys::Node implicitly by using deref.

The use of deref is mainly for API ergonomic reasons, to make it easy for developers to use the . operation to transparently use the parent class.

Using AsRef

A large number of AsRef conversions are also implemented in web-sys for various types.

impl AsRef<HtmlElement> for HtmlAnchorElement
impl AsRef<Element> for HtmlAnchorElement
impl AsRef<Node> for HtmlAnchorElement
impl AsRef<EventTarget> for HtmlAnchorElement
impl AsRef<Object> for HtmlAnchorElement
impl AsRef<JsValue> for HtmlAnchorElement

A reference to a parent structure can be obtained by explicitly calling .as_ref().

Deref focuses on implicitly and transparently using the parent structure, while AsRef focuses on explicitly obtaining a reference to the parent structure. This is a trade-off with a specific API design, rather than a mindless simulation of OOP inheritance.

Another example of using AsRef is the http-types[30] library, which uses AsRef and AsMut to convert various types.

For example, Request is a combination of Stream / headers/ URL, so it implements AsRef<Url>, AsRef<Headers>, and AsyncRead. Similarly, Response is a combination of Stream / headers/ Status Code. So it implements AsRef<StatusCode>, AsRef<Headers>, and AsyncRead.

fn forwarded_for(headers: impl AsRef<http_types::Headers>) {
    // get the X-forwarded-for header
}

// 所以,forwarded_for 可以方便处理 Request/ Response / Trailers 
let fwd1 = forwarded_for(&req);
let fwd2 = forwarded_for(&res);
let fwd3 = forwarded_for(&trailers);

std[31]::borrow[32]::Borrow[33]

Take a look at the definition of Borrow.

pub trait Borrow<Borrowed: ?Sized> {
    fn borrow(&self) -> &Borrowed;
}

Contrast AsRef:

pub trait AsRef<T: ?Sized> {
    fn as_ref(&self) -> &T;
}

Isn''t this very similar? So, some people suggest that one of these two functions could be removed altogether. But in fact, there is a difference between Borrow and AsRef, and they both have their own uses.

The Borrow trait is used to represent borrowed data. the AsRef trait is used for type conversion. In Rust, it is common to provide different type representations for different use cases for different semantics.

A type provides a reference/borrow to T in the borrow() method by implementing Borrow<T>, expressing the semantics that it can be borrowed, rather than converted to some type T. A type can be freely borrowed as several different types, or it can be borrowed in a mutable way.

So how do you choose between Borrow and AsRef?

  • Choose Borrow when you want to abstract different borrow types in a uniform way, or when you want to create a data structure that handles self-contained values (owned) and borrowed values (borrowed) in the same way.
  • When you want to convert a type directly to a reference and you are writing generic code, choose AsRef. simpler case.

In fact, the HashMap example given in the standard library documentation explains this very well. Let me translate it for you.

HashMap<K, V> stores key-value pairs, and its API should be able to retrieve the corresponding value in the HashMap properly using either the key''s own value or its reference. Since the HashMap has to hash and compare keys, it must require that both the key''s own value and the reference behave the same when hashed and compared.

use std::borrow::Borrow;
use std::hash::Hash;

pub struct HashMap<K, V> {
    // fields omitted
}

impl<K, V> HashMap<K, V> {
    // The insert method uses the key''s own value and takes ownership of it.
    pub fn insert(&self, key: K, value: V) -> Option<V>
    where K: Hash + Eq
    {
        // ...
    }

    // If you use the get method to get the corresponding value by key, you can use the reference of key, which is denoted by &Q here
    // and requires Q to satisfy `Q: Hash + Eq + ?Sized`
    // As for K, it is expressed as a borrowed data of Q by `K: Borrow<Q>`.
    // So, the hash implementation of Q is required to be the same as K
    pub fn get<Q>(&self, k: &Q) -> Option<&V>
    where
        K: Borrow<Q>,
        Q: Hash + Eq + ?Sized
    {
        // ...
    }
}

Borrow is a bound on borrowed data and is used with additional traits, such as Hash and Eq in the example.

See another example.

//  Can this structure be used as the key of a HashMap?
pub struct CaseInsensitiveString(String);

// It implements PartialEq without problems
impl PartialEq for CaseInsensitiveString {
    fn eq(&self, other: &Self) -> bool {
       // Note that the comparison here is required to ignore ascii case
        self.0.eq_ignore_ascii_case(&other.0)
    }
}

impl Eq for CaseInsensitiveString { }

// Implementing Hash is no problem
// But since PartialEq ignores case, the hash calculation must also ignore case
impl Hash for CaseInsensitiveString {
    fn hash<H: Hasher>(&selfstate: &mut H) {
        for c in self.0.as_bytes() {
            c.to_ascii_lowercase().hash(state)
        }
    }
}

Can CaseInsensitiveString implement Borrow<str>?

Obviously, CaseInsensitiveString and str have different implementations of Hash. str does not ignore case. Therefore, Borrow<str> must not be implemented for CaseInsensitiveString, so CaseInsensitiveString cannot be used as a key for a HashMap. What happens if we force Borrow<str> to be used? It will fail due to case difference when determining the key.

But CaseInsensitiveString can be fully implemented as AsRef.

This is the difference between Borrow and AsRef. Borrow is a bit stricter and represents a completely different semantics than AsRef.

std[34]::borrow[35]::Cow[36]

Look at the definition of Cow.

pub enum Cow<''a, B> 
where
    B: ''a + ToOwned + ?Sized
 {
    Borrowed(&''a B),
    Owned(<B as ToOwned>::Owned),
}

As you can see, Cow is an enumeration. It is somewhat similar to Option, in that it represents one of two cases, Cow here means borrowed and self-owned, but only one of these cases can occur.

The main functions of Cow are:

  1. acts as a smart pointer, providing transparent immutable access to instances of this type (e.g. the original immutable methods of this type can be called directly, implementing Deref, but not DerefMut).
  2. if there is a need to modify an instance of this type, or to gain ownership of an instance of this type, Cow provides methods to do cloning and avoid repeated cloning.

Cow is designed to improve performance (reduce replication) while increasing flexibility, because most of the time, business scenarios are read more and write less. With Cow, this can be achieved in a uniform, canonical form, where object replication is done only once when a write is needed. This may reduce the number of replications significantly.

It has the following key points to master.

  1. Cow<T> can directly call the immutable methods of T, since Cow, an enumeration, implements Deref.
  2. the .to_mut() method can be used to obtain a mutable borrow with an ownership value when T needs to be modified.
    1. note that a call to .to_mut() does not necessarily result in a Clone.
    2. calling .to_mut() when ownership is already present is valid, but does not produce a new Clone.
    3. multiple calls to .to_mut() will produce only one Clone.
  3. .into_owned() can be used to create a new owned object when T needs to be modified, a process that often implies a memory copy and the creation of a new object.
    1. calling this operation will perform a Clone if the value in the previous Cow was in borrowed state.
    2. this method, whose argument is of type self, will "consume" the original instance of that type, after which the life cycle of the original instance of that type will end, and cannot be called more than once on Cow.

Cow is used more often in API design.

use std::borrow::Cow;

// Use Cow for the return value to avoid multiple copies
fn remove_spaces<''a>(input: &''a str) -> Cow<''astr> {
    if input.contains('' '') {
        let mut buf = String::with_capacity(input.len());
        for c in input.chars() {
            if c != '' '' {
                buf.push(c);
            }
        }
        return Cow::Owned(buf);
    }
    return Cow::Borrowed(input);
}

Of course, when to use Cow comes back to the "when to use AsRef" discussion in our previous article, there are trade-offs and no one-size-fits-all standard answer.

Summary

To understand the various types and traits in Rust, you need to take into account the ownership semantics and ponder the documentation and examples, which should be easy to understand. I don''t know if reading this article has solved your doubts? Feel free to share your feedback.

参考资料

[1]

std: https://doc.rust-lang.org/std/index.html

[2]

ops: https://doc.rust-lang.org/std/ops/index.html

[3]

Deref: https://doc.rust-lang.org/std/ops/trait.Deref.html

[4]

trait: https://doc.rust-lang.org/std/ops/index.html#traits

[5]

std: https://doc.rust-lang.org/std/index.html

[6]

convert: https://doc.rust-lang.org/std/convert/index.html

[7]

AsRef: https://doc.rust-lang.org/std/convert/trait.AsRef.html

[8]

traits: https://doc.rust-lang.org/std/convert/index.html#traits

[9]

Rust API Guidelines: https://rust-lang.github.io/api-guidelines/

[10]

std: https://doc.rust-lang.org/std/index.html

[11]

borrow: https://doc.rust-lang.org/std/borrow/index.html

[12]

Borrow: https://doc.rust-lang.org/std/borrow/trait.Borrow.html

[13]

traits: https://doc.rust-lang.org/std/borrow/index.html#traits

[14]

Borrow: https://doc.rust-lang.org/std/borrow/trait.Borrow.html

[15]

BorrowMut: https://doc.rust-lang.org/std/borrow/trait.BorrowMut.html

[16]

ToOwned: https://doc.rust-lang.org/std/borrow/trait.ToOwned.html

[17]

std: https://doc.rust-lang.org/std/index.html

[18]

borrow: https://doc.rust-lang.org/std/borrow/index.html

[19]

Cow: https://doc.rust-lang.org/std/borrow/enum.Cow.html

[20]

std: https://doc.rust-lang.org/std/index.html

[21]

ops: https://doc.rust-lang.org/std/ops/index.html

[22]

Deref: https://doc.rust-lang.org/std/ops/trait.Deref.html

[23]

deref coercion: https://doc.rust-lang.org/std/ops/trait.Deref.html#more-on-deref-coercion

[24]

slice : https://doc.rust-lang.org/std/primitive.slice.html

[25]

std: https://doc.rust-lang.org/std/index.html

[26]

convert: https://doc.rust-lang.org/std/convert/index.html

[27]

AsRef: https://doc.rust-lang.org/std/convert/trait.AsRef.html

[28]

wasm-bindgen: https://github.com/rustwasm/wasm-bindgen

[29]

web-sys: https://github.com/rustwasm/wasm-bindgen/tree/

[30]

http-types: https://github.com/http-rs/http-types

[31]

std: https://doc.rust-lang.org/std/index.html

[32]

borrow: https://doc.rust-lang.org/std/borrow/index.html

[33]

Borrow: https://doc.rust-lang.org/std/borrow/trait.Borrow.html

[34]

std: https://doc.rust-lang.org/std/index.html

[35]

borrow: https://doc.rust-lang.org/std/borrow/index.html

[36]

Cow: https://doc.rust-lang.org/std/borrow/enum.Cow.html


本文分享自微信公众号 - 觉学社(WakerGroup)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与 “OSC 源创计划”,欢迎正在阅读的你也加入,一起分享。

- 集合 遍历 foreach Iterator 并发修改 ConcurrentModificationException MD

- 集合 遍历 foreach Iterator 并发修改 ConcurrentModificationException MD

Markdown 版本笔记 我的 GitHub 首页 我的博客 我的微信 我的邮箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目录

[TOC]

为什么不能在 foreach 循环里进行元素的 remove/add 操作

参考: Hollis 的公众号文章

背景

在阿里巴巴 Java 开发手册中,有这样一条规定:

但是手册中并没有给出具体原因,本文就来深入分析一下该规定背后的思考。

foreach 循环

foreach 循环(Foreach loop)是计算机编程语言中的一种控制流程语句,通常用来循环遍历数组或集合中的元素。

Java 语言从 JDK 1.5.0 开始引入 foreach 循环。在遍历数组、集合方面,foreach 为开发人员提供了极大的方便。通常也被称之为增强for循环

foreach 语法格式如下:

for(元素类型t 元素变量x : 遍历对象obj){ 
     引用了xjava语句; 
}

以下实例演示了 普通 for 循环 和 foreach 循环使用:

public static void main(String[] args) {
    // 使用ImmutableList初始化一个List
    List<String> userNames = ImmutableList.of("Hollis", "hollis", "HollisChuang", "H");

    System.out.println("使用for循环遍历List");
    for (int i = 0; i < userNames.size(); i++) {
        System.out.println(userNames.get(i));
    }

    System.out.println("使用foreach遍历List");
    for (String userName : userNames) {
        System.out.println(userName);
    }
}

可以看到,使用 foreach 语法遍历集合或者数组的时候,可以起到和普通 for 循环同样的效果,并且代码更加简洁。所以,foreach 循环也通常也被称为增强 for 循环。

但是,作为一个合格的程序员,我们不仅要知道什么是增强 for 循环,还需要知道增强for循环的原理是什么

其实,增强 for 循环也是 Java 给我们提供的一个语法糖,如果将以上代码编译后的 class 文件进行反编译(使用 jad 工具)的话,可以得到以下代码:

Iterator iterator = userNames.iterator();
do{
    if(!iterator.hasNext())
        break;
    String userName = (String)iterator.next();
    if(userName.equals("Hollis"))
        userNames.remove(userName);
} while(true);
System.out.println(userNames);

可以发现,原本的增强 for 循环,其实是依赖了 while 循环和 Iterator 实现的。

问题重现

规范中指出不让我们在 foreach 循环中对集合元素做 add/remove 操作,那么,我们尝试着做一下看看会发生什么问题。

首先使用双括弧语法(double-brace syntax)建立并初始化一个 List,其中包含四个字符串,分别是 Hollis、hollis、HollisChuang 和 H:

List<String> userNames = new ArrayList<String>() {{
    add("Hollis");
    add("hollis");
    add("HollisChuang");
    add("H");
}};

然后使用普通 for 循环对 List 进行遍历,删除 List 中元素内容等于 Hollis 的元素,然后输出 List:

for (int i = 0; i < userNames.size(); i++) {
    if (userNames.get(i).equals("Hollis")) {
        userNames.remove(i);
    }
}
System.out.println(userNames);

输出结果如下

[hollis, HollisChuang, H]

以上是使用普通的 for 循环在遍历的同时进行删除,那么,我们再看下,如果使用增强 for 循环的话会发生什么:

for (String userName : userNames) {
    if (userName.equals("Hollis")) {
        userNames.remove(userName);
    }
}
System.out.println(userNames);

以上代码,使用增强 for 循环遍历元素,并尝试删除其中的 Hollis 字符串元素。运行以上代码,会抛出以下异常:

java.util.ConcurrentModificationException

同样的,读者可以尝试下在增强 for 循环中使用 add 方法添加元素,结果也会同样抛出该异常。

之所以会出现这个异常,是因为触发了一个 Java 集合的错误检测机制 ——fail-fast

fail-fast

接下来,我们就来分析下在增强 for 循环中 add/remove 元素的时候会抛出 java.util.ConcurrentModificationException 的原因,即解释下到底什么是 fail-fast 进制。

fail-fast,即快速失败,它是 Java 集合的一种错误检测机制当多个线程对非fail-safe的集合类进行结构上的改变的操作时,有可能会产生fail-fast机制,这个时候就会抛出 ConcurrentModificationException(当方法检测到对象的并发修改,但不允许这种修改时就抛出该异常)。

需要注意的是,即使不是多线程环境,如果单线程违反了规则,同样也有可能会抛出改异常。

那么,在增强 for 循环进行元素删除,是如何违反了规则的呢?

要分析这个问题,我们先将增强 for 循环这个语法糖进行解糖(使用 jad 对编译后的 class 文件进行反编译),得到以下代码:

public static void main(String[] args) {
    // 使用ImmutableList初始化一个List
    List<String> userNames = new ArrayList<String>() {{
        add("Hollis");
        add("hollis");
        add("HollisChuang");
        add("H");
    }};

    Iterator iterator = userNames.iterator();
    do
    {
        if(!iterator.hasNext())
            break;
        String userName = (String)iterator.next();
        if(userName.equals("Hollis"))
            userNames.remove(userName);
    } while(true);
    System.out.println(userNames);
}

然后运行以上代码,同样会抛出异常。我们来看一下 ConcurrentModificationException 的完整堆栈:

通过异常堆栈我们可以到,异常发生的调用链 ForEachDemo 的第 23 行,Iterator.next 调用了 Iterator.checkForComodification 方法 ,而异常就是 checkForComodification 方法中抛出的。

其实,经过 debug 后,我们可以发现,如果 remove 代码没有被执行过,iterator.next 这一行是一直没报错的。抛异常的时机也正是 remove执行之后的的那一次next方法的调用

我们直接看下 checkForComodification 方法的代码,看下抛出异常的原因:

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

代码比较简单,modCount != expectedModCount 的时候,就会抛出 ConcurrentModificationException。

那么,就来看一下,remove/add 操作室如何导致 modCount 和 expectedModCount 不相等的吧。

remove/add 做了什么

首先,我们要搞清楚的是,到底 modCount 和 expectedModCount 这两个变量都是个什么东西。

通过翻源码,我们可以发现:

  • modCount 是 ArrayList 中的一个成员变量。它表示该集合实际被修改的次数。
  • expectedModCount 是 ArrayList 中的一个内部类 ——Itr 中的成员变量。expectedModCount 表示这个迭代器期望该集合被修改的次数。其值是在 ArrayList.iterator 方法被调用的时候初始化的。只有通过迭代器对集合进行操作,该值才会改变。

Itr 是一个 Iterator 的实现,使用 ArrayList.iterator 方法可以获取到的迭代器就是 Itr 类的实例。

他们之间的关系如下:

class ArrayList{
    private int modCount;
    public void add();
    public void remove();
    private class Itr implements Iterator<E> {
        int expectedModCount = modCount;
    }
    public Iterator<E> iterator() {
        return new Itr();
    }
}

其实,看到这里,大概很多人都能猜到为什么 remove/add 操作之后,会导致 expectedModCount 和 modCount 不想等了。

通过翻阅代码,我们也可以发现,remove 方法核心逻辑如下: !

可以看到,它只修改了 modCount,并没有对 expectedModCount 做任何操作。

简单总结一下,之所以会抛出 ConcurrentModificationException 异常,是因为我们的代码中使用了增强 for 循环,而在增强for循环中,集合遍历是通过iterator进行的,但是元素的add/remove却是直接使用的集合类自己的方法。这就导致 iterator 在遍历的时候,会发现有一个元素在自己不知不觉的情况下就被删除 / 添加了,就会抛出一个异常,用来提示用户,可能发生了并发修改

正确姿势

至此,我们介绍清楚了不能在 foreach 循环体中直接对集合进行 add/remove 操作的原因。

但是,很多时候,我们是有需求需要过滤集合的,比如删除其中一部分元素,那么应该如何做呢?有几种方法可供参考:

直接使用普通 for 循环进行操作

我们说不能在 foreach 中进行,但是使用普通的 for 循环还是可以的,因为普通 for 循环并没有用到 Iterator 的遍历,所以压根就没有进行 fail-fast 的检验。

for (int i = 0; i < 1; i++) {
    if (userNames.get(i).equals("Hollis")) {
        userNames.remove(i);
    }
}

直接使用 Iterator 进行操作

除了直接使用普通 for 循环以外,我们还可以直接使用 Iterator 提供的 remove 方法。

Iterator iterator = userNames.iterator();
while (iterator.hasNext()) {
    if (iterator.next().equals("Hollis")) {
        iterator.remove();
    }
}

如果直接使用 Iterator 提供的 remove 方法,那么就可以修改到 expectedModCount 的值。那么就不会再抛出异常了。其实现代码如下:

使用 Java8 中提供的 filter 过滤

Java 8 中可以把集合转换成流,对于流有一种 filter 操作, 可以对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。

userNames = userNames.stream()
    .filter(userName -> !userName.equals("Hollis"))
    .collect(Collectors.toList());

使用 fail-safe 的集合类

在 Java 中,除了一些普通的集合类以外,还有一些采用了 fail-safe 机制的集合类,比如 ConcurrentLinkedDeque。这样的集合容器在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历

由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发 ConcurrentModificationException。

基于拷贝内容的优点是避免了 ConcurrentModificationException,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

java.util.concurrent 包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

使用增强 for 循环其实也可以

如果,我们非常确定在一个集合中,某个即将删除的元素只包含一个的话, 比如对 Set 进行操作,那么其实也是可以使用增强 for 循环的,只要在删除之后,立刻结束循环体,不要再继续进行遍历就可以了,也就是说不让代码执行到下一次的next方法

for (String userName : userNames) {
    if (userName.equals("Hollis")) {
        userNames.remove(userName);
        break;
    }
}

以上这五种方式都可以避免触发 fail-fast 机制,避免抛出异常。如果是并发场景,建议使用 concurrent 包中的容器,如果是单线程场景,Java8 之前的代码中,建议使用 Iterator 进行元素删除,Java8 及更新的版本中,可以考虑使用 Stream 及 filter。

总结

我们使用的增强 for 循环,其实是 Java 提供的语法糖,其实现原理是借助 Iterator 进行元素的遍历。

但是如果在遍历过程中,不通过 Iterator,而是通过集合类自身的方法对集合进行添加 / 删除操作。那么在 Iterator 进行下一次的遍历时,经检测发现有一次集合的修改操作并未通过自身进行,那么可能是发生了并发被其他线程执行的,这时候就会抛出异常,来提示用户可能发生了并发修改,这就是所谓的 fail-fast 机制。

当然还是有很多种方法可以解决这类问题的。比如使用普通 for 循环、使用 Iterator 进行元素删除、使用 Stream 的 filter、使用 fail-safe 的类等。

AI - TensorFlow - 分类与回归(Classification vs Regression)

AI - TensorFlow - 分类与回归(Classification vs Regression)

分类与回归

分类(Classification)与回归(Regression)的区别在于输出变量的类型
通俗理解,定量输出称为回归,或者说是连续变量预测;定性输出称为分类,或者说是离散变量预测。

回归问题的预测结果是连续的,通常是用来预测一个值,如预测房价、未来的天气情况等等。
一个比较常见的回归算法是线性回归算法(LR,Linear Regression)。
回归分析用在神经网络上,其最上层不需要加上softmax函数,而是直接对前一层累加即可。
回归是对真实值的一种逼近预测。

分类问题的预测结果是离散的,是用于将事物打上一个标签,通常结果为离散值。
分类通常是建立在回归之上,分类的最后一层通常要使用softmax函数进行判断其所属类别。
分类并没有逼近的概念,最终正确结果只有一个,错误的就是错误的,不会有相近的概念。
最常见的分类方法是逻辑回归(Logistic Regression),或者叫逻辑分类。

 

MNIST数据集

MNIST(Mixed National Institute of Standards and Technology database)是一个计算机视觉数据集;

  • 官方下载地址:http://yann.lecun.com/exdb/mnist/
  • 包含70000张手写数字的灰度图片,其中60000张为训练图像和10000张为测试图像;
  • 每一张图片都是28*28个像素点大小的灰度图像;

如果无法从网络下载MNIST数据集,可从官方下载,然后存放在当前脚本目录下的新建MNIST_data目录即可;

  •  MNIST_data\train-images-idx3-ubyte.gz
  • MNIST_data\train-labels-idx1-ubyte.gz
  • MNIST_data\t10k-images-idx3-ubyte.gz
  • MNIST_data\t10k-labels-idx1-ubyte.gz

 

示例程序

 1 # coding=utf-8
 2 from __future__ import print_function
 3 import tensorflow as tf
 4 from tensorflow.examples.tutorials.mnist import input_data  # MNIST数据集
 5 import os
 6 
 7 os.environ[''TF_CPP_MIN_LOG_LEVEL''] = ''2''
 8 
 9 old_v = tf.logging.get_verbosity()
10 tf.logging.set_verbosity(tf.logging.ERROR)
11 
12 mnist = input_data.read_data_sets(''MNIST_data'', one_hot=True)  # 准备数据(如果本地没有数据,将从网络下载)
13 
14 
15 def add_layer(inputs, in_size, out_size, activation_function=None, ):
16     Weights = tf.Variable(tf.random_normal([in_size, out_size]))
17     biases = tf.Variable(tf.zeros([1, out_size]) + 0.1, )
18     Wx_plus_b = tf.matmul(inputs, Weights) + biases
19     if activation_function is None:
20         outputs = Wx_plus_b
21     else:
22         outputs = activation_function(Wx_plus_b, )
23     return outputs
24 
25 
26 def compute_accuracy(v_xs, v_ys):
27     global prediction
28     y_pre = sess.run(prediction, feed_dict={xs: v_xs})
29     correct_prediction = tf.equal(tf.argmax(y_pre, 1), tf.argmax(v_ys, 1))
30     accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
31     result = sess.run(accuracy, feed_dict={xs: v_xs, ys: v_ys})
32     return result
33 
34 
35 xs = tf.placeholder(tf.float32, [None, 784])  # 输入数据是784(28*28)个特征
36 ys = tf.placeholder(tf.float32, [None, 10])  # 输出数据是10个特征
37 
38 prediction = add_layer(xs, 784, 10, activation_function=tf.nn.softmax)  # 激励函数为softmax
39 
40 cross_entropy = tf.reduce_mean(-tf.reduce_sum(ys * tf.log(prediction),
41                                               reduction_indices=[1]))  # loss函数(最优化目标函数)选用交叉熵函数
42 
43 train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)  # train方法(最优化算法)采用梯度下降法
44 
45 sess = tf.Session()
46 init = tf.global_variables_initializer()
47 sess.run(init)
48 
49 for i in range(1000):
50     batch_xs, batch_ys = mnist.train.next_batch(100)  # 每次只取100张图片,免得数据太多训练太慢
51     sess.run(train_step, feed_dict={xs: batch_xs, ys: batch_ys})
52     if i % 50 == 0:  # 每训练50次输出预测精度
53         print(compute_accuracy(
54             mnist.test.images, mnist.test.labels))

 

程序运行结果:

Extracting MNIST_data\train-images-idx3-ubyte.gz
Extracting MNIST_data\train-labels-idx1-ubyte.gz
Extracting MNIST_data\t10k-images-idx3-ubyte.gz
Extracting MNIST_data\t10k-labels-idx1-ubyte.gz
0.146
0.6316
0.7347
0.7815
0.8095
0.8198
0.8306
0.837
0.8444
0.8547
0.8544
0.8578
0.8651
0.8649
0.8705
0.8704
0.8741
0.8719
0.8753
0.8756

 

问题处理

问题现象

执行程序提示“Please use tf.data to implement this functionality.”等信息

WARNING:tensorflow:From D:/Anliven/Anliven-Code/PycharmProjects/TempTest/TempTest_2.py:13: read_data_sets (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.
Instructions for updating:
Please use alternatives such as official/mnist/dataset.py from tensorflow/models.
WARNING:tensorflow:From C:\Users\anliven\AppData\Local\conda\conda\envs\mlcc\lib\site-packages\tensorflow\contrib\learn\python\learn\datasets\mnist.py:260: maybe_download (from tensorflow.contrib.learn.python.learn.datasets.base) is deprecated and will be removed in a future version.
Extracting MNIST_data\train-images-idx3-ubyte.gz
Instructions for updating:
Please write your own downloading logic.
WARNING:tensorflow:From C:\Users\anliven\AppData\Local\conda\conda\envs\mlcc\lib\site-packages\tensorflow\contrib\learn\python\learn\datasets\mnist.py:262: extract_images (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.
Instructions for updating:
Please use tf.data to implement this functionality.
Extracting MNIST_data\train-labels-idx1-ubyte.gz
......
......

处理方法

参考链接:https://stackoverflow.com/questions/49901806/tensorflow-importing-mnist-warnings

 

android – java.util.ConcurrentModificationException和SharedPreference

android – java.util.ConcurrentModificationException和SharedPreference

我在崩溃日志中发现了这个崩溃.我不知道它什么时候发生.有人能给我建议是什么原因吗?也许有人有同样的崩溃.
java.util.ConcurrentModificationException
       at java.util.HashMap$HashIterator.nextEntry(HashMap.java:787)
       at java.util.HashMap$KeyIterator.next(HashMap.java:814)
       at com.android.internal.util.XmlUtils.writeSetXml(XmlUtils.java:350)
       at com.android.internal.util.XmlUtils.writeValueXml(XmlUtils.java:688)
       at com.android.internal.util.XmlUtils.writeMapXml(XmlUtils.java:295)
       at com.android.internal.util.XmlUtils.writeMapXml(XmlUtils.java:264)
       at com.android.internal.util.XmlUtils.writeMapXml(XmlUtils.java:230)
       at com.android.internal.util.XmlUtils.writeMapXml(XmlUtils.java:187)
       at android.app.SharedPreferencesImpl.writetoFile(SharedPreferencesImpl.java:597)
       at android.app.SharedPreferencesImpl.access$800(SharedPreferencesImpl.java:51)
       at android.app.SharedPreferencesImpl$2.run(SharedPreferencesImpl.java:512)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
       at java.lang.Thread.run(Thread.java:818)

解决方法

我们在使用SharedPreferences getStringSet方法时看到了这个异常.此方法返回不应修改的集合.在将SharedPreferences对象序列化为flash时修改此集将产生ConcurrentModificationException.

如果您使用getAll并修改返回的地图,也会发生这种情况.

见documentation

Appium问题解决方案(8)- selenium.common.exceptions.WebDriverException: Message: An unknown server-side error occurred while processing the command. Original error: Could not sign with default certificate.

Appium问题解决方案(8)- selenium.common.exceptions.WebDriverException: Message: An unknown server-side error occurred while processing the command. Original error: Could not sign with default certificate.

背景

运行时代码报错:

selenium.common.exceptions.WebDriverException: Message: An unkNown server-side error occurred while processing the command. Original error: Could not sign with default certificate. Original error Command ''C:\\Program Files\\Java\\jdk1.8.0_131\\bin\\java.exe' -jar 'C:\\Program Files\\Appium\\resources\\app\\node_modules\\appium\\node_modules\\appium-adb\\jars\\sign.jar' 'C:\\Program Files\\Appium\\resources\\app\\node_modules\\appium\\node_modules\\appium-uiautomator2-server\\apks\\appium-uiautomator2-server-debug-androidTest.apk' --override' exited with code 1

问题分析

该问题主要是证书签名错误,猜测是用了新版appium装了驱动之后,又换成旧版本的appium这个时候会出现两者的签名证书不一致导致认证错误

 

解决方案一

就是把appium-desktop换成新的,或者进入设备-应用管理-搜索 appium 相关程序,手动卸载掉再执行一遍脚本

 

解决方案二

还有一种解释是运行appium-server时没有足够的权限,使用管理员去打开appium-server可以解决这个问题

关于Rust Concept Clarification: Deref vs AsRef vs Borrow vs Cow的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于- 集合 遍历 foreach Iterator 并发修改 ConcurrentModificationException MD、AI - TensorFlow - 分类与回归(Classification vs Regression)、android – java.util.ConcurrentModificationException和SharedPreference、Appium问题解决方案(8)- selenium.common.exceptions.WebDriverException: Message: An unknown server-side error occurred while processing the command. Original error: Could not sign with default certificate.等相关知识的信息别忘了在本站进行查找喔。

本文标签: