سؤال

هل هناك ضمان أنه عند العمل مع الدفق، سيتم تنفيذ العمليات الوسيطة بترتيب البرنامج؟أظن أن هذا هو الحال أو أنه سيؤدي إلى أخطاء دقيقة للغاية ولكن لم أتمكن من العثور على إجابة محددة.

مثال:

List<String> list = Arrays.asList("a", "b", "c");
List<String> modified = list.parallelStream()
        .map(s -> s + "-" + s)                 //"a-a", "b-b", "c-c"
        .filter(s -> !s.equals("b-b"))         //"a-a", "c-c"
        .map(s -> s.substring(2))              //"a", "c"
        .collect(toList());

فهل هذا مضمون للعودة دائما ["a", "c"] أو ["c", "a"]؟(إذا تم تنفيذ عملية الخريطة الأخيرة قبل عملية الخريطة الأولى، فقد يؤدي ذلك إلى حدوث استثناء - وبالمثل إذا تم تنفيذ عامل التصفية بعد عملية الخريطة الثانية، فسيتم الاحتفاظ بـ "b" في القائمة النهائية)

هل كانت مفيدة؟

المحلول

لقد نشأ سؤالك لأنك تقوم بالتعيين من نوع واحد إلى نفس النوع.إذا فكرت في العمليات الشكلية التي تقوم بها، يصبح من الواضح أنه لا توجد طريقة لتغيير ترتيب العمليات المحددة:

  • قمت بتعيين عناصر من أ Stream<A> إلى نوع تعسفي B خلق شيء Stream<B>
  • قمت بتطبيق أ Filter<B> على نتيجة التعيين الأول
  • يمكنك تعيين التصفية Stream<B> إلى نوع تعسفي C خلق شيء Stream<C>
  • تقوم بجمع العناصر من النوع C الى List<C>

بالنظر إلى هذه الخطوات الرسمية، يجب أن يكون واضحًا أنه لا توجد طريقة لتغيير ترتيب هذه الخطوات بسبب متطلبات توافق النوع.

حقيقة أنه في حالتك الخاصة جميع الأنواع الثلاثة تحدث String لا يغير منطق كيفية Streamالعمل.ضع في اعتبارك أن الأنواع الفعلية التي تستخدمها لمعلمات النوع يتم مسحها ولا تكون موجودة في وقت التشغيل.

ال Stream التنفيذ قد يفرض العمليات حيثما يكون ذلك مفيدًا، على سبيل المثال.أداء أ sorted و distinct دفعة واحدة ولكن هذا يتطلب أن يتم طلب كلتا العمليتين على نفس العناصر و Comparator.أو ببساطة، يجب ألا تغير التحسينات الداخلية دلالات العمليات المطلوبة.

نصائح أخرى

توجد بالفعل عدة أسئلة حول الطلب مضمنة في السؤال الأصلي.

إجابة هولجر يغطي ترتيب عمليات الدفق داخل خط الأنابيب. لعنصر دفق معين يجب تنفيذ عمليات خطوط الأنابيب كما هو مكتوب في البرنامج، لأنه بشكل عام، يجب أن تتطابق الأنواع، وأيضًا لأنه ليس من المنطقي القيام بذلك بأي طريقة أخرى.بدءًا من المثال الأصلي، لا تستطيع مكتبة التدفقات إعادة ترتيب العمليات كما لو كانت مكتوبة،

List<String> modified = list.parallelStream()
    .filter(s -> !s.equals("b-b")) // these two operations are swapped
    .map(s -> s + "-" + s)         // compared to the original example
    .map(s -> s.substring(2))
    .collect(toList());

لأن النتيجة ستكون [أ، ب، ج].هذا لن يحدث.

كان السؤال الأصلي يدور حول ما إذا كان يمكن أن تكون الإجابة [ج، أ] بدلاً من [أ، ج].هذا في الواقع سؤال حول نوع مختلف من الترتيب، والذي نشير إليه باسم أمر اللقاء.تم ذكر هذا المفهوم في وثائق الحزمة java.util.stream.لسوء الحظ لم يتم تعريفه بشكل واضح في أي مكان على حد علمي.باختصار، يتعلق الأمر بالموضع النسبي للعناصر داخل الدفق (على عكس أمر التنفيذ) وما إذا كان هذا الموضع له أي دلالات.

على سبيل المثال، فكر في التدفقات التي يتم الحصول عليها من HashSet ومن ArrayList.لا يحتوي الدفق المستند إلى HashSet على ترتيب لقاء محدد، أو بعبارة أخرى، فهو غير مرتب.إذا قمت بوضع مجموعة من العناصر في HashSet ثم كررتها، فسوف تظهر بترتيب ربما لا علاقة له بالترتيب الذي وضعتها فيه.

ومع ذلك، فإن الدفق المبني على قائمة له ترتيب لقاء محدد.في المثال الأصلي، القائمة هي [a، b، c]، ومن الواضح أن "a" تأتي قبل "b" الذي يأتي قبل "c".يتم الحفاظ على هذا الموضع بشكل عام من خلال عمليات الدفق من المصدر وحتى الإخراج.

اسمحوا لي بتعديل المثال الأصلي لإظهار أهمية ترتيب اللقاء.كل ما فعلته هو تغيير ترتيب السلاسل في القائمة الأصلية:

List<String> list = Arrays.asList("c", "b", "a");
List<String> modified = list.parallelStream()
    .map(s -> s + "-" + s)                 //"c-c", "b-b", "a-a"
    .filter(s -> !s.equals("b-b"))         //"c-c", "a-a"
    .map(s -> s.substring(2))              //"c", "a"
    .collect(toList());

وكما نتوقع فإن الناتج هو [c,a].لنقم الآن بتشغيل الدفق على مجموعة بدلاً من القائمة:

List<String> list = Arrays.asList("c", "b", "a");
Set<String> set = new HashSet<>(list);
List<String> modified = set.parallelStream()
    .map(s -> s + "-" + s)
    .filter(s -> !s.equals("b-b"))
    .map(s -> s.substring(2))
    .collect(toList());

هذه المرة، النتيجة هي [أ، ج].ال عمليات خطوط الأنابيب (خريطة، مرشح، خريطة) لم تتغير الترتيب، ولكن منذ أمر اللقاء من العناصر في المجموعة غير محددة، تنتهي النتائج في قائمة الوجهة بترتيب يختلف عن النتيجة السابقة.

(اضطررت إلى تغيير ترتيب القيم في القائمة الأصلية، لأنه يحدث أن ترتيب تكرار HashSet يرتبط برموز التجزئة للعناصر، وأمثلة السلسلة البسيطة الواردة هنا تحتوي على رموز تجزئة متتالية.)

هناك "ترتيب" آخر يمكن للمرء أن يأخذه بعين الاعتبار، وهو ترتيب التنفيذ النسبي لعمليات خطوط الأنابيب فيما بينها مختلف عناصر.بالنسبة للتيارات المتوازية، هذا غير حتمي تمامًا.إحدى طرق ملاحظة ذلك هي تعديل كائن من داخل عملية خط أنابيب.(للقيام بذلك بشكل آمن، يجب بالطبع أن يكون الكائن الذي يتم تحوره آمنًا، وليس من الحكمة الاعتماد على ترتيب أي من هذه الآثار الجانبية.) إليك مثال:

List<Integer> list1 = Collections.synchronizedList(new ArrayList<>());
List<Integer> list2 =
    IntStream.range(0, 10)
        .parallel()
        .boxed()
        .peek(i -> list1.add(i))
        .collect(toList());
System.out.println(list1);
System.out.println(list2);

على نظامي، الإخراج هو:

[5, 6, 2, 3, 4, 8, 9, 7, 0, 1]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

يتم الاحتفاظ بترتيب مواجهة المصدر للإخراج في القائمة 2، لكن ترتيب القائمة 1 يختلف بشكل عام.في الواقع، ترتيب العناصر في list1 يختلف من تشغيل لآخر، في حين أن ترتيب العناصر في list2 هو نفسه دائما.

باختصار، هناك ثلاثة أنواع مختلفة من الترتيب الموضح هنا:

  • ترتيب عمليات خطوط الأنابيب على عنصر معين؛
  • ترتيب لقاء الدفق، و
  • ترتيب تنفيذ عمليات خطوط الأنابيب على عناصر مختلفة.

كلهم متميزون.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top